10. 接続とケイパビリティ確認

最初のサンプル samples/00_connect.py は、PyIceberg から Apache Polaris の REST カタログに接続し、接続時に何が起きているかを確認します。REST カタログの接続は URI を指定して終わりではなく、GET /v1/config によるサーバ設定の受け取りと、クライアント設定との合成が挟まります。

ここではその合成結果と、サーバが宣言するケイパビリティ(対応エンドポイント一覧)を見ます。

確かめたいこと

  • GET /v1/config が返す defaults / overrides が、クライアント側の設定とどう合成されるか
  • 合成後のカタログ設定に、サーバ由来の値がどう現れるか
  • サーバが endpoints フィールドで何を宣言しているか
  • PyIceberg 側からその宣言を参照できるか

背景

Iceberg REST Catalog 仕様は、設定エンドポイントについて「All REST clients should first call this route」と定めています。クライアントはカタログに対する最初のリクエストとしてこれを呼び、返ってきた設定を自身の設定と合成してから以降の操作に入ります。

合成の優先順位は次のとおりです。後に来るものが勝ちます。

defaults  →  クライアント設定  →  overrides
 ↑弱い                            ↑最強

defaults はサーバの推奨値で、クライアントが同じキーを指定していればクライアント側が勝ちます。overrides はサーバが強制する値で、クライアントの指定を上書きします。Polaris の場合、マルチテナントのルーティングに使う prefix などが overrides に入ります。

もうひとつの要素が endpoints です。これはサーバが対応する REST 操作の一覧で、ケイパビリティの宣言にあたります。endpoints を返さないサーバは、仕様が定めるデフォルトセット(13 個)だけをサポートしていると解釈されます。このデフォルトセットにはビュー関連も scan planning も含まれません。PyIceberg 0.11.0 で ConfigResponse の supported endpoints ネゴシエーションが実装され、未対応の操作を呼んだときにエラーが明確になりました。

なお、Polaris への接続には PyIceberg の既定値のままでは通らない設定がいくつかあります。詳細は samples/_common.py のコメントにまとまっていますが、要点は次の 3 点です。

  • scope の既定値は catalog だが、Polaris では PRINCIPAL_ROLE:ALL が必要
  • warehouse は S3 パスではなくカタログ名を指定する
  • s3.region は S3 互換ストレージが相手でも実質必須(未指定だとリージョン解決に失敗するか極端に遅くなる)

処理の流れ

  1. load_catalog() でカタログに接続します。この時点で OAuth2 の client_credentials フローによるトークン取得と GET /v1/config の呼び出しが行われ、結果が catalog.properties にマージ済みで保持されます。

  2. マージ後の設定を列挙します。秘密情報はマスクします。

for k, v in sorted(catalog.properties.items()):
    if any(s in k.lower() for s in ("secret", "credential", "token", "password")):
        v = "***"
    print(f"    {k} = {v}")
  1. サーバが宣言したエンドポイントを、PyIceberg の内部属性から取得します。取得できない場合はその旨を表示します。
endpoints = getattr(catalog, "_endpoints", None)
  1. namespace lab を作成(既存なら何もしない)し、一覧を表示します。

実測結果

接続と設定のマージ結果は次のようになりました。

--- カタログに接続します
    接続先: http://localhost:8181/api/catalog
    warehouse: lab_catalog
    型: RestCatalog

--- マージ後のカタログ設定 (GET /v1/config の結果が反映済み)
    credential = ***
    default-base-location = s3://warehouse/lab_catalog
    namespace-separator = %1F
    prefix = lab_catalog
    s3.access-key-id = rustfsadmin
    s3.endpoint = http://localhost:9000
    s3.region = us-east-1
    s3.secret-access-key = ***
    scope = PRINCIPAL_ROLE:ALL
    type = rest
    uri = http://localhost:8181/api/catalog
    warehouse = lab_catalog

このうち default-base-location はサーバの defaultsnamespace-separatorprefix はサーバの overrides に由来します。クライアント側は一切指定していません。生のレスポンスは samples/06_rest_api_raw.py で確認でき、次の内容でした。

{
  "defaults": {
    "default-base-location": "s3://warehouse/lab_catalog"
  },
  "overrides": {
    "namespace-separator": "%1F",
    "prefix": "lab_catalog"
  },
  "endpoints": [ ... ]
}

endpoints には 36 個のエントリが並びます。仕様のデフォルト 13 個に加えて、ビュー関連(/views/views/renameregister-view)と、Polaris 独自の generic-tables・policies 系エンドポイントが宣言されていました。前者は Iceberg 仕様の範囲、後者は polaris/v1/... というパスを持つ拡張です。

一方、PyIceberg 側からの参照は取得できませんでした。

--- サーバが宣言したエンドポイント (ケイパビリティ)
    (PyIceberg の内部表現から取得できませんでした)
    生のレスポンスは 06_rest_api_raw.py で確認できます。

_endpoints という属性名は PyIceberg の内部実装に依存しており、0.11.1 の RestCatalog では同名の属性を参照できませんでした。ネゴシエーション自体が行われていないのか、別の名前で保持されているのかは未確認です。サーバの宣言内容そのものは前述のとおり生のレスポンスで確認できます。

namespace の作成は成功しました。

--- namespace を作成/確認します
    namespace 一覧: [('lab',)]

ここから分かること

REST カタログでは、クライアントの設定ファイルに書いた内容がそのまま使われるとは限りません。サーバが overrides で上書きする項目があり、実際に有効な設定は接続後に初めて確定します。設定の食い違いを調べるときは、クライアント側の記述ではなく合成後の値(catalog.properties)を見る必要があります。

endpoints の宣言は、カタログ実装の差異をクライアントが事前に把握するための仕組みです。宣言されていない操作は、実行してから 404 や 405 に遭遇するのではなく、クライアント側で早期に判断できます。ただし PyIceberg 0.11.1 の公開 API からこの一覧を読む手段はこのサンプルでは見つかっておらず、確認したい場合は GET /v1/config を直接叩くのが確実です。

また、Polaris のように仕様の範囲外のエンドポイントを同じ endpoints 配列で宣言する実装があります。パスに polaris/v1/ が含まれるものがそれにあたり、他のカタログ実装に移す際は使えません。ポータビリティを気にする場合は、宣言されているからといって使ってよいとは限らない点に注意が必要です。

次は 11. 基本的な CRUD と既定値 です。