スキーマのライフサイクル管理

アプリケーションとそのデータ要件が変化すると、Kafka メッセージの構造も適応させる必要があります。これらの変更をスムーズに処理し、データの完全性を維持するには、効果的なスキーマ ライフサイクル管理が不可欠です。このプロセスでは、スキーマを変更するだけでなく、スキーマに依存するアプリケーションにとって安全または十分に互換性のある変更の種類を体系的に制御します。

Managed Service for Apache Kafka スキーマ レジストリは、スキーマ管理のライフサイクル全体をサポートしており、次の機能が含まれています。

  • 新しいスキーマ バージョンが導入されたときにスキーマの進化を管理するために、互換性ルール(互換性タイプ)を定義して適用します。これらのルールにより、プロデューサーとコンシューマーが引き続き正しく動作します。

  • 運用制御(スキーマモード)を構成して、さまざまなレベルでスキーマの変更可能性を管理し、データ処理パイプラインを保護します。

  • スキーマ参照を管理して、スキーマ全体で再利用性と整合性を高めます。

スキーマの進化の仕組み

  1. スキーマ定義を変更します。たとえば、.proto ファイルまたは .avsc ファイルに省略可能なフィールドを追加します。

  2. auto.register.schemas=true で構成されたプロデューサーが新しいスキーマを使用してメッセージを送信するか、API またはクライアント ライブラリを使用して新しいスキーマを明示的に登録しようとします。

  3. 新しいバージョンの登録リクエストがスキーマ レジストリに届くと、ターゲット サブジェクト用に構成された互換性ルールが取得されます。このルールに従って、提案された新しいスキーマを必要な以前のバージョンと比較します。

  4. スキーマ バージョンに互換性がある場合、新しいスキーマはサブジェクトの次のバージョンとして正常に登録され、新しいバージョン番号と、定義が一意の場合は新しい schema_id が割り当てられます。

  5. プロデューサーは(該当する場合)、メッセージに含める schema_id を受け取ります。

  6. スキーマ バージョンに互換性がない場合、登録試行は失敗し、エラーが返されます。

互換性タイプについて

スキーマの互換性により、スキーマ レジストリが異なるスキーマ バージョン間の互換性チェックをどのように処理するかを定義できます。これらの構成は、次のリソース パターン オプションで示されているように、スキーマ レジストリ階層内のさまざまなレベルで適用できます。

  • レジストリ レベル: スキーマ レジストリ全体のデフォルト構成を設定します。

    • パス: projects/project/locations/location/schemaRegistries/schema_registry/config
  • デフォルト コンテキスト内のサブジェクト レベル: レジストリのデフォルト コンテキスト内のサブジェクトに特定の構成を設定します。

    • パス: projects/project/locations/location/schemaRegistries/schema_registry/config/subject
  • 特定のコンテキスト内のサブジェクト レベル: 名前付きコンテキスト内のサブジェクトに特定の構成を設定します。

    • パス: projects/project/locations/location/schemaRegistries/schema_registry/contexts/context/config/subject

サブジェクト レベルで設定された構成は、レジストリ レベルで設定された構成をオーバーライドします。サブジェクト レベルで設定が指定されていない場合、レジストリ レベルから値が継承されます。レジストリレベルで明示的に設定されていない場合、デフォルトは Backward です。

次の使用可能なタイプは、スキーマ レジストリが新しいスキーマ バージョンを以前のバージョンと比較する方法を決定します。

  • None: 互換性チェックは実行されません。あらゆる変更が可能ですが、クライアントが破損するリスクが高くなります。

  • Backward(デフォルト): 新しいスキーマを使用するコンシューマー アプリケーションは、以前に登録されたスキーマのみで生成されたデータをデコードできます。これにより、オプションのフィールドを追加したり、フィールドを削除したりできます。コンシューマーはプロデューサーの前にアップグレードする必要があります。

  • Backward_transitive: 新しいスキーマを使用するコンシューマー アプリケーションは、そのサブジェクトの以前のスキーマ バージョンすべてで生成されたデータをデコードできます。この設定は Backward よりも厳格です。

  • Forward: 新しいスキーマを使用して生成されたデータは、以前に登録されたスキーマを使用するクライアントが読み取れる必要があります。プロデューサーを最初にアップグレードする必要がありますが、新しいスキーマを使用するコンシューマーは、さらに古いスキーマで生成されたデータを読み取れない可能性があります。この設定により、オプションのフィールドを削除したり、フィールドを追加したりできます。

  • Forward_transitive: 新しいスキーマを使用して生成されたデータは、すべての以前のスキーマ バージョンを使用して読み取り可能である必要があります。この設定は Forward よりも厳格です。

  • Full: 新しいスキーマは、以前に登録されたスキーマ バージョンと下位互換性があり、上位互換性もあります。クライアントは、新しいスキーマを使用して、プロデューサーに対して任意の順序でアップグレードできます。オプションのフィールドの追加または削除を許可します。

  • Full_transitive: 新しいスキーマは、そのサブジェクトの以前のすべてのスキーマ バージョンと下位互換性があり、上位互換性もあります。この設定は Full よりも厳格です。

互換性タイプの例

互換性タイプが Backward のスキーマ レジストリがあるとします。また、このレジストリ内に複数のサブジェクトを作成します。これらのサブジェクトは、レジストリの Backward 互換性を継承します。

user-events という名前の特定のサブジェクトには、より厳格な互換性ルールが必要です。user-events サブジェクトのスキーマ互換性レベルを Full に更新します。

この場合、次のルールが適用されます。

  • user-events サブジェクトに登録される新しいスキーマ バージョンは、そのサブジェクトの以前に登録されたスキーマ バージョンと後方互換性と前方互換性の両方を持つ必要があります。

  • スキーマ レジストリ内の他のサブジェクトは、互換性が明示的に構成されていない限り、レジストリ レベルの Backward 互換性設定に準拠します。

後でスキーマ レジストリの互換性レベルを Forward に変更すると、この変更はレジストリ内で作成された新しいサブジェクトのデフォルトの互換性に影響します。ただし、サブジェクト レベルの構成はレジストリ レベルの構成をオーバーライドするため、user-events サブジェクトは明示的に設定された Full 互換性を保持します。

これにより、レジストリ全体にデフォルトの互換性レベルを設定しながら、アプリケーションのニーズに基づいて個々のサブジェクトに特定の互換性要件を柔軟に定義する方法が示されます。

詳細については、更新の互換性タイプをご覧ください。

互換モードに関するベスト プラクティス

  • None を互換性タイプ戦略として使用しないでください。スキーマの変更によってクライアントが破損する可能性があります。

  • プロデューサーを最初に更新する場合は、ForwardForward-transitive などの転送ベースの戦略を選択します。コンシューマーを最初に更新する場合は、BackwardBackward-transitive などの後方互換性に基づく戦略を選択します。

  • 複数の以前のスキーマ バージョンとの互換性を維持する場合は、推移的戦略を選択します。互換性を最大化し、スキーマ バージョンの更新時にクライアントが破損するリスクを最小限に抑えるには、Full-transitive 戦略を使用します。

スキーマ リファレンスについて

スキーマ参照を使用すると、共通の構造を一度定義して、複数のスキーマから参照できます。たとえば、Address スキーマは Customer スキーマと Supplier スキーマの両方の一部として使用できます。

このアプローチにより、スキーマ全体で再利用性と一貫性が向上します。また、スキーマ参照を使用すると、どのスキーマが他のスキーマに依存しているかを明示的に追跡できるため、依存関係が明確になります。これにより、スキーマ アーキテクチャの保守性が向上します。

1 つのスキーマが別の共通スキーマを使用する必要がある場合、その共通スキーマへの参照が含まれます。この関係は、SchemaReference 構造体によって正式に定義されます。

SchemaReference には次のコンポーネントがあります。

  • name(文字列): Avro 形式で参照されるスキーマの完全修飾名、またはスキーマ定義自体で使用される Protobuf 形式のインポートされた型のファイル名。

  • subject(文字列): 参照スキーマがスキーマ レジストリに登録されているサブジェクトの名前。

  • version(int32): 参照されるスキーマの特定のバージョン番号。

他のスキーマを使用するスキーマは、references フィールドでこれらの依存関係を宣言します。このフィールドには、SchemaReference オブジェクトのリストが保持されます。

スキーマ リファレンスの例

Customer データと Supplier データの両方のスキーマを定義する必要があり、両方に住所を含める必要があるとします。スキーマ参照を使用すると、アドレス構造を一度定義して再利用できます。

この例に沿って操作するには、サブジェクトを作成するをご覧ください。

  1. address_schema という名前のサブジェクトを作成し、標準アドレスの定義を登録します。サブジェクトを初めて作成するときは、そのサブジェクトのスキーマのバージョン 1 も作成します。

    Avro

    これを作成して、サブジェクト address_schema_avro バージョン 1 として保存します。

    {
      "type": "record",
      "name": "Address",
      "namespace": "com.example.common",
      "fields": [
        {"name": "street", "type": "string"},
        {"name": "city", "type": "string"},
        {"name": "zipCode", "type": "string"},
        {"name": "country", "type": "string", "default": "USA"}
      ]
    }
    

    Protobuf

    これを作成して、サブジェクト address_schema_proto バージョン 1 として保存します。

    syntax = "proto3";
    
    package com.example.common;
    
    message Address {
      string street = 1;
      string city = 2;
      string zip_code = 3;
      string country = 4;
    }
    
  2. customer_schema スキーマを作成します。アドレス フィールドを繰り返す代わりに、address_schema スキーマを参照します。

    Avro

    billingAddress フィールドの型 com.example.common.Address は、前のステップで定義した Address スキーマを参照します。

    {
      "type": "record",
      "name": "Customer",
      "namespace": "com.example.crm",
      "fields": [
        {"name": "customerId", "type": "long"},
        {"name": "customerName", "type": "string"},
        // This field's type refers to the Address schema
        {"name": "billingAddress", "type": "com.example.common.Address"}
      ]
    }
    

    customer_schema_avro を登録する際、そのメタデータにはスキーマ参照が含まれます。

    // Conceptual metadata for customer_schema_avro
    "references": [
      {
        "name": "com.example.common.Address",
        "subject": "address_schema_avro",
        "version": 1
      }
    ]
    

    Protobuf

    customer.proto ファイルは address.proto をインポートし、billing_address フィールドに com.example.common.Address を使用します。

    syntax = "proto3";
    package com.example.crm;
    import "address.proto";
    
    message Customer {
      int64 customer_id = 1;
      string customer_name = 2;
      // This field's type refers to the imported Address message
      com.example.common.Address billing_address = 3;
    }
    

    customer_schema_proto を登録する際、そのメタデータにはスキーマ参照が含まれます。

    // Conceptual metadata for customer_schema_proto
    "references": [
      {
        "name": "address.proto",
        "subject": "address_schema_proto",
        "version": 1
      }
    ]
    
  3. 同様に、Supplier スキーマの場合は、同じ共通の Address スキーマを指すスキーマ リファレンスを追加します。

スキーマモードについて

スキーマモードは、スキーマ レジストリまたは特定のサブジェクトの運用状態を定義し、許可される変更のタイプを制御します。スキーマモードは、レジストリまたはスキーマ レジストリ内の特定のサブジェクトに適用できます。スキーマモードのリソースのパスは次のとおりです。

  • レジストリレベル モード: スキーマ レジストリ全体に適用されます。

    • パス: projects/project/locations/location/schemaRegistry/schema_registry/mode
  • レジストリレベルのサブジェクト モード: スキーマ レジストリ全体の特定のサブジェクトに適用されます。

    • パス: projects/project/locations/location/schemaRegistries/schema_registry/mode/subject

次のモードがサポートされています。

  • Readonly: このモードでは、スキーマ レジストリまたは指定されたサブジェクトを更新できません。構成の更新や新しいスキーマ バージョンの追加などの変更は禁止されます。

  • Readwrite: このモードでは、スキーマ レジストリまたは指定されたサブジェクトに対して、書き込みオペレーションが制限されます。構成の更新や新しいスキーマ バージョンの追加などの変更が可能になります。これは、新しいスキーマ レジストリと新しいサブジェクトの両方のデフォルト モードです。

特定のサブジェクトに対する変更が許可されるかどうかを判断する場合、サブジェクト レベルで設定されたモードは、スキーマ レジストリ レベルで設定されたモードよりも優先されます。

たとえば、スキーマ レジストリが Readonly モードで、その中の特定のサブジェクトが Readwrite モードの場合、その特定のサブジェクトの変更は許可されます。ただし、新しいサブジェクトの作成は、レジストリ レベルの Readonly モードによって制限されます。

スキーマモードの例

モードが Readwrite に設定されているスキーマ レジストリについて考えてみましょう。この構成では、新しいサブジェクトをレジストリに追加し、新しいスキーマ バージョンを既存のサブジェクトに追加できます。

誤って変更されないように保護する production-config という名前のサブジェクトがあるとします。production-config サブジェクトのモードを Readonly に設定します。その結果、production-config サブジェクトには次の条件が適用されます。

  • 新しいスキーマ バージョンをサブジェクトに追加することはできません。

  • サブジェクトの構成(互換性の種類など)は更新できません。

  • 独自のモードが明示的に設定されていないレジストリ内の他のサブジェクトは Readwrite モードのままなので、引き続き変更できます。

  • レジストリ自体は Readwrite モードのままであるため、レジストリにサブジェクトを作成し続けることができます。

後で、レジストリ レベルのモードを Readonly に設定して、スキーマ レジストリ全体をメンテナンス状態に移行することもできます。ただし、継続的なテストのために変更可能な状態を維持する必要がある別のサブジェクト staging-config があります。staging-config サブジェクトのモードを Readwrite に明示的に設定します。その結果、staging-config サブジェクトには次の条件が適用されます。

  • スキーマ レジストリは Readonly になりました。新しい科目は作成できません。

  • 特定のモードのオーバーライドがないなどの既存のサブジェクトのほとんども、継承によって Readonly になります。新しいスキーマ バージョンを追加したり、構成を更新したりすることはできません。

  • production-config サブジェクトは、明示的に設定された Readonly のままです。

  • staging-config サブジェクトのサブジェクト レベルの設定がレジストリ レベルの Readonly モードをオーバーライドするため、staging-config サブジェクトは Readwrite モードのままになります。引き続き、スキーマ バージョンを追加して staging-config の構成を更新できます。

この階層型アプローチにより、さまざまな粒度でスキーマ変更を柔軟に管理できます。

スキーマモードを更新する方法の詳細については、スキーマモードを更新するをご覧ください。

本番環境でスキーマ レジストリを使用するための推奨構成

本番環境でスキーマを保護するには、次の構成を適用します。

  • スキーマ レジストリ全体または特定のプロダクション トピックに mode=READONLY を設定して、新しいスキーマの登録を防ぎます。

  • Kafka クライアントが新しいスキーマ バージョンを作成できないように、create version 権限がないことを確認します。

  • Kafka クライアントのシリアライザーで、auto.register.schemas=false を設定します。Kafka Connect の場合は、必要に応じてキーと値のシリアライザーにこの設定を構成します。

    • key.serializer.auto.register.schemas=false
    • value.serializer.auto.register.schemas=false
  • (省略可)クライアントにサブジェクトの最新のスキーマを強制的に使用させるには、シリアライザーまたはデシリアライザーで use.latest.version=true を設定します。この設定により、クライアントは特定のメッセージと一致するバージョンではなく、サブジェクトに登録された最新のスキーマ バージョンを使用するようになります。

次のステップ

Apache Kafka® は、Apache Software Foundation または米国その他の諸国における関連会社の商標です。