Kafka で Avro を使うなら、互換性モードとデフォルト値の扱いを外すと痛い目にあいます。この記事は、フィールド追加・削除・型変更・エイリアス(リネーム)を、Schema Registry の互換性と結びつけて整理します。
CCDAK では、Backwards/Forwards/Full と Transitive の違い、デフォルト値の必須性、null を含む union の並び順が頻出です。現場で壊さない設計と、試験で落とさない要点をセットで確認しましょう。
Avro の互換性は「ライタースキーマ(writer)のデータを、リーダースキーマ(reader)で読む」際の解決ルールに基づきます。Confluent Schema Registry は新しいスキーマを登録するたびに、選択した互換性モードと既存バージョンを突き合わせて検証します。
一般運用では Backward 互換を採用するケースが多く、先にコンシューマを更新してからプロデューサを更新する流れに合致します。一方、先にプロデューサ更新をしたい場合は Forward、双方向の安全性が必要なら Full を選びます。Transitive は直前だけでなく全バージョンに対して検証する強化オプションです。
| 互換性モード | 既存コンシューマへの影響 | 既存プロデューサへの影響 | 代表的に許可される変更例 |
|---|---|---|---|
| BACKWARD | 新スキーマのコンシューマは旧データを読める | 既存プロデューサは継続稼働可 | フィールド追加(デフォルト必須)、型の昇格(int→long など)、フィールドのリネーム(エイリアス) |
| FORWARD | 旧コンシューマが新データを読める | 新プロデューサに制約が強い | フィールド削除(ただし旧読者側にデフォルトが必要)、将来互換を壊さない限定的変更 |
| FULL | 旧⇔新どちら向きも読める | 新旧いずれの変更も厳格に制約 | 追加はデフォルト必須、型変更は昇格のみ、名前変更はエイリアス前提 |
| NONE | 検証なし。破壊的変更も通る | 短期実験以外非推奨 | — |
| TRANSITIVE 拡張 | 全過去版に対して保証 | リリース列のどこかで破綻しないことを担保 | BACKWARD_TRANSITIVE / FORWARD_TRANSITIVE / FULL_TRANSITIVE として利用 |
Schema Registry を介した Avro 読み書きの流れ
最も多い変更が「フィールドの追加」です。Backward 互換を確保するには、追加フィールドにデフォルト値を必ず設定します。旧データにはそのフィールドが存在しないため、リーダー側でデフォルトが使われます。
optional を表すなら union に null を含め、かつ union の先頭に null を置き、default も null にします。Avro の仕様上、union の default は配列の先頭型と一致していなければなりません。
複合型のデフォルト値は制約が多く、特にレコードや配列のデフォルトは空集合か仕様に沿った完全値が必要です。可能なら nullable にして null をデフォルトにする方が保守しやすいです。
フィールド削除は Forward 互換の観点で危険です。新ライターがそのフィールドを書かなくなると、旧リーダーはリーダースキーマに default がない限り解決に失敗します。現実には旧リーダーのスキーマを後から直せないため、ストレートな削除は避けます。
安全策は段階的な非推奨化です。まずフィールドを nullable + default null に変更し、コンシューマを全て更新してから、将来のメジャー更新で削除を検討します。必須化も同様に、一度 nullable に戻れない設計は破壊的になります。
型変更は昇格(promotion)のみが安全です。代表例は int→long→float→double の昇格です。string と bytes の相互昇格は Avro の自動昇格では扱われないため、union で併存させる設計を検討します。
名前変更は aliases を使います。フィールドやレコード名を変更しても、aliases に旧名を列挙しておけば、解決時に旧名フィールドが新名にマッピングされます。これにより Backward 互換を保ったリネームが可能です。
enum は新しいシンボルの追加は Backward 互換(新リーダーは旧データを読める)ですが、Forward 互換では危険です(旧リーダーが未知のシンボルを解釈できない)。
典型的な進化例として、フィールド追加(デフォルト付き)、型昇格、フィールドのリネーム(aliases)を組み合わせます。互換性は Backward に設定し、先にコンシューマを更新してからプロデューサを更新します。
以下は v1→v2 への進化例です。v2 は Backward 互換を満たすため、追加フィールドは nullable + default null、数値は型昇格、リネームには aliases を使用しています。
Avro スキーマ進化例と互換性設定
# v1: 初期スキーマ
{
"type": "record",
"name": "Order",
"namespace": "nl.shop",
"fields": [
{"name": "id", "type": "string"},
{"name": "amount", "type": "int"},
{"name": "status", "type": {"type": "enum", "name": "OrderStatus", "symbols": ["NEW", "PAID"]}}
]
}
# v2: 追加・昇格・リネーム (Backward 互換)
{
"type": "record",
"name": "Order",
"namespace": "nl.shop",
"fields": [
{"name": "order_id", "type": "string", "aliases": ["id"]},
{"name": "amount", "type": "long"},
{"name": "status", "type": {"type": "enum", "name": "OrderStatus", "symbols": ["NEW", "PAID", "CANCELLED"]}},
{"name": "note", "type": ["null", "string"], "default": null}
]
}
# ポイント
# - amount: int->long は型昇格
# - order_id: フィールド名の変更だが、aliases で旧名 id を吸収
# - note: ["null", "string"] の順で default は null
# - enum 追加(CANCELLED): Backward は OK。Forward(旧リーダー)は新シンボルを解せない点に注意
# Schema Registry: subject の互換性を Backward に設定 (例: <topic>-value)
# API パスは実環境に合わせる
curl -X PUT \
-H "Content-Type: application/json" \
--data '{"compatibility": "BACKWARD"}' \
http://schema-registry:8081/config/my-orders-value
Confluent の Avro シリアライザ/デシリアライザは、メッセージにスキーマ ID を埋め込み、Registry と連携して writer schema を取得します。SpecificRecord を使う場合は reader schema が生成クラスから与えられ、GenericRecord を使う場合はデシリアライザが writer schema のまま解釈します。
本番では auto.register.schemas を無効にして、スキーマ登録とレビューを別パイプラインで管理するのが安全です。subject の命名戦略は topic 単位(TopicNameStrategy)が一般的ですが、レコード再利用が多い場合は RecordNameStrategy も検討します。
試験では言葉の定義とセットの暗記で点が伸びます。設問は具体的な変更提案に対して、どの互換性モードで可か不可か、どんなデフォルトが必要かを問う形式が多いです。
CCDAK
問題 1
Value 側 subject が BACKWARD 互換に設定されている。既存の Order レコードに新しい任意フィールド note を追加したい。最も安全な変更はどれか。
正解: B
Backward 互換でフィールド追加を行う場合、追加フィールドには default が必須。optional にするなら union の先頭を null にし、default も null にする必要がある。よって ["null", "string"] かつ default=null が正解。
union における default の型はどの要素に合わせるべきですか?
Avro 仕様では union の default は配列の先頭型と一致していなければなりません。optional フィールドは ["null", "type"] の順にし、default は null にします。
型変更はどこまで安全ですか?
安全なのは昇格のみです。代表的には int→long、long→double、float→double。string↔bytes、boolean→int のような変更は不可。decimal の精度縮小など論理型の後退も避けてください。
enum のシンボル追加・削除は互換性にどう影響しますか?
シンボルの追加は Backward 互換(新リーダーは旧データを読める)ですが、Forward 互換は満たしません(旧リーダーは新シンボルを解釈できない)。削除はその逆方向で危険度が高く、基本的に避けます。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
Kafka Topic と Partition の基礎: 分散とスケーラビリティの要
CCDAK 対策と実務の両立を意識し、Topic/Partition/Replica/Consumer Group の役...
CCDAK 試験ガイド:出題範囲・配点・申込み・対策
Confluent Certified Developer for Apache Kafka (CCDAK) の出題範囲...
Confluent Certified Administrator (CCAAK) 対策: 出題範囲・配点の考え方・運用観点の要点
CCAAKに向けて、試験領域の押さえどころを運用目線で整理。プロダクションで通用する設定・監視・セキュリティの実践知を、...
Kafka の Replica と In-Sync Replicas を正しく設計する: 耐障害性と一貫性
レプリカとISRの仕組みを起点に、acks と min.insync.replicas、クリーン/アンクリーンリーダー選...
Kafka の Offset とコミット: ポジション管理と at-least-once の基礎
CCDAK 対策と実務の両立を意識して、Kafka コンシューマのオフセット管理とコミット戦略を整理。at-least-...