スキーマレジストリ(Schema Registry)とスキーマ進化
1. 概要
スキーマレジストリ(Schema Registry) とは、イベントストリームやメッセージのデータ構造(スキーマ)を中央で登録・バージョン管理し、プロデューサー(Producer)とコンシューマー(Consumer)がやり取りするメッセージが合意されたスキーマに従うかを検証し、スキーマ変更時に 互換性(compatibility) 規則に従って安全な進化を強制するメタデータ管理システムである。
Apache Kafka のようなイベント駆動アーキテクチャ(EDA)が広がるにつれ、一つのトピックを数十のサービスが同時に生産・消費する構造が一般的になった。しかしメッセージ本文はバイト配列にすぎないため、プロデューサーがフィールドを追加したり型を変えたりすると、それを知らないコンシューマーは逆直列化(deserialization)に失敗するか、静かに誤った値を読む。このように異なる時点でデプロイされたサービスが暗黙に同じデータ構造を仮定していて食い違う現象を スキーマドリフト(schema drift) といい、分散システムの代表的な運用障害の原因である。
従来の方式は、プロデューサーとコンシューマーが共通ライブラリにスキーマをハードコードして共有するものであった。しかしこれは二つの根本的問題を生む。第一に、スキーマを変えるたびにすべてのサービスを 同時に再デプロイ しなければならず、疎結合(loose coupling)という EDA の本質が壊れる。第二に、メッセージごとにスキーマ全体を一緒に送るとペイロードが肥大化する。スキーマレジストリはスキーマを 一度登録し整数 ID で参照 させることで、この二つの問題を同時に解決する。メッセージにはスキーマ ID だけが約 5 バイト乗り、実際の構造定義はレジストリから取得する。
スキーマレジストリの核心的価値は デプロイ順序の独立性 である。プロデューサーとコンシューマーが異なる時点で、互いを知らぬまま デプロイされてもメッセージ互換性が壊れないよう、「変更可能なもの」と「変更不可能なもの」を事前に規約として固定する。これは単なるバリデーターを超え、データを一つの 契約(contract) として扱うガバナンスツールへと発展している。
まとめると、スキーマレジストリは次の四つの特徴を持つ。
- 中央集中の登録・バージョン管理: スキーマをサブジェクト単位で登録し、バージョン・グローバル ID を付与する。
- 互換性の強制: 登録時点でポリシーを検査し、壊れる変更を事前に遮断する。
- 参照の効率: メッセージにはスキーマ ID だけを乗せペイロードを最小化する。
- 疎結合: プロデューサー・コンシューマーの独立デプロイを可能にする。
この問題意識は、同期式 REST API における API バージョニングと本質的に同じである。REST では /v1、/v2 のように URI でバージョンを明示し、クライアントが望むバージョンを選ぶが、非同期イベントストリームではコンシューマーがメッセージを「選択」できず、トピックに積まれたすべての過去・現在のメッセージを順に受け取らねばならない。したがってイベント世界のバージョン管理は「クライアントが選ぶ」方式ではなく「どのバージョンでも壊れずに読める」互換性保証で解かねばならず、まさにこの役割をスキーマレジストリが担う。すなわちスキーマレジストリは イベント駆動アーキテクチャのための API 契約管理層 として理解するのが正確である。
2. スキーマレジストリの構造と動作
スキーマレジストリは、プロデューサー・コンシューマー・直列化器(SerDes)・ストアで構成される照会型サービスである。プロデューサーはメッセージを送る前にスキーマを登録して ID を受け取り、メッセージの先頭に付ける。コンシューマーはその ID でスキーマを逆照会して安全に解釈する。下の構造図は全構成要素の関係を示す。
graph LR
subgraph P["プロデューサー側"]
PA["アプリケーション"] --> PS["直列化器(SerDes)"]
end
subgraph R["スキーマレジストリ"]
API["REST API"] --> STORE["スキーマストア(Kafkaトピック/DB)"]
API --> CHK["互換性検査器"]
end
subgraph C["コンシューマー側"]
CS["逆直列化器(SerDes)"] --> CA["アプリケーション"]
end
PS -->|"スキーマ登録/照会"| API
PS -->|"ID+ペイロード"| BROKER["メッセージブローカー(Kafka)"]
BROKER -->|"ID+ペイロード"| CS
CS -->|"IDでスキーマ照会"| API
直列化器(SerDes) はレジストリと通信するクライアントライブラリで、最も重要な要素である。プロデューサー側の直列化器は送信直前にオブジェクトのスキーマをレジストリに登録(またはキャッシュから ID を確認)し、メッセージの先頭に「マジックバイト + 4 バイトのスキーマ ID」を付けて本文を二進符号化する。コンシューマー側はその ID を外してレジストリからスキーマを取得し(一度取得すればローカルキャッシュ)逆直列化する。おかげでネットワークにはスキーマ ID だけが行き交い、レジストリ照会はキャッシュミス時のみ発生するため、スループットにほとんど影響しない。
スキーマストア は登録されたスキーマとバージョン、サブジェクト(subject)メタデータを保管する。Confluent の実装は別途 DB ではなく _schemas という Kafka 内部トピックをログとして用い、レジストリインスタンスがこれを再生(replay)して同一状態を復元する。これによりレジストリ自体が Kafka の複製・耐久性をそのまま受け継ぐ。サブジェクト はスキーマバージョンの名前空間で、通常 <トピック名>-value、<トピック名>-key の形を使う(TopicNameStrategy)。一つのサブジェクトが v1、v2、v3 のように複数のバージョンを持ち、各バージョンはグローバル一意の ID を付与される。
互換性検査器 は新しいスキーマを登録しようとするとき既存バージョンと比較し、設定された互換性ポリシーに違反すれば登録を拒否する。例えばあるコンシューマーが必須で読むフィールドをプロデューサーが削除しようとすると、レジストリは登録段階でこれを阻止し、障害を デプロイ前に 遮断する。この「登録時点ゲート」がスキーマレジストリを単なるストアではなく ガバナンス統制点 にする。実際、金融の実時間取引パイプラインでは CI パイプラインに互換性検査を含め、互換性のないスキーマ変更 PR をビルド段階で失敗させる方式(いわゆる schema-as-code)を使う。
メッセージ送信時のバイトレイアウトを具体的に見ると、その効率を実感できる。Confluent 形式のメッセージは「マジックバイト(0x00)1 バイト + スキーマ ID 4 バイト + 実際のペイロード」で構成される。すなわちスキーマ定義全体(数百バイトから数 KB に及ぶ JSON/Avro スキーマ)を毎メッセージごとに乗せる代わりに、5 バイトの接頭辞 だけでどの構造かを識別する。毎秒数十万件を流すトピックで、この差はネットワーク帯域と保存コストを数十倍節約する。コンシューマーは初めて見る ID のときだけレジストリを 1 回照会し以後はローカルキャッシュを使うため、レジストリはトラフィック経路ではなく 制御平面(control plane) にのみ位置する。この設計が「中央集中ガバナンス」と「分散高スループット」を同時に達成する核心である。
3. スキーマ進化と互換性ポリシー
スキーマ進化の核心的な問いは「スキーマが変わるとき 誰が先にデプロイされれば安全か」である。互換性ポリシーはこのデプロイ順序を規定する。下の状態フローは互換性検査の過程を示す。
flowchart TD
A["新スキーマ登録要求"] --> B{"互換性モードを確認"}
B -->|"NONE"| G["検査を省略して登録"]
B -->|"BACKWARD"| C["新スキーマで旧バージョンのデータを読めるか?"]
B -->|"FORWARD"| D["旧スキーマで新バージョンのデータを読めるか?"]
B -->|"FULL"| E["双方向とも可能か?"]
C -->|"可能"| G
C -->|"不可"| F["登録拒否(409)"]
D -->|"可能"| G
D -->|"不可"| F
E -->|"可能"| G
E -->|"不可"| F
G --> H["新バージョン番号とグローバルIDを付与"]
Confluent Schema Registry は 七つの互換性モード(BACKWARD、BACKWARD_TRANSITIVE、FORWARD、FORWARD_TRANSITIVE、FULL、FULL_TRANSITIVE、NONE)を提供する。既定値は BACKWARD である。_TRANSITIVE 接尾辞が付くと検査対象が「直前のバージョン一つ」から「これまで登録されたすべてのバージョン」へ拡張され、過去のどのバージョンで書かれたデータでも安全に扱える。
| モード | 許容される変更(代表) | 検査基準 | アップグレード順序 |
|---|---|---|---|
| BACKWARD(既定) | フィールド削除、既定値ありフィールド追加 | 直前バージョン | コンシューマー先 |
| BACKWARD_TRANSITIVE | 上と同じ | 全バージョン | コンシューマー先 |
| FORWARD | フィールド追加、既定値ありフィールド削除 | 直前バージョン | プロデューサー先 |
| FORWARD_TRANSITIVE | 上と同じ | 全バージョン | プロデューサー先 |
| FULL | 既定値ありフィールドの追加・削除 | 直前バージョン | 任意順序 |
| FULL_TRANSITIVE | 上と同じ | 全バージョン | 任意順序 |
| NONE | すべての変更 | 検査なし | 保証なし |
後方互換(BACKWARD) は「新スキーマで 旧データ を読めねばならない」という規則である。したがってコンシューマーを先に新バージョンへ上げた後、プロデューサーを上げる。フィールドを削除するか既定値のあるフィールドを追加する変更が許容される。Confluent がこれを既定値とする理由は、トピックを最初まで巻き戻して(rewind)再処理するときも最新コンシューマーが過去メッセージをすべて読めるからである。ログ再処理・障害復旧が頻繁な実務で最も安全な選択である。
前方互換(FORWARD) は逆に「旧スキーマで 新データ を読めねばならない」という規則で、プロデューサーを先に上げる。まだ旧バージョンのコンシューマーが新バージョンのプロデューサーが作ったメッセージを壊さず読まねばならない状況、例えば外部パートナーがコンシューマーを統制し即時アップグレードできない B2B 連携に適する。完全互換(FULL) は双方向をともに要求してデプロイ順序自体を自由にするが、許容される変更幅が「既定値のあるフィールドの追加・削除」に狭まる。統制が最も難しい大規模組織で安全を最大化するときに使う。
互換性検査を通れない変更、すなわち 壊れる変更(breaking change) がどうしても必要なときもある。このときは同じサブジェクトを無理に進化させる代わりに新トピック(例:orders.v2)を作って新旧ストリームを並行運用し、コンシューマーを漸進的に移行させる 二重書き込み(dual-write)・拡張後収縮(expand-and-contract) パターンを使う。プロデューサーがしばらく両方のトピックに書き、すべてのコンシューマーが新トピックへ移った後に旧トピックを廃棄する形である。スキーマレジストリはこの移行期間中も各トピックの互換性を独立に保証し、大規模な移行を無停止で遂行できるようにする。
実務的含意は明快である。互換性モードの選択は技術的選好ではなく 組織のデプロイガバナンス決定 である。プロデューサー・コンシューマーを同じチームが統制するなら BACKWARD で十分だが、コンシューマーが複数組織に散らばり統制不能なら FULL_TRANSITIVE へ上げて「壊れない」ことを強く保証せねばならない。一方 NONE は検査を切るので開発初期以外は厳禁であり、運用系で NONE を乱用すればレジストリは単に「高価なバージョンストア」に転落する。
4. 直列化フォーマット比較(Avro・Protobuf・JSON Schema)
スキーマレジストリは特定の直列化フォーマットに依存しないが、各フォーマットはスキーマ進化の扱い方が異なる。Confluent 5.5 以降は三つのフォーマットをすべて支援し、フォーマットごとに「何が互換変更か」の規則が異なる。例えば FORWARD モードで Protobuf は新しいメッセージ型の追加が禁止されるなど、フォーマット別の制約が存在する。
| 区分 | Apache Avro | Protobuf | JSON Schema |
|---|---|---|---|
| 符号化 | 二進(スキーマ分離) | 二進(タグ番号) | テキスト(JSON) |
| 進化の識別 | フィールド名 + 既定値 | フィールド 番号 | プロパティ名 |
| サイズ/速度 | 小さく速い | 小さく速い | 大きく遅い |
| 可読性 | 低い | 低い | 高い |
| 適合領域 | データパイプライン・分析 | gRPC・高性能 RPC | Web/REST 親和 |
Avro はデータがスキーマと分離して送信され、逆直列化時に「書き込みスキーマ(writer)」と「読み込みスキーマ(reader)」をともに用いてフィールドを名前でマッピングする。この構造のおかげでスキーマ進化規則が明確で、ビッグデータ保存(Hadoop・データレイク)の事実上の標準として定着した。ただし既定値(default)設計が進化の核心であり、追加フィールドに既定値を抜かすと後方互換が壊れる。
例えば Avro で注文イベントに coupon_code という選択フィールドを追加するとしよう。後方互換(BACKWARD)を守るには、このフィールドに必ず既定値(例:"default": "")を指定せねばならない。そうしてこそ新バージョンのコンシューマーがこのフィールドのない過去メッセージを読むとき既定値で埋めて逆直列化に成功する。もし既定値を抜かすと、レジストリは登録自体を拒否(HTTP 409)し、運用にデプロイされる前に設計ミスを露わにする。このように「既定値設計」は Avro 進化の最も多い失敗点であり互換性の核心装置である。
Protobuf はフィールドを名前ではなく 整数タグ番号 で識別する。したがって番号さえ維持すれば名前を変えても互換であり、gRPC と結合して低遅延のサービス間通信に強い。ただし一度使ったタグ番号を再利用するとデータが静かに汚染されるため、削除した番号を reserved で永久に封印する規律が必須である。JSON Schema はテキストベースで人が読みやすく Web 生態系と親和的だが、ペイロードが大きく解析が遅いため超高スループットのストリーミングには不適である。実務では「大量分析パイプラインは Avro、内部マイクロサービス RPC は Protobuf、外部公開 API イベントは JSON Schema」のように領域別に混用するのが一般的である。
5. 実装事例の比較
代表的な実装には Confluent Schema Registry(Kafka 生態系の事実上の標準)、AWS Glue Schema Registry(マネージド、Kinesis・MSK 連携)、オープンソースの Apicurio Registry(Avro・Protobuf・JSON Schema・OpenAPI・AsyncAPI まで幅広く支援)がある。Confluent は _schemas トピックを基盤に Kafka に深く統合され運用成熟度が高く、Glue はサーバーレスで運用負担が少なく IAM と自然に結合する。Apicurio はベンダー依存を避けたい組織や API 仕様まで一緒に管理したい場合に適する。
規模の例を挙げると、Netflix・LinkedIn 級の組織は一日数兆件のイベントを処理し数千のサブジェクトを運用する。この規模ではスキーマ ID キャッシング、レジストリ多重化、そしてスキーマ変更をコードレビューのように扱う承認ワークフローがなければ、些細なフィールド変更一つが全社パイプラインを止めうる。実際あるフィンテック事例では、決済イベントスキーマに互換性検査を CI に統合した後、スキーマ関連の運用障害が四半期あたり数件からほぼゼロに減少したと報告される(一般的傾向として理解する)。
選択基準を整理すると次のとおりである。三つの実装は互換性モデルという本質を共有するが、運用責任の所在と支援範囲で分かれる。
| 区分 | Confluent | AWS Glue | Apicurio |
|---|---|---|---|
| 保存方式 | Kafka _schemas トピック |
マネージド(サーバーレス) | SQL DB/Kafka など選択 |
| 運用負担 | 自前運用(セルフホスト) | ほぼなし(完全マネージド) | 自前運用 |
| 支援仕様 | Avro・Protobuf・JSON Schema | Avro・Protobuf・JSON | 上 + OpenAPI・AsyncAPI |
| 適合状況 | Kafka 中心・高成熟運用 | AWS 依存・運用簡素化 | マルチ仕様・ベンダー中立 |
サブジェクトのネーミング戦略も実務で重要な設計決定である。既定の TopicNameStrategy(<トピック>-value)は一つのトピックに一種類のイベントだけが載るときは単純だが、一つのトピックに複数のイベント型を混ぜて順序を保存せねばならない場合は RecordNameStrategy(レコード型名基準)や TopicRecordNameStrategy(トピック+レコード名)に変えねばならない。戦略を誤ると異なるイベントが同じサブジェクトを共有し互換性検査が見当違いに作動するため、トピック設計の初期に決めておかねばならない。
6. 深化:データ契約(Data Contract)への進化
近年スキーマレジストリは データ契約(Data Contract) と データメッシュ(Data Mesh) の議論の中心技術として再評価されている。データ契約は「データを生産するチームと消費するチームの間の明示的・バージョン管理された約束」で、スキーマ(構造)だけでなく意味(semantics)、品質期待値(SLO)、所有権まで含む。スキーマレジストリはこの契約の 機械が強制できる構造部分 を担う。データメッシュで各ドメインがデータを「製品(data product)」として公開するとき、互換性ポリシーはすなわち製品の API 安定性等級 に等しい。
もう一つの成熟の兆候は スキーマ参照(schema references) である。共通型(例:Address、Money)を別途スキーマとして定義し、複数のイベントスキーマがこれを参照するようにして、組織全体のデータ定義を再利用・標準化する。これはマイクロサービス間の「共有カーネル(shared kernel)」をコードライブラリではなくレジストリで宣言的に管理するに等しく、重複定義による意味の不一致を減らす。ただし参照が深まると変更の影響範囲が広がるため、共通スキーマにはより厳格な互換性ポリシー(FULL_TRANSITIVE)と変更承認手続きを適用する階層的ガバナンスが必要である。
Confluent はこれに対応して Data Contracts 機能でスキーマにメタデータ・タグ(例:PII 表示)、規則(rule)ベースの検証・変換、移行規則を結合する方向へ拡張している。例えば個人情報(PII)フィールドをスキーマタグで表示すると、下流パイプラインで自動的にマスキング・暗号化規則を適用するよう接続できる。これはスキーマレジストリが単なる直列化補助ツールから データガバナンス・個人情報保護の実行地点 へ昇格していることを示す。今後はスキーマレジストリがデータカタログ・リニージ(系統)システムと統合され、スキーマ変更が下流に及ぼす影響を自動分析(impact analysis)する方向へ発展する見通しである。
7. 考慮事項および示唆点
- 互換性ポリシーは組織設計の問題である。 技術士の観点で BACKWARD/FORWARD/FULL の選択は性能ではなく「プロデューサーとコンシューマーを誰が統制するか」という組織構造から逆算せねばならない。内部単一チームは BACKWARD、多組織・外部連携は FULL_TRANSITIVE が基本戦略であり、ポリシーをサブジェクト別に差等適用する設計が望ましい。
- スキーマをコードのように管理せよ(schema-as-code)。 スキーマ変更を Git PR と CI 互換性検査で扱い、デプロイ前に 壊れを遮断せねばならない。運用系で NONE モードを許す瞬間、レジストリのガバナンス価値が消えるため、NONE は組織ポリシーで禁止し例外は承認手続きを置く。
- フォーマット選択のトレードオフを明確にせよ。 Avro(分析・パイプライン)、Protobuf(低遅延 RPC)、JSON Schema(外部公開 API)はそれぞれ進化規則と性能特性が異なる。Protobuf のタグ番号再利用禁止、Avro の既定値設計のようなフォーマット別の落とし穴をガイドラインとして文書化してこそ運用事故を予防する。
- 単一障害点(SPOF)と可用性。 直列化器がレジストリに依存するため、レジストリ障害が全体の生産・消費を止めうる。多重化・ローカルスキーマキャッシュ・読み取り専用フォールバックを設計し、レジストリの可用性 SLO をメッセージブローカー水準に合わせねばならない。
- ガバナンス・個人情報連携の展望。 スキーマタグベースの PII 識別、データ契約、リニージ・カタログ統合はスキーマレジストリをデータガバナンスの実行地点へ拡張する。今後データメッシュ環境で各ドメインのデータ製品の品質・互換性・個人情報統制を自動化する核心軸として位置づくであろう。
参考資料
- Confluent, "Schema Evolution and Compatibility", https://docs.confluent.io/platform/current/schema-registry/fundamentals/schema-evolution.html
- Confluent, "Schema Registry API Reference", https://docs.confluent.io/platform/current/schema-registry/develop/api.html
- AWS, "AWS Glue Schema Registry", https://docs.aws.amazon.com/glue/latest/dg/schema-registry.html
- Apicurio Registry, https://www.apicur.io/registry/
一言まとめ: スキーマレジストリはイベントストリームのデータ構造を中央でバージョン管理し互換性ポリシー(BACKWARD・FORWARD・FULL)で安全なスキーマ進化を強制することで、プロデューサー・コンシューマーを疎結合にしデータ契約・ガバナンスの実行地点へと発展する技術である。