後方互換性を守るとは、過去に書かれたメッセージ(旧Writerスキーマ)を、新しいReaderスキーマで正しく読める状態を保つこと。Confluent Schema Registry の BACKWARD(特に TRANSITIVE)設定が軸になります。
本稿はCCDAKの出題範囲に合わせ、用語の正確さと、現場で手が動く具体策(安全に追加・改名・段階的廃止)に重点を置いています。
後方互換性は、新しいReaderスキーマ(主に新コンシューマ側)が、古いWriterスキーマで書かれたデータを読む保証です。Confluent Schema Registry の BACKWARD はこの性質を指し、TRANSITIVE を付けると全履歴の全バージョンに対して保証します。
一方で、既存のコンシューマを壊さずに新しいProducerを出したい場合は前方互換性(FORWARD)が関係します。FULL は BACKWARD と FORWARD の両方を同時に満たす必要があり、保守性は高い一方で許される変更の幅は狭くなります。実務ではロールアウト順序(先にコンシューマ更新か、先にプロデューサ更新か)に応じて使い分けます。
Avro のリーダー/ライター解決則に基づき、追加フィールドのデフォルト、数値の型拡張、別名(aliases)などが互換性維持に役立ちます。Protobuf や JSON Schema でも原理は似ていますが、細部は異なります(例えば Protobuf はフィールド番号が本質)。
| 互換性レベル | 新旧の読み書き保証 | 代表的に安全な変更例 |
|---|---|---|
| BACKWARD_TRANSITIVE | 新Readerが全履歴の旧Writerを読める | フィールド追加(デフォルト必須)、enumシンボル追加、数値型の拡張(int→long/float/double等)、フィールド並び替え、フィールド名変更+aliases |
| FORWARD_TRANSITIVE | 旧Readerが全履歴の新Writerを読める | フィールド追加(旧Readerが無視できる形)、デフォルト維持、既存必須フィールドの削除は不可 |
| FULL_TRANSITIVE | 双方が全履歴で相互読取可能 | 追加は厳格(デフォルト必須)、破壊的変更は段階移行で吸収 |
後方互換性(BACKWARD_TRANSITIVE)の流れ
互換性レベルの設定(例: BACKWARD_TRANSITIVE)
curl -s -X PUT http://localhost:8081/config/t-value \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{"compatibility": "BACKWARD_TRANSITIVE"}'
# グローバル既定を変える場合(推奨はSubject単位での設定)
# curl -s -X PUT http://localhost:8081/config -H "Content-Type: application/vnd.schemaregistry.v1+json" -d '{"compatibility": "BACKWARD_TRANSITIVE"}'最も安全で頻出なのは、フィールドの追加です。Avro では新たなフィールドにデフォルト値を必ず付けます。旧Writerがそのフィールドを持たない場合でも、新Readerはデフォルトで補完して読み取れます。
数値型の拡張(int→long/float/double、long→float/double、float→double)はAvroの解決則上、Reader側の後方互換で有効です。enum も新しいシンボルを追加するのは後方互換ですが、削除や並び替えは注意が必要です。
フィールド名変更は直接は破壊的ですが、Avro の aliases を使えば旧名で書かれたデータを新名のフィールドにマッピングできます。Protobuf ではフィールド名よりも番号が本質なので、番号を変えない限り改名は安全です。
Avro スキーマの追加・改名(aliases)例
// 旧スキーマ(Sv0)
{
"type": "record",
"name": "Order",
"namespace": "com.example",
"fields": [
{"name": "id", "type": "string"},
{"name": "amount", "type": "int"},
{"name": "status", "type": {"type":"enum","name":"Status","symbols":["NEW","PAID"]}}
]
}
// 新スキーマ(Sr1, BACKWARD互換):フィールド追加+改名
{
"type": "record",
"name": "Order",
"namespace": "com.example",
"fields": [
{"name": "id", "type": "string"},
{"name": "amount", "type": "long"}, // Reader側の型拡張
{"name": "state", "type": "string", "aliases": ["status"], "default": "NEW"},
{"name": "coupon", "type": ["null","string"], "default": null} // 新規追加
]
}
# 互換性チェック(最新と比較)
curl -s -X POST http://localhost:8081/compatibility/subjects/t-value/versions/latest \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{"schema": "{ \"type\": \"record\", \"name\": \"Order\", ... }"}'どうしても破壊的に見える変更(必須フィールド削除、型の縮小、enum削除、Protobufのフィールド番号変更など)は、一度にやらず段階的に分解します。まずは後方互換な形で併存させ、消費者・生産者の順に切替え、最後に不要部分を除去します。
移行中は Registry の互換性を BACKWARD_TRANSITIVE で固定し、Producer 先行のフェーズが必要な場合のみ短期に FORWARD 系を使います。FULL は両方向を同時に満たす必要があるため、変更幅がさらに狭まる点に留意します。
互換性ガードを効かせた登録フロー(擬似)
# 1) 互換性は原則 BACKWARD_TRANSITIVE
curl -s -X PUT http://localhost:8081/config/t-value -H "Content-Type: application/vnd.schemaregistry.v1+json" -d '{"compatibility":"BACKWARD_TRANSITIVE"}'
# 2) 登録前に必ずドライラン確認
curl -s -X POST http://localhost:8081/compatibility/subjects/t-value/versions/latest \
-H "Content-Type: application/vnd.schemaregistry.v1+json" -d '{"schema": "...new schema..."}'
# 3) 問題なければ登録
curl -s -X POST http://localhost:8081/subjects/t-value/versions \
-H "Content-Type: application/vnd.schemaregistry.v1+json" -d '{"schema": "...new schema..."}'互換性は Subject 単位で評価されます。TopicNameStrategy(例: topic-name-value)が一般的で、トピックごとに互換性を独立管理できます。レコードを複数トピックで共有したい場合は RecordNameStrategy を使うと、同一レコード名の互換性履歴を共有できますが、想定外の衝突には注意が必要です。
マルチトピック配信やKafka Connect/ksqlDBを交える場合、どのSubjectに登録されるか(valueかkeyか、トピック駆動かレコード駆動か)を事前に確認し、互換性レベルと移行計画を合わせます。
Kafka Avro Serializer の Subject 名戦略指定(例)
props.put("value.subject.name.strategy", "io.confluent.kafka.serializers.subject.TopicNameStrategy");
// 例: RecordNameStrategy を使う場合
// props.put("value.subject.name.strategy", "io.confluent.kafka.serializers.subject.RecordNameStrategy");スキーマの変更はコードレビューと同じ強度でゲートするのが安全です。事前に Registry 互換性APIでドライランチェックをかけ、テストデータで旧バージョンのメッセージを読み出す回帰テストを自動化します。スキーマの正規化(フィールド順やスペース差異の吸収)を前提に比較することで、ノイズの少ないレビューができます。
本番では互換性レベルをSubject単位で明示し、例外的に緩める必要がある場合は期間・影響・ロールバック手順をチケット化します。監査用にスキーマIDとGitコミットの対応表を残すのも有効です。
CIでの簡易ドライラン例(Bash)
SUBJECT="t-value"
NEWSCHEMA_FILE="schema_new.avsc"
BODY=$(jq -Rs '{schema: .}' < "$NEWSCHEMA_FILE")
curl -s -X POST "http://localhost:8081/compatibility/subjects/${SUBJECT}/versions/latest" \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d "$BODY" | jq .後方互換の鍵はコンシューマ側の堅牢さです。新しいReaderスキーマにデフォルトを定義し、未知フィールドは無視、null許容の扱いは明示的に分岐します。ログ圧縮トピックでは tombstone(value=null)を受ける可能性があるため、Avro/JSON Schema/Protobuf いずれでも null安全を確保してください。
GenericRecord なら存在チェックとデフォルト適用、SpecificRecord なら生成コードのデフォルトに依存します。エラー時にはスキップよりも死信キューやリトライキューに退避して、スキーマやデータの不一致を可観測化するのが有効です。
Avro GenericRecord の慎重な読み取り例(Java, 概略)
GenericRecord r = consumerRecord.value();
Long amount = null;
if (r.getSchema().getField("amount") != null) {
Object v = r.get("amount");
if (v instanceof Integer) amount = ((Integer) v).longValue();
else if (v instanceof Long) amount = (Long) v;
}
String state = (String) (r.get("state") != null ? r.get("state") : "NEW");
// coupon は optional
String coupon = r.get("coupon") == null ? null : r.get("coupon").toString();CCDAK
問題 1
Avro の value スキーマに新しいオプションフィールドをデフォルト付きで追加します。まずコンシューマを新Readerスキーマに更新し、旧データも継続して読みたい。Schema Registry の Subject に設定すべき互換性レベルとして最も適切なのはどれか(履歴全体を対象)?
正解: A
新しいReaderが過去のWriterで書かれた全履歴を読める保証が必要なため BACKWARD_TRANSITIVE が最適です。FORWARD は旧Readerが新Writerを読める保証であり順序が逆、NONE は無保証、FULL は双方向で制約が厳しく本件には不要です。
後方互換(BACKWARD)と前方互換(FORWARD)の使い分けは?
先にコンシューマを更新する計画ならBACKWARD(新Readerが旧Writerを読む)を選び、先にプロデューサを更新する計画ならFORWARD(旧Readerが新Writerを読む)を選びます。両方向を同時に満たしたい特殊なケースでのみFULLを検討します。
フィールドの改名はどう行えば安全ですか?
Avroでは新フィールド名にaliasesで旧名を登録します。段階的にコンシューマを新名へ切替え、十分な期間の後に旧名を持つフィールドを削除します。Protobufはフィールド番号が本質なので番号を変えずに名前だけを変えるのが安全です。
enum の変更で気をつける点は?
新しいシンボルの追加は後方互換ですが、既存シンボルの削除・置換は多くの互換性モードで失敗します。意味変更も実質的に破壊的です。追加のみで進め、不要になった値はアプリ側で非推奨扱いに留めるのが無難です。
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-...