プログラミング
ArcadeDBのためのネイティブPythonおよびTypeScriptドライバー、OpenAPIとProtobufから生成
Native Python and TypeScript Drivers for ArcadeDB, from OpenAPI and Protobuf (arcadedb.com)
要約
ArcadeDBへのPythonまたはNode.jsからの接続方法に新たな選択肢が登場しました。これまで手動でのHTTP呼び出しや他データベース用ドライバーの借用が必要でしたが、今回、ArcadeDB自身が公開するコントラクトから生成された4つの公式ドライバー(Python用HTTP/gRPC、TypeScript/JavaScript用HTTP/gRPC)がリリースされました。これらはApache-2.0ライセンスで提供され、開発途上ですが、よりネイティブな体験を提供します。
全文翻訳
これまで、PythonまたはNode.jsからArcadeDBに接続するには、REST APIに対して手動でHTTP呼び出しを記述するか、他のデータベース用に構築されたドライバーを借りて、そのデータベースプロトコルが公開するArcadeDBのサブセット内に留まるかのどちらかでした。どちらも機能しますが、どちらもネイティブとは呼べないクライアントでした。
これは、ArcadeDB自体が公開するコントラクトから生成された4つの公開クライアント(Python用のHTTPドライバーとgRPCドライバー、TypeScript/JavaScript用のHTTPドライバーとgRPCドライバー)を収録した新しいリポジトリ、arcadedb-driversによって変わります。これら4つすべてがApache-2.0ライセンスであり、すべてArcadeDB自身が公開するコントラクトから生成され、すべてが公開レジストリに掲載されています。これらはまだ新しく、バージョン0.1.0で、活発な開発が進んでおり、さらに多くの言語への対応も予定されています。
4つのパッケージ
| パッケージ名 | 言語 | API | インストール |
| :------------------------ | :--------------- | :--------------------------------- | :--------------------------------------------------------------------------- |
| arcadedb-driver | Python | HTTP | pip install arcadedb-driver |
| arcadedb-driver-grpc | Python | gRPC | pip install arcadedb-driver-grpc |
| @arcadedb/driver | TypeScript/JS | HTTP | npm install @arcadedb/driver |
| @arcadedb/driver-grpc | TypeScript/JS | gRPC | npm install @arcadedb/driver-grpc |
4つすべてが実行中のサーバーと通信するため、組み込みデプロイメントではなくクライアント・サーバーのデプロイメントを想定しています。PythonパッケージにはPython 3.10以降が必要です。TypeScriptパッケージにはNode 20以降が必要で、ESMのみです(require()ではなくimportしてください)。
各パッケージのバージョン0.1.0はArcadeDBサーバー26.9.1を対象としており、各パッケージのREADMEにはドライバーバージョンとサーバーバージョンの対応表が記載されています。
HTTPかgRPCか?
言語ごとに2つのドライバーがあるのは優柔不断ではありません。HTTPとgRPCはArcadeDBが話す2つのトランスポートであり、それぞれが他のものよりも得意なワークロードを処理します。
HTTPドライバーを使用する場合
* ブラウザコードを書いている場合
* サーバーレス関数にいてコールドスタートを心配している場合
* トラフィックが通常の要求/応答の場合
* 既存のHTTPインフラストラクチャ(プロキシ、ゲートウェイ、トレーシング)を再利用したい場合
gRPCドライバーを使用する場合
* サーバー間コードを書いている場合
* 大量の結果セットを読み取ってストリーミングしたい場合
* バルク挿入中で、1つの長寿命接続を維持したい場合
* トラフィックがスループット重視で持続的な場合
* ネイティブ双方向ストリーミングが必要な場合
まずはHTTPから始めましょう。どこでも動作し、fetchやhttpx以外に何も必要とせず、ほとんどのアプリケーショントラフィックではプロトコルがボトルネックになりません。スループットの数値がそれを正当化できる場合に、ワークロードをgRPCに移行してください。
1つの制約について、1日を費やすのは簡単なので、ここに明確に記載します。gRPCドライバーのブラウザビルドはなく、サーバーが変更されない限り、将来も存在しません。ArcadeDBのGrpcServerPluginは、Netty上で構築されたプレーンなgrpc-java over HTTP/2であり、gRPC-Webハンドラー、Connectプロトコル、またはその前のサーブレットアダプターはありません。ブラウザは生のHTTP/2 gRPCフレーミングを話すことができないため、どの言語のクライアントライブラリもブラウザタブからこのサーバーに到達できません。ブラウザコードはHTTPドライバーを使用します。制限はサーバー側にあります。
接続とクエリ
HTTPドライバーは開始点です。
Python、同期的に:
```python
from arcadedb_driver import ArcadeDBServer, basic_auth
with ArcadeDBServer(base_url="http://localhost:2480", auth=basic_auth("root", "playwithdata")) as srv:
db = srv.db("mydb")
envelope = db.query(language="sql", command="SELECT FROM Person WHERE age > ?", params={"1": 21})
print(envelope.result)
```
非同期ファサードは、メソッドごとに同期ファサードをミラーリングします:
```python
import asyncio
from arcadedb_driver import AsyncArcadeDBServer, basic_auth
async def main() -> None:
async with AsyncArcadeDBServer(base_url="http://localhost:2480", auth=basic_auth("root", "playwithdata")) as srv:
db = srv.db("mydb")
envelope = await db.query(language="sql", command="SELECT FROM Person WHERE age > ?", params={"1": 21})
print(envelope.result)
asyncio.run(main())
```
どちらもコンテキストマネージャーです。なぜなら、どちらも独自の接続プールを持つhttpxクライアントを所有しており、それを解放する必要があるからです。
本番環境で人々を驚かせる可能性のある1つの詳細:タイムアウトを省略すると、httpxのデフォルト5秒にフォールバックするのではなく、タイムアウトが完全に無効になります。なぜなら、httpxでは明示的なtimeout=Noneはその意味を正確に伝えるからです。リクエストを制限したい場合は、httpx.Timeoutを渡してください。
TypeScript、同じクエリ:
```typescript
import { createClient, basicAuth } from "@arcadedb/driver";
const server = createClient({
baseUrl: "http://localhost:2480",
auth: basicAuth("root", "playwithdata"),
});
const db = server.db("mydb");
const { result } = await db.query({
language: "sql",
command: "SELECT FROM Person WHERE age > ?",
params: { 1: 21 },
});
```
セッショントークン(/api/v1/loginから返されるものなど)のようなベアラートークンは、両方の言語で同じように機能します。basic_authをbearer_authに、またはbasicAuthをbearerAuthに置き換えてください。
ArcadeDBはマルチモデルであるため、言語はここで実際の作業を行います。「sql」、「cypher」、「gremlin」:同じクエリ呼び出しがすべてに到達し、ドライバーはあなたがどれを選んだかを気にしません。
結果エンベロープ、およびtruncatedがなぜ重要なのか
HTTPドライバーはどちらも、行の生の配列を返しません。どちらも完全な応答エンベロープを返します:
```typescript
interface QueryEnvelope<T> {
result: T[];
limit: number;
returned: number;
truncated: boolean;
}
```
ドライバーのすべてのAPI決定の中で、これは無駄な儀式のように見える可能性が最も高いものです。そのため、その理由を説明します。
truncatedがtrueなのは、サーバーのシリアライザーが行の上限に達し、クエリにまだ書き込むべき行が残っている場合です。その場合、resultは部分的な回答であり、短いが完全なものではありません。そして、これら2つは形状では区別できません。5,000行の配列が途中で停止した場合と、一致するレコードがなくなったために終了した5,000行の配列は、見た目が全く同じです。
エンベロープをアンラップしてresultのみを返すドライバーは、確認できない値をあなたに渡すことになります。ですから、確認してください。
truncatedがtrueの場合は、より狭いフィルターまたはより高いlimitで再クエリしてください。ただし、limitを増やすことが常に解決策とは限りません。サーバーのハードシーリング(arcadedb.server.httpQueryMaxResultRows)を超える実際のサイズの結果は、切り捨てられるのではなく413エラーで拒否されるため、それ以降はより狭いフィルターが唯一の解決策です。
トランザクション
両方のHTTPドライバーは、ArcadeDBのサーバーサイドトランザクションセッションを、その言語がすでに持っているイディオムでラップします。
Pythonはコンテキストマネージャーを使用します:
```python
with srv.db("mydb").transaction() as tx:
tx.command(language="sql", command="INSERT INTO Account SET balance = 100")
total = tx.query(language="sql", command="SELECT sum(balance) as total FROM Account").result[0]["total"]
```
TypeScriptはコールバックを使用します:
```typescript
const total = await db.transaction(async (tx) => {
await tx.command({ language: "sql", command: "INSERT INTO Account SET balance = 100" });
const { result } = await tx.query({ language: "sql", command: "SELECT sum(balance) as total FROM Account" });
return result[0].total;
});
```
両方のルールは同じで、正しく理解すべきものです。トランザクションに参加すべきすべての呼び出しは、トランザクションから開いた外部dbオブジェクトではなく、txハンドルを通じて行われます。
トランザクションが開いている間に外部ハンドルを通じて行われた呼び出しは、トランザクション外で自動的にコミットされます。あたかもトランザクションが開いていないかのように。
コミットとロールバックの契約は、両方の言語で3つの条項があります。
ブロックが正常に終了し、トランザクションがコミットされます。
ブロックで例外が発生し、トランザクションがロールバックされ、ブロック自体の例外が伝播します。ロールバックも失敗した場合、その失敗は、あなたが実際に問い合わせたエラーを置き換えるのではなく、__cause__(Python)またはerr.cause(TypeScript)としてアタッチされます。
コミット自体が失敗した場合、まずベストエフォートのロールバックが発行されるため、コミットのエラーが再スローされる前に、サーバーサイドセッションはarcadedb.server.httpTxExpireTimeoutによって回収されるまで開いたままになりません。
gRPC over ストリーミング
HTTPの要求/応答形式がコストとなるワークロードのためにgRPCドライバーが存在します。
HTTP経由の大規模な結果セットは、繰り返しの呼び出しによるページングを意味します。gRPC経由では、それは1つのストリームです。
```python
from arcadedb_driver_grpc import create_client, messages
with create_client("localhost:50051", insecure=True) as client:
response = client.raw.ExecuteQuery(
messages.ExecuteQueryRequest(
database="mydb",
query="S
```