Skip to content
Published on

トスバンク Data Engineer(Kafka & Streaming)合格ガイド:技術スタック・面接・6ヶ月ロードマップ

Authors

はじめに:トスバンクのリアルタイムデータチームが重要(じゅうよう)な理由

トスバンクは韓国(かんこく)のフィンテック企業(きぎょう)の中でも特別(とくべつ)な存在(そんざい)です。毎日(まいにち)数百万件(すうひゃくまんけん)のトランザクションを処理(しょり)するモバイルファーストバンクであり、それぞれのトランザクションはリアルタイムでキャプチャ、変換(へんかん)、配信(はいしん)される必要(ひつよう)があります。リアルタイムデータチームはこのアーキテクチャの中心(ちゅうしん)に位置(いち)しています。すべてのチームが依存(いぞん)するKafkaインフラストラクチャを運用(うんよう)し、不正検知(ふせいけんち)モデルに供給(きょうきゅう)するストリーミングパイプラインを構築(こうちく)し、数十(すうじゅう)のマイクロサービスを同期(どうき)させるCDC(Change Data Capture)システムを維持(いじ)しています。

トスバンクがData Engineer(Kafka & Streaming)のポジションを募集(ぼしゅう)する場合(ばあい)、ほとんどの企業が到達(とうたつ)しない規模(きぼ)でKafkaを運用できる人材(じんざい)を求(もと)めています。3ノードクラスターを立(た)ち上(あ)げて終(お)わりという役割(やくわり)ではありません。数百のブローカーを管理(かんり)し、損失(そんしつ)が許(ゆる)されない金融(きんゆう)データのデータセンター間レプリケーションを処理し、規制(きせい)された銀行(ぎんこう)環境(かんきょう)でExactly-Onceの結果(けっか)を生成(せいせい)するストリーム処理パイプラインを構築する役割です。

本ガイドでは、JDを一行(いちぎょう)ずつ分析(ぶんせき)し、各要件(ようけん)を具体的(ぐたいてき)な技術(ぎじゅつ)と学習(がくしゅう)リソースにマッピングし、準備(じゅんび)のための現実的(げんじつてき)な6ヶ月(ろっかげつ)プランを提供(ていきょう)します。


1. JD徹底分析(てっていぶんせき):トスバンクが本当(ほんとう)に求めるもの

1.1 主要責任(しゅようせきにん)

「全社(ぜんしゃ)のイベントストリーミングを処理するKafka Brokerクラスターの運用と最適化(さいてきか)」

これが最も重要な責任です。あなたはKafkaの消費者(しょうひしゃ)ではなく、Kafkaを稼働(かどう)させ続(つづ)ける人です。具体的には:

  • ブローカー、パーティション、トピックのキャパシティプランニング
  • パフォーマンスチューニング(OS、JVM、Kafka設定(せってい))
  • Kafka Manager、Burrow、カスタムPrometheusエクスポーターによるモニタリング
  • ダウンタイムゼロのローリングアップグレード
  • 午前3時にブローカーがダウンした際(さい)のインシデント対応(たいおう)

「全社的に使用されるSpring BootベースKafka Client SDKの開発(かいはつ)と保守(ほしゅ)」

トスバンクはすべてのチームがKafkaメッセージを生成・消費する方法(ほうほう)を標準化(ひょうじゅんか)する社内(しゃない)ライブラリを構築しています。以下のスキルが必要です:

  • Spring Boot自動設定(じどうせってい)とスターターモジュール
  • Kafka ProducerおよびConsumer APIの低レベル理解(りかい)
  • AvroまたはProtobufとSchema Registryによるスキーマ管理
  • エラーハンドリングパターン:デッドレターキュー、リトライトピック、サーキットブレーカー
  • SDK進化(しんか)における後方互換性(こうほうごかんせい)

「データセンター間Active-Activeレプリケーションの設計(せっけい)と実装(じっそう)」

金融規制により、トスバンクはデータセンター全体(ぜんたい)がオフラインになっても生(い)き残(のこ)れなければなりません。Active-Active Kafkaレプリケーションとは:

  • MirrorMaker 2またはConfluent Replicatorによるクロスクラスターレプリケーション
  • クラスター間のオフセット変換
  • デュアルライトシナリオの競合解決(きょうごうかいけつ)戦略(せんりゃく)
  • RTO/RPO要件を満(み)たすフェイルオーバーとフェイルバック手順(てじゅん)

「Debeziumを使用したCDCパイプラインの構築」

CDCはマイクロサービス間の緊密(きんみつ)な結合(けつごう)なしに同期を維持する方法です。Debeziumはデータベース(MySQL、PostgreSQL)から行(ぎょう)レベルの変更(へんこう)をキャプチャし、Kafkaトピックに公開(こうかい)します:

  • 各データベース向けDebeziumコネクター
  • Kafka Connectアーキテクチャと分散(ぶんさん)モード
  • データベーススキーマ変更時のスキーマ進化
  • 大規模テーブルの初期スナップショット処理
  • エンドツーエンドのExactly-Onceデリバリーセマンティクス

「Flinkを使用したストリーム処理アプリケーションの開発」

Flinkはステートフルストリーム処理のためのチームの選択(せんたく)です。単純な変換を超えた領域:

  • ウィンドウイング(タンブリング、スライディング、セッションウィンドウ)
  • RocksDBバックエンドによるステート管理
  • チェックポインティングとセーブポイントによるフォールトトレランス
  • イベント時間 vs 処理時間セマンティクス
  • 不正検知パターンのための複合(ふくごう)イベント処理(CEP)

「リアルタイム分析ダッシュボード用ClickHouseの管理」

ClickHouseは大規模データセットでの高速分析クエリに最適化されたOLAPデータベースです:

  • トランザクション量、レイテンシパーセンタイル、エラー率を表示するリアルタイムダッシュボード
  • マテリアライズされたストリーミングデータに対するアドホッククエリ
  • データ保持(ほじ)と階層型(かいそうがた)ストレージ戦略

1.2 必須要件(ひっすようけん)

「Kafkaの本番運用経験(けいけん)3年以上(いじょう)」

これは交渉(こうしょう)の余地(よち)がありません。ISR(In-Sync Replica)縮小(しゅくしょう)の問題(もんだい)をデバッグし、不均衡(ふきんこう)なパーティションリーダーに対処し、unclean.leader.election.enableがデフォルトでfalseである理由を理解している人が求められています。

「JavaまたはKotlinとSpring Bootの習熟(しゅうじゅく)」

社内SDKはSpring Bootで構築されています。スクリプトではなく、本番品質のJavaまたはKotlinコードを書く必要があります。

「分散システムの基礎(きそ)の理解」

CAP定理(ていり)のトレードオフ、リーダー選出(せんしゅつ)アルゴリズム、合意(ごうい)プロトコル、分散システムでExactly-Onceセマンティクスが難(むずか)しい理由を議論(ぎろん)できることが求められます。

「Linux システム管理経験」

Kafkaはlinux上で動作(どうさ)します。ファイルシステムチューニング(XFS、ページキャッシュ、ディスクI/Oスケジューリング)、ネットワークチューニング、JVMガベージコレクションチューニングに精通(せいつう)している必要があります。

1.3 優遇要件(ゆうぐうようけん)

「FlinkまたはSpark Structured Streamingの経験」

少なくとも1つのストリーム処理フレームワークの実践(じっせん)経験があれば有利(ゆうり)です。Flinkが好(この)まれますが、Spark Structured Streamingの知識(ちしき)も活(い)かせます。

「Kubernetesベースのデプロイメントの知識」

トスはインフラの多(おお)くをKubernetes上で運用しています。StatefulSet、永続ボリューム、K8s上でKafkaのようなステートフルシステムを実行する課題(かだい)の理解が価値(かち)あります。

「オープンソースプロジェクトへの貢献(こうけん)」

トスバンクはオープンソースに積極的(せっきょくてき)に貢献しています。小さなものでも貢献の実績(じっせき)があれば、大規模な協力的(きょうりょくてき)ソフトウェア開発を理解していることを示(しめ)せます。


2. 技術スタック深掘(ふかぼ)り

2.1 Apache Kafka:基盤(きばん)

Kafkaはトスバンクのデータインフラの中枢神経系(ちゅうすうしんけいけい)です。基本を超えて知るべきことを解説(かいせつ)します。

ブローカー内部構造(ないぶこうぞう)

Kafkaブローカーはメッセージをディスク上のセグメントに保存(ほぞん)します。ログ構造の理解は基本です:

topic-partition/
  00000000000000000000.log       # 最初のセグメント
  00000000000000000000.index     # オフセットインデックス
  00000000000000000000.timeindex # 時間ベースインデックス
  00000000000000065536.log       # 2番目のセグメント(ロール後)

理解必須の主要設定パラメータ:

# レプリケーション
default.replication.factor=3
min.insync.replicas=2
unclean.leader.election.enable=false

# パフォーマンス
num.io.threads=8
num.network.threads=3
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400

# ログ管理
log.segment.bytes=1073741824
log.retention.hours=168
log.cleanup.policy=delete

Producerチューニング

Producerの設定はスループット、レイテンシ、耐久性(たいきゅうせい)の保証(ほしょう)を決定(けってい)します:

Properties props = new Properties();
props.put("acks", "all");                    // 全ISRの確認を待つ
props.put("retries", Integer.MAX_VALUE);     // 無限リトライ
props.put("max.in.flight.requests.per.connection", 5);
props.put("enable.idempotence", true);       // パーティションごとのExactly-Once
props.put("batch.size", 16384);              // バッチサイズ(バイト)
props.put("linger.ms", 5);                   // バッチ充填まで最大5ms待機
props.put("compression.type", "lz4");        // バッチ圧縮

Consumer Groupリバランシング

Consumer Groupのリバランシングは最も一般的な運用上の課題の1つです。プロトコルの理解が重要です:

  • Eagerリバランシング:全Consumerが停止(ていし)し、再割当(さいわりあて)し、再開(さいかい)。単純(たんじゅん)だがStop-the-Worldの停止を引き起こす。
  • Cooperativeリバランシング(Incremental):影響(えいきょう)を受(う)けるパーティションのみが取消(とりけし)・再割当。Kafka 2.4+で導入(どうにゅう)。
  • Static Group Membershipgroup.instance.idを持つConsumerは一時的(いちじてき)な障害(しょうがい)でリバランシングを回避(かいひ)。
// Cooperativeリバランシング設定
props.put("partition.assignment.strategy",
    "org.apache.kafka.clients.consumer.CooperativeStickyAssignor");
props.put("group.instance.id", "consumer-host-1");
props.put("session.timeout.ms", 30000);
props.put("heartbeat.interval.ms", 10000);

2.2 KRaftモード:ZooKeeper不要のKafka

KafkaはKIP-500以降、ZooKeeper依存からの脱却(だっきゃく)を進(すす)めています。KRaft(Kafka Raft)モードはZooKeeperを内部Raftベースの合意プロトコルに置き換えます。

トスバンクにとっての重要性:

  • 運用の複雑性(ふくざつせい)の低減(ていげん)(ZooKeeperアンサンブルの別管理が不要)
  • コントローラーフェイルオーバーの高速化(数十秒から数秒へ)
  • メタデータのスケーラビリティ向上(数百万パーティション)
  • Kubernetes上でのデプロイ簡素化(かんそか)
# KRaftコントローラー設定
process.roles=controller
node.id=1
controller.quorum.voters=1@controller1:9093,2@controller2:9093,3@controller3:9093
controller.listener.names=CONTROLLER

2.3 Spring Boot Kafka Client SDK

社内SDKの構築は、Kafkaの課題であると同時にソフトウェアエンジニアリングの課題です:

@Configuration
@EnableKafka
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, GenericRecord> producerFactory(
            KafkaProperties properties) {
        Map<String, Object> config = properties.buildProducerProperties();
        config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
            StringSerializer.class);
        config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
            KafkaAvroSerializer.class);
        config.put("schema.registry.url", schemaRegistryUrl);
        return new DefaultKafkaProducerFactory<>(config);
    }

    @Bean
    public KafkaTemplate<String, GenericRecord> kafkaTemplate(
            ProducerFactory<String, GenericRecord> factory) {
        KafkaTemplate<String, GenericRecord> template =
            new KafkaTemplate<>(factory);
        template.setObservationEnabled(true); // Micrometerトレーシング
        return template;
    }
}

デッドレターキューパターン

リトライ後もConsumerがメッセージを処理できない場合、デッドレタートピックにルーティングします:

@Bean
public ConcurrentKafkaListenerContainerFactory<String, String>
        kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, String> factory =
        new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory());
    factory.setCommonErrorHandler(new DefaultErrorHandler(
        new DeadLetterPublishingRecoverer(kafkaTemplate),
        new FixedBackOff(1000L, 3)  // 3回リトライ、1秒間隔
    ));
    return factory;
}

2.4 Active-Active Kafkaレプリケーション

Active-Activeレプリケーションは分散Kafkaデプロイメントで最も困難(こんなん)な問題の1つです。

MirrorMaker 2アーキテクチャ

MirrorMaker 2(MM2)はKafka Connect上に構築され、以下を提供します:

  • 設定可能(かのう)なトピック名リマッピングによるトピックレプリケーション
  • Consumer Groupオフセット同期
  • トピック設定の自動同期(パーティション、設定)
  • レプリケーションラグ監視(かんし)用ハートビートトピック
# MirrorMaker 2設定
clusters = dc1, dc2
dc1.bootstrap.servers = dc1-kafka1:9092,dc1-kafka2:9092
dc2.bootstrap.servers = dc2-kafka1:9092,dc2-kafka2:9092

# 双方向レプリケーション
dc1->dc2.enabled = true
dc2->dc1.enabled = true

# トピックフィルタリング
dc1->dc2.topics = transactions.*, user-events.*
dc2->dc1.topics = transactions.*, user-events.*

# レプリケーションループ防止
replication.policy.class = org.apache.kafka.connect.mirror.IdentityReplicationPolicy

競合解決

Active-Activeセットアップでは、同じトピックが両方のデータセンターで書き込みを受ける可能性があります。戦略には以下が含まれます:

  • タイムスタンプによるLast-Writer-Wins:単純だがデータ損失の可能性あり
  • アプリケーションレベルの競合解決:Consumerが競合レコードをマージ
  • リージョンベースのパーティショニング:各DCが異なるパーティションに書き込み、競合を完全に回避

2.5 DebeziumによるCDC

Debeziumはデータベースのトランザクションログ(MySQLのbinlog、PostgreSQLのWAL)を読み取ることでデータベースの変更をキャプチャします。

{
  "name": "postgres-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgres-primary",
    "database.port": "5432",
    "database.user": "debezium",
    "database.dbname": "tossbank",
    "topic.prefix": "cdc",
    "table.include.list": "public.accounts,public.transactions",
    "plugin.name": "pgoutput",
    "slot.name": "debezium_slot",
    "publication.name": "debezium_pub",
    "snapshot.mode": "initial",
    "transforms": "route",
    "transforms.route.type": "io.debezium.transforms.ByLogicalTableRouter",
    "transforms.route.topic.regex": "(.*)\\\\.(.*)",
    "transforms.route.topic.replacement": "cdc.$2"
  }
}

重要なDebeziumコンセプト

  • スナップショットモードinitial(フルスナップショット後にストリーム)、schema_only(構造のみ、現在位置からストリーム)、never(ストリームのみ)
  • Outboxパターン:ビジネステーブルへのCDCの代わりに、アプリケーションがOutboxテーブルにイベントを書き込み、Debeziumがそれをキャプチャ。イベントスキーマをデータベーススキーマから分離。
  • スキーマ進化:データベースカラムが追加(ついか)・変更されると、DebeziumはKafkaメッセージスキーマに変更を反映。Schema Registryの互換性モード(BACKWARD、FORWARD、FULL)がConsumerが変更に対応できるかを決定。

2.6 Apache Flink:ストリーム処理

Flinkは Kafkaストリーム上のステートフル計算(けいさん)のためのストリーム処理エンジンです。

Flinkアプリケーション構造

StreamExecutionEnvironment env =
    StreamExecutionEnvironment.getExecutionEnvironment();

// Exactly-Onceチェックポインティング有効化
env.enableCheckpointing(60000, CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(30000);
env.getCheckpointConfig().setCheckpointTimeout(120000);

// Kafkaソース
KafkaSource<Transaction> source = KafkaSource.<Transaction>builder()
    .setBootstrapServers("kafka:9092")
    .setTopics("transactions")
    .setGroupId("flink-processor")
    .setStartingOffsets(OffsetsInitializer.committedOffsets())
    .setDeserializer(new TransactionDeserializer())
    .build();

DataStream<Transaction> transactions = env.fromSource(
    source,
    WatermarkStrategy.<Transaction>forBoundedOutOfOrderness(
        Duration.ofSeconds(5))
        .withTimestampAssigner((tx, ts) -> tx.getTimestamp()),
    "Kafka Source"
);

// ウィンドウ集約:アカウントごと1分間のトランザクション数
transactions
    .keyBy(Transaction::getAccountId)
    .window(TumblingEventTimeWindows.of(Time.minutes(1)))
    .aggregate(new TransactionCountAggregate())
    .addSink(new ClickHouseSink());

env.execute("Transaction Aggregation");

ステート管理

Flinkはステートバックエンドでステートを維持します。本番ワークロードの場合:

  • HashMapStateBackend:高速、JVMヒープにステートを保存。小さなステート向き。
  • EmbeddedRocksDBStateBackend:RocksDB経由でローカルディスクにステートを保存。メモリを超える大きなステートに必須。
env.setStateBackend(new EmbeddedRocksDBStateBackend(true));
env.getCheckpointConfig().setCheckpointStorage(
    "s3://flink-checkpoints/job-1");

2.7 ClickHouse:リアルタイム分析

ClickHouseは大規模データセットでの集約(しゅうやく)クエリに優(すぐ)れたカラム型OLAPデータベースです。

ストリーミングデータ用テーブル設計

CREATE TABLE transactions_realtime (
    transaction_id UUID,
    account_id UInt64,
    amount Decimal64(2),
    currency LowCardinality(String),
    transaction_type LowCardinality(String),
    status LowCardinality(String),
    created_at DateTime64(3, 'Asia/Seoul'),
    region LowCardinality(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(created_at)
ORDER BY (account_id, created_at)
TTL created_at + INTERVAL 90 DAY;

Kafka統合(とうごう)

ClickHouseはKafkaエンジンを使用してKafkaから直接(ちょくせつ)消費できます:

CREATE TABLE transactions_kafka (
    transaction_id UUID,
    account_id UInt64,
    amount Decimal64(2),
    created_at DateTime64(3)
) ENGINE = Kafka()
SETTINGS
    kafka_broker_list = 'kafka1:9092,kafka2:9092',
    kafka_topic_list = 'transactions',
    kafka_group_name = 'clickhouse-consumer',
    kafka_format = 'JSONEachRow';

CREATE MATERIALIZED VIEW transactions_mv TO transactions_realtime AS
SELECT * FROM transactions_kafka;

3. 面接対策(めんせつたいさく):30問

3.1 Kafka基礎(きそ)(問題1-10)

Q1. Kafkaがディスクに書き込みながら高スループットを達成(たっせい)する仕組(しく)みを説明(せつめい)してください。

Kafkaはシーケンシャルl/O、OSページキャッシュ、ゼロコピー転送(sendfileシステムコール)を使用します。ディスクへのシーケンシャル書き込みは最新のSSDで600 MB/s以上のスループットを達成でき、ネットワークスループットに匹敵(ひってき)します。OSページキャッシュにより、頻繁にアクセスされるデータはKafka自身がキャッシュを管理することなくメモリから提供されます。

Q2. ISRセットのKafkaブローカーが遅延(ちえん)した場合、何が起(お)こりますか?

レプリカがリーダーからreplica.lag.time.max.ms(デフォルト30秒)以上遅れると、ISRセットから除外(じょがい)されます。min.insync.replicasが2に設定されていて、1つのレプリカだけが同期している場合、acks=allのProducerはNotEnoughReplicasExceptionを受け取ります。遅延したレプリカはキャッチアップ後にISRに再参加します。

Q3. EagerとCooperativeのConsumerリバランシングプロトコルを比較(ひかく)してください。

EagerリバランシングはすべてのConsumerからすべてのパーティションを取り消し、すべてを再割当します。Consumerがメッセージを処理しないStop-the-Worldの停止を引き起こします。Cooperativeリバランシングは移動が必要なパーティションのみを取り消し、他のConsumerは処理を続行(ぞっこう)できます。本番環境ではCooperativeが常に推奨(すいしょう)されます。

Q4. KafkaのIdempotent Producerはどのように機能(きのう)しますか?

enable.idempotence=trueの場合、各Producerインスタンスは一意のProducer ID(PID)を取得します。各メッセージバッチにはシーケンス番号が含まれます。ブローカーはPIDとパーティションごとに最後のシーケンス番号を追跡し、メッセージを重複排除(じゅうふくはいじょ)します。リトライが同じバッチを送信した場合、ブローカーは重複を認識し、再書き込みせずに確認応答を返します。

Q5. log.retention.hourslog.retention.bytesの違(ちが)いは?

log.retention.hoursは指定時間より古いセグメントを削除します。log.retention.bytesは合計パーティションサイズが制限を超えた場合、最も古いセグメントを削除します。両方が設定されている場合、先にしきい値に達した方が削除をトリガーします。圧縮トピックでは、log.cleanup.policy=compactが削除をキーベースの圧縮に置き換えます。

Q6. Kafkaトランザクションとexactly-onceセマンティクスを説明してください。

Kafkaトランザクションにより、Producerは複数のパーティションにアトミックに書き込めます。ProducerはbeginTransaction()を呼び出し、メッセージを送信し、commitTransaction()を呼び出します。ブローカーのトランザクションコーディネーターがトランザクション状態を追跡します。isolation.level=read_committedのConsumerはコミットされたメッセージのみを参照します。Idempotent Producerと組み合わせることで、Kafka内でExactly-Onceセマンティクスを実現します。

Q7. Kafkaクラスターの健全性をどのように監視しますか?

追跡すべき主要メトリクス:UnderReplicatedPartitions(0であるべき)、ActiveControllerCount(正確に1)、OfflinePartitionsCount(0であるべき)、RequestHandlerAvgIdlePercent(0.3以上)、NetworkProcessorAvgIdlePercent(0.3以上)、ブローカーごとのBytesInPerSec/BytesOutPerSec、パーティションごとのConsumer Groupラグ。ツール:JMXエクスポーターからPrometheus、Grafanaダッシュボード、Consumer Lag用Burrow、重要メトリクスのカスタムアラート。

Q8. unclean.leader.election.enableの目的と、金融データでfalseにすべき理由は?

trueに設定すると、ISRにないレプリカがすべてのISRレプリカがダウンした場合にリーダーになれます。これは可用性の喪失を防ぎますが、新しいリーダーが最近コミットされたメッセージを欠いている可能性があるため、データ損失のリスクがあります。データ損失が許容(きょよう)されない金融データでは、データの不整合(ふせいごう)よりも一時的な非可用性を受け入れるため、falseに設定する必要があります。

Q9. ログコンパクションの仕組みと使用場面(ばめん)を教えてください。

ログコンパクションは各キーの最新値のみを保持します。バックグラウンドスレッド(ログクリーナー)がセグメントをスキャンし、同じキーの古いレコードを削除します。イベントストリームではなく、エンティティの現在の状態(ユーザープロファイル、口座(こうざ)残高)を表すトピックに使用します。設定:log.cleanup.policy=compactmin.cleanable.dirty.ratiolog.cleaner.min.compaction.lag.ms

Q10. パーティション数がパフォーマンスと順序保証(じゅんじょほしょう)に与える影響を説明してください。

パーティションが多いほど並列性(へいれつせい)が向上(より多くのConsumerが同時に読み取り可能)しますが、ブローカーのメモリ使用量の増加、エンドツーエンドのレイテンシ増加、リーダー選出時間の増加、ファイルハンドル数の増加も伴います。順序保証は単一パーティション内でのみ保証されます。順序付きイベント処理には、一貫(いっかん)したパーティションキー(例:アカウントID)を使用します。

3.2 Spring BootとSDK設計(問題11-15)

Q11. スキーマ進化に対応するKafka Client SDKをどのように設計しますか?

Apache AvroとConfluent Schema Registryを使用します。SDKは初回使用時にスキーマを登録しキャッシュします。BACKWARD互換性を設定し、新しいConsumerが古いデータを読めるようにします。SDKはKafkaAvroSerializerKafkaAvroDeserializerをラップし、シリアライゼーションを透過的(とうかてき)に処理します。

Q12. SDKにどのようなリトライパターンを組み込みますか?

一時的な障害には指数バックオフとジッターを実装します。Spring RetryのRetryTemplateまたは組み込みのKafka Consumerリトライメカニズムを使用します。最大リトライ後も失敗するメッセージは、元メッセージ、エラー詳細、リトライ回数をヘッダーとしてデッドレタートピックにルーティングします。

Q13. Spring Boot Kafka Consumerのグレースフルシャットダウンの処理方法は?

consumer.wakeup()を呼び出すシャットダウンフックを登録します。WakeupExceptionがConsumerをpoll()から抜けさせ、finallyブロックでconsumer.close()を呼び出してオフセットをコミットし、Consumer Groupをクリーンに離脱します。Spring Bootでは、ConcurrentKafkaListenerContainerFactorycontainerProperties.setShutdownTimeout()設定でこれを処理します。

Q14. Kafkaメッセージに分散トレーシングを追加する方法は?

Producer側でトレースコンテキスト(トレースID、スパンID)をKafkaメッセージヘッダーに注入します。Consumer側でヘッダーからコンテキストを抽出し、Producerスパンにリンクされた新しいスパンを作成します。Spring Cloud SleuthまたはMicrometer Tracingを使用すると、KafkaTemplate@KafkaListenerで観測(かんそく)を有効にすることで大部分が自動化されます。

Q15. 既存Consumerを壊さずにSDKをバージョン管理する方法は?

セマンティックバージョニングに従います。Spring Bootの自動設定で既存Consumerが継承するデフォルト値を提供します。新機能はオプトイン設定プロパティを使用します。破壊的(はかいてき)変更が避けられない場合は、移行ガイドと互換性モジュールを提供します。

3.3 分散システム(問題16-20)

Q16. CAP定理とKafkaの位置付(いちづ)けを説明してください。

CAPは分散システムが一貫性(Consistency)、可用性(Availability)、分断耐性(Partition Tolerance)の3つのうち最大2つしか保証できないと述べています。acks=allmin.insync.replicas=2unclean.leader.election.enable=falseで設定された場合、KafkaはCPを優先します。十分なレプリカが同期していない場合、書き込みを拒否し、可用性を犠牲にします。

Q17. KRaftモードでのRaftコンセンサスの動作は?

KRaftモードでは、コントローラーノードがRaftプロトコルを使用してリーダーを選出します。リーダーはメタデータの変更を複製ログに追記します。フォロワーがログを複製し変更を適用します。リーダーはコントローラークォーラムの過半数からの投票を受けて選出されます。リーダーが失敗した場合、最新のログを持つフォロワーが新しい選挙を開始します。

Q18. スプリットブレイン問題とKafkaの防止策は?

スプリットブレインは2つのノードが両方ともリーダーだと信じる場合に発生します。ZooKeeperモードでは、Kafkaはエフェメラルノードを使用します。ブローカーがZooKeeperセッションを失うと、そのリーダーシップが取り消されます。KRaftモードでは、Raftプロトコルのtermベースの投票が2つのリーダーの同時存在を防止します。

Q19. ストリーム処理におけるバックプレッシャーとFlinkの処理方法は?

バックプレッシャーは下流のオペレーターが上流のレートに追いつけない場合に発生します。Flinkはクレジットベースのフロー制御メカニズムを使用します。各下流タスクは受け入れ可能なネットワークバッファ数(クレジット)を通信します。クレジットがなくなると上流タスクは送信を停止し、この圧力がソースまで伝搬(でんぱ)します。

Q20. KafkaとデータベースにまたがるExactly-Onceメッセージ処理を保証するシステム設計は?

トランザクショナルOutboxパターンを使用します。KafkaとデータベースICIの両方に書き込む代わりに、データベースのみに書き込みます(ビジネスデータとOutboxテーブルへ、単一トランザクションで)。DebeziumがOutboxテーブルの変更をキャプチャしKafkaに公開します。これにより、メッセージはトランザクションがコミットされた場合にのみ公開されることが保証されます。

3.4 CDCとデータパイプライン(問題21-25)

Q21. CDCアプローチの比較:ログベース vs クエリベース vs トリガーベース

ログベースCDC(Debezium)はデータベーストランザクションログを直接読み取ります。低オーバーヘッドですべての変更をキャプチャします。クエリベースCDCはタイムスタンプやバージョン列を使用してデータベースの変更をポーリングします。削除を見逃(みのが)し、レイテンシが高くなります。トリガーベースCDCはデータベーストリガーを使用します。柔軟(じゅうなん)ですが書き込みオーバーヘッドと複雑性を追加します。本番システムではログベースが推奨されます。

Q22. CDCパイプラインでのスキーマ変更の処理方法は?

カラムが追加されると、Debeziumは新しいスキーマをキャプチャしSchema Registryに転送します。BACKWARD互換性では、既存ConsumerはOldメッセージを読み取れます(新フィールドにデフォルト値あり)。カラム削除時はFORWARD互換性を使用します。重要なのはSchema Registryの互換性モードを正しく設定し、本番前にステージング環境でスキーマ変更をテストすることです。

Q23. DebeziumコネクターがデータベースのWALに遅れた場合、何が起こりますか?

コネクターが遅れすぎると、データベースはすでにWALセグメントをリサイクルしている可能性があります。コネクターは「WALセグメントが見つかりません」エラーで失敗します。回復オプション:影響を受けるテーブルの再スナップショット(一時的にsnapshot.modealwaysに設定)、またはWAL位置を復元するためのポイントインタイムリカバリ。予防:コネクターラグの監視、十分なWAL保持の確保。

Q24. Outboxパターンとデュアルライトに対する利点(りてん)を説明してください。

デュアルライトでは、アプリケーションがデータベースとKafkaの両方に書き込みます。一方の書き込みが失敗すると不整合になります。Outboxパターンではこれを回避:アプリケーションがビジネスデータとOutboxイベントを単一データベーストランザクションで書き込みます。DebeziumがOutboxイベントをキャプチャしKafkaに公開します。両方の書き込みが同じトランザクション内にあるため、アトミックです。

Q25. 金融システムでのCDCデータ品質(ひんしつ)をどのように確保しますか?

定期的にソースデータベースの状態とCDCイベントから導出された状態を比較するリコンシリエーションパイプラインを実装します。テーブルごと、時間ウィンドウごとのチェックサムまたは行数を使用します。不一致のアラートを設定します。さらに、エンドツーエンドのウォーターマーキングを追加:ソースで合成イベントを注入し、SLA内にシンクに到達するか検証します。

3.5 運用と本番環境(問題26-30)

Q26. Kafkaクラスターのローリングアップグレードの手順は?

ブローカーを1つずつアップグレードします。停止前に、そのブローカーがリードするすべてのパーティションに最新のレプリカがあることを確認します。controlled.shutdown.enable=trueを設定し、シャットダウン前にリーダーシップを移譲(いじょう)させます。アップグレード後、ブローカーがクラスターに再参加しキャッチアップすることを確認します。プロセス全体でUnderReplicatedPartitionsを監視します。

Q27. Consumer Groupが特定パーティションで高いラグを持つ場合、どう診断(しんだん)しますか?

影響を受けるパーティションが同じブローカーにあるか確認(ブローカーの問題の可能性)。レコードごとのConsumer処理時間を確認(遅い処理)。パーティションキーのデータスキューを確認(ホットパーティション)。GCログで長いポーズを確認。下流システムでのレート制限を確認。kafka-consumer-groups.sh --describeでパーティションごとのラグとConsumer割り当てを確認します。

Q28. Kafkaのデータセンターフェイルオーバーの処理方法は?

MirrorMaker 2がすべての重要トピックを最小ラグでレプリケーションしていることを確認。フェイルオーバー時:障害DCのProducerを停止、存続DCでレプリケーションがキャッチアップしていることを確認、DNSまたはロードバランサーをリダイレクト、変換されたオフセットから存続DCのConsumerを起動。フェイルオーバー後:データギャップの監視、リコンシリエーションの実行、元のDCが回復した際のフェイルバック計画。

Q29. Kafkaトピックの命名規則(めいめいきそく)へのアプローチは?

ドメイン、イベントタイプ、バージョンをエンコードする階層的な命名スキームを使用:

domain.subdomain.event-type.version

例:payments.transactions.created.v1cdc.accounts.changes。特殊文字を避ける。SDKのトピックガバナンスツールまたは命名バリデーションで規則を強制します。

Q30. Kafkaクラスターのキャパシティプランニングの方法は?

必要なスループット(MB/s)と保持期間から始めます。ストレージ計算:スループット x 保持期間 x レプリケーションファクター。コンパクションとセグメントロールのオーバーヘッドを追加。ブローカー:各ブローカーはハードウェアに応じて約100-200 MB/sを処理可能。N+1プランニング(1ブローカーが障害してもオーバーロードしない)。パーティション:パーティションあたり10-20 MB/sを目標。実際の使用量を監視し四半期(しはんき)ごとに調整。


4. 6ヶ月学習ロードマップ

第1ヶ月:Kafka基礎

第1-2週:コアコンセプト

  • 「Kafka: The Definitive Guide」(第2版)第1-6章を読む
  • Docker Composeで3ブローカーKafkaクラスターをローカルに構築
  • CLIツールでメッセージの生成と消費を実践
  • セグメントファイルを直接確認してログ構造を学習

第3-4週:ProducerとConsumerの深掘り

  • 異なるacks設定のJava Producerを実装しスループットを測定
  • 手動オフセットコミットのConsumer Groupを実装
  • リバランシングの実験:Consumerの追加・削除、パーティション再割当の観察
  • KafkaProducer.send()KafkaConsumer.poll()のKafkaソースコードを読む

第2ヶ月:Spring BootとSDK開発

第1-2週:Spring Kafka

  • spring-kafkaでSpring Bootアプリケーションを構築
  • ロギングとメトリクス用のProducerインターセプターを実装
  • エラーハンドリングとデッドレターキュー付きConsumerを構築
  • EmbeddedKafkaを使用した統合テストを作成

第3-4週:SDK設計

  • 再利用可能なSpring Boot Kafkaスターターモジュールを設計・構築
  • 共通シリアライゼーション形式(JSON、Avro)の自動設定を追加
  • Schema Registry統合を実装
  • ドキュメント付きMaven/Gradleライブラリとしてパッケージ化

第3ヶ月:分散システムとレプリケーション

第1-2週:理論(りろん)

  • 「Designing Data-Intensive Applications」第5、8、9章を読む
  • Raftコンセンサス論文(ろんぶん)を学習
  • KRaftモードアーキテクチャの理解
  • 分散システム設計問題を練習

第3-4週:Active-Activeレプリケーション

  • 2つのKafkaクラスター間でMirrorMaker 2をローカルに構築
  • トピックレプリケーション、オフセット同期、フェイルオーバーをテスト
  • クラスター切替を処理するConsumerを実装
  • フェイルオーバー手順をステップバイステップで文書化

第4ヶ月:DebeziumによるCDC

第1-2週:Debezium基礎

  • PostgreSQLとKafka ConnectでDebeziumを構築
  • サンプルテーブルでINSERT、UPDATE、DELETEをキャプチャ
  • Debeziumイベント形式とスキーマを学習
  • サンプルアプリケーションでOutboxパターンを実装

第3-4週:本番CDC

  • スキーマ進化シナリオのテスト(カラム追加、名前変更、削除)
  • ソースとシンク間のリコンシリエーションチェックを実装
  • DebeziumのExactly-Onceデリバリー設定を学習
  • 大規模テーブルの初期スナップショット処理

第5ヶ月:Flinkストリーム処理

第1-2週:Flink基礎

  • 公式Flinkトレーニング演習(えんしゅう)を完了
  • Kafkaから読み取り集約を書き出すストリーミングアプリケーションを構築
  • ウィンドウイングの学習:タンブリング、スライディング、セッションウィンドウ
  • ウォーターマーク付きイベント時間処理を実装

第3-4週:高度なFlink

  • マネージドキー付きステートによるステートフル処理を実装
  • RocksDBステートバックエンドでチェックポインティングを設定
  • 不正検知用CEPパターンを構築
  • ローカルYARNまたはKubernetesクラスターにFlinkジョブをデプロイ

第6ヶ月:ClickHouseと統合

第1-2週:ClickHouse

  • ClickHouseの構築とサンプルデータのロード
  • 適切なエンジン(MergeTree、ReplacingMergeTree)でテーブルスキーマを設計
  • KafkaエンジンとマテリアライズドビューによるKafka-ClickHouseパイプラインを構築
  • 分析クエリの作成とEXPLAINによる最適化

第3-4週:エンドツーエンドプロジェクトと面接準備

  • 完全パイプライン構築:PostgreSQL → Debezium → Kafka → Flink → ClickHouse
  • Grafanaでモニタリングダッシュボードを作成
  • プロジェクトの5分間アーキテクチャプレゼンテーションを準備
  • 30問の面接質問をパートナーと練習
  • 履歴書(りれきしょ)のレビューと改善

5. 履歴書戦略(りれきしょせんりゃく)

トスバンクが見たいもの

履歴書は1つの質問に答えるべきです:この人は我々のKafkaインフラを運用・進化させられるか?

影響(えいきょう)メトリクスをリードに

  • 「50ブローカーKafkaクラスターを運用、毎秒200万メッセージ処理、99.99%アップタイム」
  • 「Cooperativeリバランシングへの移行により、Consumerラグを10分から30秒未満に削減」
  • 「Debeziumを使用したCDCパイプライン構築でバッチETLを置換、データ鮮度(せんど)を6時間から5秒未満に改善」

運用成熟度(うんようせいじゅくど)を示す

  • オンコール経験、インシデント対応、ポストモーテム文化に言及
  • ダウンタイムゼロのアップグレードと移行プロジェクトを強調
  • 監視、アラート、キャパシティプランニングなどの地味だが重要な部分の理解を示す

Kafka以外の幅広さを見せる

  • Spring Boot SDK開発はソフトウェアエンジニアリングスキルを示す
  • FlinkまたはSparkの経験はデータ処理能力を示す
  • ClickHouseなどのOLAP経験は分析意識を示す
  • Kubernetesデプロイ経験は運用の多様性(たようせい)を示す

履歴書フォーマットのヒント

  • 最大2ページ以内
  • XYZ形式:「Yを実装してXを達成し、Zの結果を得た」
  • JDとの関連性順に技術を列挙
  • JDの技術スタックを反映する「テクニカルスキル」セクションを含める
  • 関連プロジェクトまたは貢献のあるGitHubプロファイルへのリンク

6. ポートフォリオプロジェクトアイデア

プロジェクト1:リアルタイム金融トランザクションパイプライン

金融トランザクション処理をシミュレートするエンドツーエンドのストリーミングパイプライン:

  • データソース:合成トランザクションイベントを生成するSpring Bootアプリケーション
  • Kafka:トランザクション、アラート、監査トピックのマルチトピックアーキテクチャ
  • Flink:リアルタイム集約と異常検知のストリーム処理
  • ClickHouse:リアルタイム分析ダッシュボード
  • 監視:KafkaとFlinkメトリクス用Prometheus + Grafana

プロジェクト2:マルチデータセンターKafkaレプリケーションラボ

Active-Activeレプリケーションの理解を実証:

  • Docker内で2つのKafkaクラスターを構築(2つのデータセンターをシミュレート)
  • 双方向レプリケーション用にMirrorMaker 2を設定
  • 両クラスターに書き込むProducerを実装
  • クラスター間でフェイルオーバーするConsumerを構築
  • フェイルオーバーとフェイルバック手順を文書化
  • 様々な負荷条件(じょうけん)でのレプリケーションラグを測定・報告

プロジェクト3:CDC駆動(くどう)イベントソーシングシステム

CDCによるイベント駆動コミュニケーションを使用するマイクロサービスシステム:

  • PostgreSQLデータベースを持つSpring Bootサービス
  • OutboxテーブルからのDebeziumキャプチャ
  • イベントバスとしてのKafka
  • イベントからマテリアライズドビューを構築する下流サービス
  • ソースとマテリアライズドビュー間の整合性を検証するリコンシリエーションツール

プロジェクト4:Kafka運用ツールキット

Kafkaオペレーターが実際に使用するツールの構築:

  • 最適なパーティション割り当てを提案するパーティションリバランシングアナライザー
  • アラート付きConsumerラグモニタリングダッシュボード
  • 基準からの逸脱(いつだつ)を検出するトピック設定オーディター
  • 登録前にAvroスキーマを検証するスキーマ互換性チェッカー

7. クイズ:知識(ちしき)をテスト

Q1:3ブローカークラスターでacks=allとmin.insync.replicas=2を設定。1ブローカーがダウンした場合、Produceリクエストはどうなりますか?

Produceリクエストは成功し続けます。3レプリカで1ブローカーがダウンすると、ISRに2レプリカが残ります。min.insync.replicas=2が満たされているため、Producerは確認応答を受け取ります。2番目のブローカーがダウンすると、ISRが1レプリカのみとなりmin.insync.replicas=2を下回るため、ProduceリクエストはNotEnoughReplicasExceptionで失敗します。

Q2:PostgreSQL用Debeziumコネクターが変更を受信しなくなりました。コネクターステータスは「RUNNING」を表示していますが、Kafkaに新しいメッセージが表示されません。最も可能性の高い原因は?

考えられる原因:(1) レプリケーションスロットが削除または非アクティブ — pg_replication_slotsを確認。(2) WALレベルがlogicalに設定されていない — wal_level設定を確認。(3) コネクターがスナップショットで停滞 — コネクタータスクステータスを確認。(4) Debeziumとデータベース間のネットワークパーティション。(5) フィルター変更によるテーブル除外。(6) PostgreSQLパブリケーションにターゲットテーブルが含まれていない。

Q3:Flinkジョブのチェックポイント時間が1週間で5秒から5分に増加。何が起きていて、どう修正しますか?

チェックポイント時間はステートサイズの増大に伴い増加します。考えられる原因:(1) TTLなしで蓄積するキー付きステート — ステートTTL設定を追加。(2) ステートバックエンドのローカルディスク不足 — ディスク増設またはインクリメンタルチェックポイントに切替。(3) RocksDBコンパクションの遅延 — RocksDB設定を調整。(4) チェックポイントストレージ(S3/HDFS)のスループットボトルネック。修正:インクリメンタルチェックポインティングを有効化、ステートTTLを設定、Flink UIでオペレーターごとのステートサイズを監視。

Q4:MirrorMaker 2を使用したActive-Active Kafkaセットアップで、無限レプリケーションループを防止する方法は?

MirrorMaker 2はレコードヘッダーのソースクラスターメタデータを使用してレプリケーションループを防止します。MM2がレコードをレプリケートする際、ソースクラスターを示すヘッダーを追加します。レコードが他のクラスターに到着しレプリケーション対象になると、MM2はヘッダーをチェックし、ターゲットクラスターから発生したレコードをスキップします。

Q5:削除ベースの保持からコンパクションにトピックをダウンタイムなしで移行する手順を説明してください。

手順:(1) 同じパーティション数でcleanup.policy=compactの新トピックを作成。(2) 旧トピックから読み取り同じキーで新トピックに書き込むKafka Streamsまたはconsumer-producerブリッジを構築。(3) ブリッジが旧トピックの末尾に追いつくまで待機。(4) Producerを新トピックへの書き込みに切替。(5) ブリッジのConsumer Groupで最後にコミットされたオフセットから新トピックを読み取るようConsumerを切替。(6) データ整合性を検証。(7) 猶予期間(ゆうよきかん)後に旧トピックを削除。


8. 参考資料(さんこうしりょう)とリソース

書籍(しょせき)

  1. Neha Narkhede, Gwen Shapira, Todd Palino — Kafka: The Definitive Guide, 第2版, O'Reilly, 2021
  2. Martin Kleppmann — Designing Data-Intensive Applications, O'Reilly, 2017
  3. Fabian Hueske, Vasiliki Kalavri — Stream Processing with Apache Flink, O'Reilly, 2019
  4. Robert Yokota — Event Streams in Action, Manning, 2019

公式ドキュメント

  1. Apache Kafka Documentation — https://kafka.apache.org/documentation/
  2. Confluent Platform Documentation — https://docs.confluent.io/
  3. Debezium Documentation — https://debezium.io/documentation/
  4. Apache Flink Documentation — https://flink.apache.org/docs/
  5. ClickHouse Documentation — https://clickhouse.com/docs/

コースとチュートリアル

  1. Confluent Developer — 無料Kafkaコース — https://developer.confluent.io/
  2. Stephane Maarek — UdemyのKafkaコース
  3. Apache Flink Training — https://nightlies.apache.org/flink/flink-docs-stable/docs/learn-flink/overview/
  4. ClickHouse Academy — https://clickhouse.com/learn

コミュニティと講演(こうえん)

  1. Kafka Summit録画 — https://www.kafka-summit.org/
  2. Flink Forward カンファレンス録画 — https://flink-forward.org/
  3. Martin Kleppmannのブログ — https://martin.kleppmann.com/
  4. Toss Tech Blog — https://toss.tech/
  5. Confluent Blog — https://www.confluent.io/blog/

まとめ

トスバンクのData Engineer(Kafka & Streaming)ポジションは、韓国フィンテックエコシステムの中で最も技術的に要求の高いデータエンジニアリングポジションの1つです。Kafkaの深い運用専門知識、SDK開発のソフトウェアエンジニアリングスキル、Active-Activeレプリケーションの分散システム知識、CDCとストリーム処理のデータパイプライン経験という稀(まれ)な組み合わせが求められます。

本ガイドの6ヶ月ロードマップは挑戦的(ちょうせんてき)ですが現実的です。Kafka基礎から始め、SDKスキルを早期(そうき)に構築し、その後高度なトピックを重ねていきます。ポートフォリオプロジェクトは面接で議論する具体的な成果物(せいかぶつ)を提供し、30問の練習問題は直面する可能性の高いトピックをカバーしています。

トスバンクは技術スキルと同等に運用の成熟度を重視していることを忘れないでください。午前3時のブローカー障害への対応、次四半期のキャパシティプランニング、重要トピックの安全な移行 — これらの議論はFlinkコードを書く能力と同じくらい重要です。

今日から勉強を始めましょう。パイプラインは誰も待ってくれません。