HN 日本語サマリー

← 一覧へ戻る
プログラミング

ArcadeDBのためのネイティブPythonおよびTypeScriptドライバー、OpenAPIとProtobufから生成

Native Python and TypeScript Drivers for ArcadeDB, from OpenAPI and Protobuf (arcadedb.com)

5 pointsby lvca0 コメント

要約

ArcadeDBへのPythonまたはNode.jsからの接続方法に新たな選択肢が登場しました。これまで手動でのHTTP呼び出しや他データベース用ドライバーの借用が必要でしたが、今回、ArcadeDB自身が公開するコントラクトから生成された4つの公式ドライバー(Python用HTTP/gRPC、TypeScript/JavaScript用HTTP/gRPC)がリリースされました。これらはApache-2.0ライセンスで提供され、開発途上ですが、よりネイティブな体験を提供します。

全文翻訳

Python および TypeScript 用ネイティブ ArcadeDB ドライバー、HTTP および gRPC 対応

ブログに戻る

Roberto Franchini· 2026年9月9日 · 14分 ·

ドライバー Python TypeScript JavaScript gRPC HTTP OpenAPI マルチモデル グラフデータベース ArcadeDB

これまで、Python や Node.js から ArcadeDB と通信するには、REST API に対して手動で HTTP リクエストを記述するか、他のデータベース用に構築されたドライバーを流用し、そのデータベースのプロトコルが公開している ArcadeDB のサブセット内で作業するかのどちらかでした。どちらの方法も機能はしますが、ネイティブとは呼べないクライアントでした。

それが、arcadedb-drivers の登場により変わります。この新しいリポジトリには、公開済みの4つのクライアントが含まれています。Python 用の HTTP ドライバーと gRPC ドライバー、そして TypeScript/JavaScript 用の HTTP ドライバーと gRPC ドライバーです。これら4つすべてが Apache-2.0 ライセンスであり、ArcadeDB 自体が公開するコントラクトから生成されており、すべて公開レジストリで利用可能です。これらはまだリリースされたばかりの 0.1.0 版で、活発に開発が進められており、今後さらに多くの言語への対応が予定されています。

4つのパッケージ

パッケージ言語APIインストール
arcadedb-driverPythonHTTPpip install arcadedb-driver
arcadedb-driver-grpcPythongRPCpip install arcadedb-driver-grpc
@arcadedb/driverTypeScript/JSHTTPnpm install @arcadedb/driver
@arcadedb/driver-grpcTypeScript/JSgRPCnpm 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 ドライバーを使用する場合gRPC ドライバーを使用する場合
ブラウザコードを記述している場合サーバー間コードを記述している場合
依存関係を最小限にしたい場合大量の結果セットを読み込み、ストリーミングしたい場合
コールドスタートが懸念されるサーバーレス関数内大量挿入を行っており、長時間接続を維持したい場合
通常のリクエスト/レスポンス通信の場合スループットが重要で持続的な通信の場合
既存の HTTP インフラストラクチャ(プロキシ、ゲートウェイ、トレーシング)を再利用したい場合ネイティブな双方向ストリーミングが必要な場合

まずは HTTP から始めましょう。どこでも動作し、fetch や httpx 以外に何も必要としません。ほとんどのアプリケーション通信では、プロトコルがボトルネックになることはありません。スループットの数値で正当化できる場合に、ワークロードを gRPC に移行してください。

1つの制約については、時間を無駄にしないように明確に説明します。gRPC ドライバーにはブラウザ版がなく、サーバーが変更されない限り、今後も提供される予定はありません。 ArcadeDB の GrpcServerPlugin は、Netty 上で構築された HTTP/2 上のプレーンな grpc-java であり、gRPC-Web ハンドラー、Connect プロトコル、またはその前のサーブレットアダプターはありません。ブラウザは生の HTTP/2 gRPC フレーミングを話すことができないため、どの言語のクライアントライブラリもブラウザタブからこのサーバーに到達できません。ブラウザコードは HTTP ドライバーを使用してください。制限はサーバー側にあります。

接続とクエリ

HTTP ドライバーは開始点として適しています。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)

非同期ファサードは、同期ファサードとメソッドごとに1対1で対応しています:

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 クライアントを所有しており、それを解放する必要があるためです。本番環境でユーザーを驚かせる可能性のある詳細:timeout を省略すると、httpx のデフォルトの5秒にフォールバックするのではなく、タイムアウトが完全に無効になります。これは、httpx では明示的な timeout=None がその意味を持つためです。リクエストに制限を設けたい場合は、httpx.Timeout を渡してください。

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 はマルチモデルであるため、language パラメータはここで実際の役割を果たします。"sql"、"cypher"、"gremlin":同じ query 呼び出しでこれらすべてにアクセスでき、ドライバーはどの言語を選択したかに関係なく処理します。

結果エンベロープ、および truncated が重要な理由

HTTP ドライバーは、生の行の配列を返しません。どちらも完全なレスポンスエンベロープを返します:

interface QueryEnvelope<T> {
  result: T[];
  limit: number;
  returned: number;
  truncated: boolean;
}

ドライバーにおけるすべての API 設計の中で、これは無駄な儀式のように見える可能性が最も高いものです。その理由を説明します。truncated が true になるのは、サーバーのシリアライザーが行の上限に達し、クエリにまだ行が残っている場合です。その場合、result は部分的な回答であり、短いが完全な回答ではありません。これらは形状からは区別できません。早期に停止した5,000行の配列は、一致するレコードがなくなった5,000行の配列とまったく同じように見えます。エンベロープをアンラップして result のみ返すドライバーは、検証できない値を渡すことになります。

したがって、検証してください。truncated が true の場合、より狭いフィルターまたはより高い limit で再クエリを実行してください。ただし、limit を増やすことが常に解決策とは限りません。真のサイズがサーバーのハード上限(arcadedb.server.httpQueryMaxResultRows)を超える結果は、切り捨てられるのではなく、413 エラーで拒否されます。その時点を超えると、より狭いフィルターが唯一の解決策となります。

トランザクション

両方の HTTP ドライバーは、ArcadeDB のサーバーサイドトランザクションセッションを、それぞれの言語が既に持っているイディオムでラップします。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 はコールバックを使用します:

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 経由

gRPC ドライバーは、HTTP のリクエスト/レスポンス形状がコストとなるワークロードのために存在します。HTTP 経由での大量の結果セットは、繰り返し呼び出しによるページングを意味します。gRPC 経由では、それは1つのストリームです。

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="SELECT FROM Person WHERE age > 21", language="sql")
    )

target に注意してください。これは gRPC ネイティブの host:port 形式であり、URL ではありません。解析するスキームもなく、デフォルト値もないため、TLS の場合は credentials=grpc.ssl_channel_credentials() を渡すか、明示的に insecure=True と指定します。

raw は ArcadeDbService 全体の生成されたスタブであり、コントラクト内のすべての RPC がそこからアクセス可能です。その上に、各クライアントは、生成されたスタブだけではうまく扱えない RPC のために3つのラッパーを追加します:stream_query、insert_stream、および transaction。TypeScript でのクエリのストリーミング:

for await (const row of grpc.streamQuery({
  database: "mydb",
  query: "SELECT FROM Person",
  language: "sql",
})) {
  console.log(row.rid, row.properties);
}

streamQuery は、サーバーからの行バッチのストリームを1行ずつフラット化します。それ以外は何も行いません。3つのモードが呼び出し元が判断できる方法で異なるため、retrievalMode を自動的に選択しません。

  • CURSOR (デフォルト): クエリを一度実行し、イテレーション中に結果をストリーミングします。メモリ使用量を抑えたい大量の結果セットに適しています。
  • MATERIALIZE_ALL: サーバー全体の結果セットを最初にロードし、バッチで発行します。安定したスナップショットが必要で、サーバー側で保持できる場合に選択します。
  • PAGED: バッチごとに LIMIT/SKIP を使用してクエリを再発行します。各バッチの一貫性が他のバッチから独立していることを望む場合に選択します。

挿入のストリーミングは逆の方向で機能します。insert_stream に行バッチの非同期イテラブルを渡し、バッチにいくつの行を含めるかを自分で決定します。ドライバーは、手動で間違いやすいエンベロープのブックキーピングを所有します。ストリーム全体で1つの安定したセッション ID、chunk_seq は 1 から始まりインクリメントされ、database は最初のチャンクでのみ設定され、最後のチャンクには last: true が付きます。

gRPC ドライバーがコードで強制するセキュリティルールがあります。password_auth はパスワードをプレーンテキストの gRPC メタデータで送信するため、create_client は、トランスポート認証情報を持たないチャネルとペアリングすることを拒否します。ただし、insecure=True を渡して意図したことを明示した場合を除きます:

create_client("localhost:50051", auth=password_auth("root", "playwithdata"))
# raises InsecureChannelError

create_client("localhost:50051", auth=password_auth("root", "playwithdata"), credentials=grpc.ssl_channel_credentials())
# fine, the channel is encrypted

ベアラートークンはパスワードではなく、このガードに触れることはありません。

1つのコントラクト、多数のクライアント

1年後、4つのパッケージ自体の重要性は、それらのコードがどこから来るかよりも少なくなるでしょう。

リポジトリ内の contracts/ ディレクトリには2つのファイルがあります。HTTP クライアントすべてが生成される OpenAPI 仕様と、gRPC クライアントすべてが生成される Protobuf .proto ファイルです。どちらも ArcadeDB 自体から取得され、手動で保守されることはありません。クライアントパッケージは生成された型を編集しません。各クライアントのビルドはコントラクトから再生成され、ドリフトゲートで失敗します。チェックインされた生成コードと新しい再生成コードが一致しない場合、ビルドは失敗し、存在しなくなったサーバーを静かに記述するクライアントが出荷されることはありません。

これは、この設計が目指している障害モードです。手書きのドライバーは静かに陳腐化します。サーバーがフィールドを追加したり、レスポンスを厳密にしたりしても、ドライバーはコンパイルを続け、もっともらしい値を返し続けます。ドリフトゲートを備えた生成クライアントはそこまで到達できません。コントラクトが移動したその日に不一致が赤いビルドとなり、後で言語を追加することは、サーバーのソースコードを再読して型を手書きするのではなく、ジェネレーター構成を記述することを意味します。

同じ考え方から、さらに2つの小さなことが導かれます。すべてのリリースは、クリーンなチェックアウトから CI によってビルドおよび公開されます。個人のラップトップからではありません。npm パッケージはプロビナンスアテステーション(来歴証明)を運び、PyPI パッケージは長期的なトークンなしで、信頼された公開を通じて出荷されます。そして、何も自動的に公開されることはありません。すべてのリリースは、人間がトリガーしたワークフローディスパッチです。

今後の予定

これらのドライバーは活発に開発中です。これらは 0.1.0 リリースであり、上記の API は維持する予定ですが、1.0 になる前に一部変更される可能性があります。表面積を固定したい場合はバージョンをピン留めし、それを離れる前に変更履歴を読んでください。

さらに多くの言語が計画されています。リポジトリはすでにそれらのためにレイアウトされています。go/ やその他の言語ディレクトリは、typescript/ および python/ の兄弟として表示され、すべて同じ2つのコントラクトから生成されます。まだ存在しませんが、コントラクトファーストの設計により、追加が現実的になっています。

実際のワークロードで実行し、どこで障害になるかを教えてください。イシューとプルリクエストは ArcadeData/arcadedb-drivers に送ってください。