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

アプリケーションとそのデータ要件が変化すると、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 スキーマの両方の一部として使用できます。

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

あるスキーマが別の共通スキーマを使用する必要がある場合は、その共通スキーマへの参照が含まれます。この関係は、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 モードをオーバーライドするため、Readwrite モードのままです。staging-config のスキーマ バージョンを追加して構成を更新できます。

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

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

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

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

  • スキーマ レジストリ全体または特定の本番環境トピックに mode=READONLY を設定して、新しいスキーマの登録を防ぎます。

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

  • 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 または米国その他の諸国における関連会社の商標です。