ログ圧縮は、同一キーの古いレコードをバックグラウンドで間引き、少なくとも最新(最後に書かれた)値を保持するKafkaのクリーンアップ方式です。履歴をすべて残すのではなく、最新の状態を取り出したい用途に有効です。
本稿では、仕組み・保証・代表設定・適用シーン・運用の勘所を、試験での出題ポイントと合わせて解説します。特に、tombstone(null値)の扱い、compactとdeleteの併用、セグメントとクリーンナーの関係は重点項目です。
ログ圧縮は、トピックのcleanup.policy=compactを有効にすると、パーティション内の各キーについて「最後に観測されたレコード」(last write)を少なくとも1件保持します。これにより、コンシューマはトピックを最初から読むだけで、最新状態のマップ(キー→値)を再構築できます。
値がnullのレコード(通称tombstone)は、そのキーの削除を意味します。tombstone自体はdelete.retention.msの期間だけ保持され、その後のクリーンで物理的に除去されます。ログ圧縮は即時ではなく、バックグラウンドで非同期に進みます。
| クリーンアップ方式 | 保持基準 | 主な用途 |
|---|---|---|
| delete | 時間/サイズ(retention.ms/bytes) | 完全履歴が必要なイベントログ |
| compact | キーごとの最新値(+最近の未圧縮領域) | 状態同期、キャッシュ、参照データ |
| compact,delete | 最新値+古いセグメントの期限削除 | 最新値を保ちつつディスクを強力制限 |
キーA/Bの更新と圧縮の概念図
Partition P0 (時系列 →)
| A:1 | B:x | A:2 | A:3 | B:y | A:null | B:z |
^ 古いAはA:3により不要
^ A:nullはAの削除指示(tombstone)
圧縮後(概念): | ... | A:null | B:z |
結果: Aは削除、Bは最新zが保持キー付きで投入する例(console-producer)
kafka-console-producer \
--broker-list localhost:9092 \
--topic users-compact \
--property parse.key=true \
--property key.separator=:
# 例: userId:json
u1:{"name":"Ann","v":1}
u1:{"name":"Ann","v":2}
u2:{"name":"Bob","v":1}
# 圧縮後、u1はv=2のみが残る(しばらく後に)パーティションは複数のセグメントに分割されます。書き込み中のアクティブセグメントは対象外で、ローテーション済み(閉じた)セグメントがクリーンの候補になります。クリーンはキーごとの最新オフセットをインデックス化し、古い重複を間引いた新しいセグメントへ再書き込みします。
どの程度“汚れて”いれば(重複が多ければ)クリーンするかはmin.cleanable.dirty.ratioや関連しきい値で制御されます。クリーナースレッド数、I/Oスループット、圧縮形式は独立で、クリーンは時間基準では実行されません。
| 設定/概念 | スコープ | 役割/要点 |
|---|---|---|
| log.cleaner.enable | ブローカー | ログクリーナーの有効化(通常は有効) |
| log.cleaner.threads | ブローカー | 並行クリーニング数の調整 |
| min.cleanable.dirty.ratio / min.cleanable.dirty.ratio(トピック) | 両方 | どの程度の重複が溜まったらクリーンするかの目安 |
セグメントのクリーンの流れ
Before:
[Seg-1(closed)] [Seg-2(closed)] [Seg-3(active)]
A:1 B:1 A:2 B:2 A:3 ...
Cleaner →
- 各キーの最新(A:3, B:2)を抽出
- 新しいクリーン済みセグメントへ再配置
After:
[Seg-1'(cleaned: A:3 B:2)] [Seg-3(active)]
旧Seg-1/2は削除候補セグメント内容を検査(kafka-dump-log.sh)
kafka-dump-log.sh --skip-record-metadata --print-data-log \
--files /var/lib/kafka/data/users-compact-0/00000000000000000000.log | head -n 50
# 圧縮の前後でキーごとの残存レコードを比較して確認するログ圧縮はトピック単位で有効化し、ブローカー既定のしきい値をトピックで上書き可能です。特にcleanup.policy、delete.retention.ms、min.cleanable.dirty.ratio、segment.ms/segment.bytes、min.compaction.lag.ms/max.compaction.lag.msの相互作用を押さえます。
compact,deleteを併用すると、最新値を保持しながら古い完全クリーン済みセグメントを期限で削除できます。tombstoneはdelete.retention.msの間は残るため、即時に完全消去はされません。試験では「キー必須」「tombstoneの意味」「クリーンは非同期」の3点が頻出です。
| パラメータ | 出題観点 | 注意点 |
|---|---|---|
| cleanup.policy | compactとdeleteの違い | 併用時は“最新値保持+期限削除”の両方が効く |
| delete.retention.ms | tombstoneの寿命 | 短いと復旧不能リスク、長いとディスク圧迫 |
| min.compaction.lag.ms/max.compaction.lag.ms | 圧縮タイミング | 早すぎ/遅すぎ双方の影響を理解する |
retentionとcompactionの適用順序イメージ
書き込み → セグメントローテーション → (閾値到達)Compaction →
[compactのみ] 最新値を保持
[compact,delete] さらに“完全クリーン済みかつ期限超”のセグメントを削除トピック作成時にcompactionを有効化
kafka-topics.sh --create \
--bootstrap-server localhost:9092 \
--topic users-compact \
--partitions 6 \
--replication-factor 3 \
--config cleanup.policy=compact \
--config min.cleanable.dirty.ratio=0.5 \
--config delete.retention.ms=86400000 \
--config segment.ms=3600000典型的な適用は、CDCの更新ストリーム(顧客の最新住所、在庫残量)、キャッシュのウォームアップ/更新、Kafka Streamsの状態ストア(changelog)です。いずれも“最新状態”が重要で、過去の細かな更新履歴は不要です。
アンチパターンは、完全履歴が不可欠な監査ログ、順序通りの全イベント再生が必要なイベントソーシング、キーが無い(またはほぼユニークで更新の無い)データです。これらはdeleteベースの保持か、別のストレージを選ぶべきです。
| シナリオ | 推奨トピック方式 | 注意点 |
|---|---|---|
| 顧客プロファイル最新値 | compact | キー設計(同一顧客→同一キー)必須 |
| 在庫残量同期 | compact,delete | ディスク制限しつつ最新を保持 |
| 監査・追跡ログ | delete | 完全履歴が要件。圧縮は不適 |
CDCから最新ビュー構築までの流れ
DB → CDC(更新/削除) → [compacted topic] →
→ キャッシュ/ビュー(キーごとの最新のみ)
削除イベントはtombstoneとして流れ、一定期間後に物理削除tombstoneを送る(Java Producer)
Properties p = new Properties();
p.put("bootstrap.servers", "localhost:9092");
p.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
p.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
try (KafkaProducer<String, String> producer = new KafkaProducer<>(p)) {
// 通常の最新値
producer.send(new ProducerRecord<>("users-compact", "u1", "{\"name\":\"Ann\"}"));
// 削除(tombstone): 値にnullを指定
producer.send(new ProducerRecord<>("users-compact", "u1", null));
producer.flush();
}圧縮は非同期で、負荷としきい値に左右されます。監視では、ログクリーナースレッドの稼働状況、クリーン対象バイト量、ディスク使用量、クリーンのバックログを追います。トピックごとの設定差分も定期点検します。
“圧縮されない/遅い/消えない”は定番の相談です。キーポイントは、(1)セグメントが閉じていない、(2)dirty比率が低い、(3)tombstone保持期間内、(4)キー無しレコード混在、(5)クリーナーのスループット不足、のいずれかであることが多いです。
| 症状 | 主因の例 | 対処の方向性 |
|---|---|---|
| 圧縮が進まない | セグメント未ローテート/dirty比率不足 | segment.ms/bytes見直し、しきい値調整 |
| 削除されない | delete.retention.ms期間内 | 保持期間の理解/調整、待機 |
| ディスク逼迫 | compactのみで高頻度更新 | compact,delete併用やスレッド増強/圧縮設定見直し |
tombstoneの寿命タイムライン
Write key=K, value=null (tombstone)
↓ 保持(delete.retention.ms)
Compaction removes K's old values
↓ 期限後
Tombstone自体も物理削除運用時の設定確認/変更
# 設定確認
kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type topics --entity-name users-compact --describe
# 動的変更(例): dirty比率を調整
kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type topics --entity-name users-compact \
--alter --add-config min.cleanable.dirty.ratio=0.4既存のdeleteベースのトピックからcompactまたはcompact,deleteへ移行する場合は、(1)キー付きで再発行できるか、(2)初期状態のバックフィル手段があるか、を先に確認します。キーが無いと圧縮の恩恵を受けません。
青/緑切替が安全です。新トピックをcompactで作成→バックフィル→新旧両配信→コンシューマ切替→旧トピック廃止の順で進めると、ダウンタイムとロールバックリスクを抑えられます。
| 移行オプション | 長所 | 注意点 |
|---|---|---|
| 青/緑(新トピック併設) | 安全・段階移行 | 二重配信期間のコスト |
| 同一トピック設定切替 | シンプル | 既存データは圧縮対象にならず効果発現に時間 |
| Streamsで集約→compact出力 | 同時に品質向上 | アプリ開発が必要 |
青/緑移行の流れ
Producers → [old-topic(delete)] → Consumers(旧)
↘︎ 変換/バックフィル ↘︎
[new-topic(compact)] → Consumers(新)
切替後にold-topicを段階廃止最新値のみを書き出すStreamsトポロジ(例)
StreamsBuilder b = new StreamsBuilder();
KStream<String, String> s = b.stream("src");
KTable<String, String> latest = s.groupByKey().reduce((agg, v) -> v);
latest.toStream().to("dst-compacted");
// dst-compacted は cleanup.policy=compact で作成しておくCCDAK / CCAAK
問題 1
Kafkaのログ圧縮トピックで、キーKの削除を確実に反映させたい。正しいアプローチはどれか。
正解: A
ログ圧縮における削除は、値がnullのレコード(tombstone)で表現する。tombstoneはdelete.retention.msの期間保持された後、クリーンによって古い値とともに物理削除される。他の選択肢は削除を意味せず、正しくない。
ログ圧縮はレコードの順序を壊しますか?
いいえ。圧縮後も、残ったレコード間の相対順序はパーティション内で維持されます。圧縮は古い重複を間引くだけで、順序の入れ替えは行いません。
compact,deleteを併用すると最新値が消えることはありますか?
通常はありません。クリーンによりキーの最新値だけが新しいセグメントへ再配置され、古い完全クリーン済みセグメントが期限で削除されます。結果として少なくとも最新値(またはtombstone)は保持されます。
コンシューマはどのオフセットから読めば最新ビューを再構築できますか?
最新ビューを初期化する場合はearliestからの全読込が推奨です。ログ圧縮は古い重複を消すだけで、最新値の再構築には“残った全レコード”の適用が必要です。
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-...