Azure Event Hubs を Kafka 互換プロトコルで利用しながら、プライマリ/セカンダリ接続文字列を併用して「無停止でキーをローテーションしたい」。この要件は多くの現場で求められます。ここでは、重複配信の正しい理解、鍵の役割、ゼロダウンタイム運用手順、そして高可用性設計の勘所を、実践的な検証観点とともに整理します。
前提と構成の整理
想定構成は次のとおりです。
- 入力用 Event Hub(以降「入力EH」)から Kafka 互換プロトコルを使って、出力用 Event Hub(以降「出力EH」)へルーティング。
- 入力側アプリ(プロデューサ/コネクタ)が
bootstrap.serversに同一のホストを指定し、プライマリ接続文字列とセカンダリ接続文字列の “2本” を持つ。
接続文字列と Shared Access Policy の関係
Event Hubs の「プライマリ/セカンダリ接続文字列」は、同一の Shared Access Policy(SAS ポリシー)にぶら下がる、2組の鍵(SharedAccessKey)です。認可主体(SharedAccessKeyName)は共通で、鍵だけが 2本用意されています。
| 項目 | 例 | 意味 |
|---|---|---|
| Namespace | contoso-ehns.servicebus.windows.net | Event Hubs 名前空間 |
| EntityPath | input-hub | Event Hub 名(省略可) |
| SharedAccessKeyName | send-policy | ポリシー(認可主体)名 |
| Primary/Secondary Key | 長い Base64 文字列 | 同一ポリシーに対する2本の鍵 |
| Connection String | Endpoint=...;SharedAccessKeyName=...;SharedAccessKey=... | クライアントがパスワードとして使う文字列 |
よくある疑問への結論
「両キーで同時送信すれば2重に届くはず」— なのに1件しか届かない理由
同じ bootstrap.servers(= 同一名前空間の Kafka 互換エンドポイント)に対し、同一ポリシーのプライマリ/セカンダリを「併用」しても、多くの Kafka クライアントは実装上、実際に使う資格情報は1系統に収束します。たとえば設定の最後に読み込まれた片方が有効になったり、同一クライアントインスタンスに複数の JAAS を同時適用できなかったりします。そのため、“2回送ったつもり”でも物理的には1回しか送っていないケースが大半です。
重要なのは、Event Hubs 側で重複除去をしているわけではないという点です。送信が1回しか実行されていないので1件しか届かない、というのが観測の正体です。
「Event Hubs が重複検出して落としている?」— いいえ、基本はしません
Event Hubs はブローカー側での自動重複排除(exactly-once の保証)を前提にしていません。リトライやネットワーク揺らぎ等で重複が起き得る(at-least-once)前提のサービスです。Kafka 互換エンドポイントであっても、ブローカー実装は Kafka と同一ではないため、サービス側の重複排除を期待しない設計が基本です。
「高可用性の観点でこの構成は正しい?」
“2本の鍵で同時送信” は高可用性には寄与しません。プライマリ/セカンダリは冗長回線ではなく、無停止のキー・ローテーション(片方ずつ更新)と障害時の切替のために用意されています。可用性を高めるのは、クライアント冗長化(多インスタンス・多ゾーン)やバックオフ&リトライ、監視/自己回復ロジックです。
観測パターンと結果の早見表
| クライアント構成 | 資格情報 | 挙動 | 結果 |
|---|---|---|---|
| 単一クライアントに両キーを「同時設定」 | 同一ポリシーの Primary/Secondary | 実装上どちらか一方だけが有効 | 送信は1回、重複せず |
| 別プロセス/別クライアントを2つ起動 | 片方が Primary、もう片方が Secondary | それぞれ独立送信 | 重複が発生(当然) |
| 出力側でシンク(プロデューサ)を2系統 | 鍵の違いに関係なし | 両方から送信 | 重複が発生(当然) |
| 片方の鍵を更新しながらもう片方で運用 | 同一ポリシー | 切替時のみ接続再確立 | 無停止ローテーション |
鍵の役割と正しいメンタルモデル
- プライマリ/セカンダリは「同一ポリシーの2本の鍵」。どちらを使っても認可主体(
SharedAccessKeyName)は同じです。 - 2本がある理由は、片方ずつ再生成(ローテーション)するため。両方同時に更新すると当然ながら全接続が無効になります。
- 「別回線」「別プロデューサ ID」にはなりません。鍵を増やしても論理的な分離は生まれないと理解してください。
ゼロダウンタイムで実施するキー・ローテーション手順
現場で安定して機能する“ブルー/グリーン鍵ローテーション”の具体手順です。
| 手順 | アクション | 目的 | ポイント |
|---|---|---|---|
| 1 | アプリに両方の接続文字列を設定(Primary/Secondary) | 切替準備 | 設定は環境変数・Key Vault 参照など外出し。起動時は Primary を優先利用、障害時に Secondary へフォールバック。 |
| 2 | Primary Key を再生成 | 鍵の更新 | 稼働中の接続は Secondary で継続。無停止。 |
| 3 | アプリ設定を更新して新しい Primary を使用 | 新鍵への移行 | ロール再起動やホットリロードで切替。メトリクスでドロップ/スロットリングを監視。 |
| 4 | Secondary Key を再生成 | 残鍵の更新 | 最後にバックアップ側を更新して完了。両鍵を同時に更新しない。 |
ローテーションの実務 Tips
- 鍵の保管は Key Vault 等で管理し、秘密情報のデプロイなしで差し替えできるようにする。
- アプリ側は再認証リトライ(401/403 相当のエラー時)と指数バックオフを実装しておく。
- 監視は Incoming Requests、Throttled Requests、Server Errors、Oldest Event Age を重点確認。
- 切替の健全性は「パーティション別スループット」「遅延」「エラー率」で判定。
Kafka クライアント設定例(PLAIN 認証 + 接続文字列)
# producer.properties
bootstrap.servers=contoso-ehns.servicebus.windows.net:9093
security.protocol=SASL_SSL
sasl.mechanism=PLAIN
# JAAS 内の username は固定で "$ConnectionString"
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
username="$ConnectionString" \
password="Endpoint=sb://contoso-ehns.servicebus.windows.net/;SharedAccessKeyName=send-policy;SharedAccessKey=${EH_PRIMARY_KEY};EntityPath=input-hub";
# 運用上推奨
acks=all
retries=2147483647
delivery.timeout.ms=120000
max.in.flight.requests.per.connection=1
linger.ms=20
batch.size=131072
client.id=producer-app-a
フォールバックのために、起動時に Primary が不可なら Secondary で再試行する簡易ロジックを入れておくと切替が安全です。
// 疑似コード(例:Java)
Properties p = loadBaseProps();
try {
p.put("sasl.jaas.config", jaasWith(primaryConnStr));
KafkaProducer<K, V> producer = new KafkaProducer<>(p);
healthCheck(producer);
return producer;
} catch (AuthException e) {
log.warn("Primary 認証失敗、Secondary へフォールバック");
p.put("sasl.jaas.config", jaasWith(secondaryConnStr));
KafkaProducer<K, V> producer = new KafkaProducer<>(p);
healthCheck(producer);
return producer;
}
Azure CLI での鍵再生成(参考)
# Primary Key を更新(Secondary で運用継続中)
az eventhubs namespace authorization-rule keys renew \
--resource-group rg-contoso \
--namespace-name contoso-ehns \
--name send-policy \
--key PrimaryKey
# 切替後、Secondary Key を更新
az eventhubs namespace authorization-rule keys renew
--resource-group rg-contoso
--namespace-name contoso-ehns
--name send-policy
--key SecondaryKey
重複を「意図的に」発生させたい/検証したい場合
鍵を2本使うだけでは論理的な送信主体は変わりません。重複の検証は次のいずれかで行います。
- 別プロセスのプロデューサを2つ立ち上げ、それぞれに別の鍵(あるいは同じ鍵)を割り当てる。
- 異なる Shared Access Policy を作成し(例:
send-aとsend-b)、クライアントを分ける。 - 別パーティションまたは別の Partition Key を用いて送信経路を分離する。
いずれも「クライアントが2つある」ことが本質であり、鍵の違い自体は重複の原因ではありません。
出力側で重複が発生した理由の整理
出力EHに対し、シンク(プロデューサ)を2系統で構成すると、それぞれ独立したクライアントとして送信するので重複は当然発生します。ここでも「鍵の違い」は本質ではありません。クライアントが2つあるかどうかが重複の有無を決めます。
Event Hubs と重複制御の実務設計
アプリケーションは at-least-once を前提に冪等(べきとう)性を確保しましょう。
| 戦略 | 方法 | ポイント |
|---|---|---|
| メッセージに一意の ID | messageId、ソースのオフセット、ハッシュ | シンク側で「既処理 ID」を記録し二重適用を防止 |
| Upsert(同一キーの更新は上書き) | データストアで MERGE/UPSERT | 二重書き込みでも最終結果が1回分に収束 |
| 順序付け + シーケンス | パーティションキー + 連番 | 欠番や逆順を検出しやすくする |
| 再処理に耐える設計 | 副作用を外部化/トランザクション境界の最小化 | リトライやバッチ再投入に強くなる |
高可用性を高める設計パターン
- クライアント冗長化:プロデューサ/コンシューマを複数インスタンス化し、可用性ゾーンをまたいで配置。
- 障害時フェイルオーバー:認証失敗や接続ドロップ時に Secondary に自動切替。回復後に Primary へフェールバック。
- リトライ・バックオフ:スロットリング(429)や一時障害に対して指数バックオフ。ジャムを作らない。
- 構成の外部化:接続文字列は環境変数/Key Vault。ロール再起動のみで鍵差し替えが完了する形に。
- 監視と自動復旧:エラー率増加でヘルスチェックを落とし、オーケストレータが再起動/別ノードに移す。
運用チェックリスト
| タイミング | チェック | 合格基準 |
|---|---|---|
| 事前 | 両鍵の所在・有効性、Key Vault 参照権限、再起動手順 | ドキュメント化済み/自動化済み |
| 切替直前 | Secondary での接続テスト(ステージングで可) | 送信・消費が成功、遅延の増悪なし |
| 切替中 | ドロップ・スロットリング・再接続回数 | 短時間で収束、データ欠損なし |
| 切替後 | 受信レイテンシ/バースト、Oldest Event Age | ベースラインに復帰 |
よくある落とし穴
- 両鍵を同時に再生成:全接続が失効してダウン。片方ずつ、順番に。
- 長期キャッシュされた SAS トークン:SAS Token を自前生成して長時間キャッシュすると切替に追従しない。原則「接続文字列→ライブラリ任せ」で。
- JAAS の取り違え:
usernameは$ConnectionString固定、passwordに接続文字列を入れるのが定石。 - EntityPath の混同:接続文字列に
EntityPathを入れる場合と入れない場合でトピック指定の仕方が変わる。運用で統一する。
質問への明快な回答(要点)
- 疑問1:「両キーで同時送信すれば2重に届くはず」→ 実際はクライアントが片方の資格情報しか使っておらず送信は1回。重複除去が働いたわけではない。
- 疑問2:Event Hubs が重複を落としている? → いいえ。サービス側で自動重複排除を前提にしない。重複は設計で吸収する。
- 疑問3:この構成は高可用? → 鍵の併用は HA にならない。プライマリ/セカンダリは無停止ローテーションとフェイルオーバーのための“バックアップ鍵”である。
運用例(実践ステップ)
| 手順 | 目的 | ポイント |
|---|---|---|
| 1 | セカンダリキーでアプリを待機運用 | 両方の接続文字列を設定しておくと切替が容易 |
| 2 | プライマリキーを再生成 | セカンダリで通信継続のため無停止 |
| 3 | アプリ設定を更新し新しいプライマリへ | 動作確認後フェールバックも容易 |
| 4 | セカンダリキーを再生成 | 両鍵の更新が完了しローテーション終了 |
要点:プライマリ/セカンダリは「鍵のバックアップ」であり「別回線」ではありません。ゼロダウンタイムでローテーションするには、片方ずつ鍵を更新し、常に少なくとも1本は有効に保つこと。
トラブルシューティングの実践手順
- クライアントの送信実績を確認:アプリログで「どの JAAS(どの鍵)で接続したか」「送信カウント」を突合。
- ネットワーク/認証エラーの分解:TLS 失敗、SASL 失敗、スロットリングを分離して把握。
- 重複検知の仕掛けを一時有効化:出力先で messageId の重複率を計測し、切替時の挙動を可視化。
- 再現テスト:別プロセスのダミープロデューサを 2 本立てて重複が生じることを確認(鍵の差ではなくクライアント数が要因だと体感する)。
セキュリティと将来拡張
- 最小権限のポリシー:送信専用には
Sendのみを付与したポリシー、受信専用にはListenのみ。 - ポリシー分割:チーム/ワークロードごとにポリシーを分けると、鍵漏えい時の影響半径を限定できる。
- 認証方式の抽象化:将来の AAD(OAuth)移行やマネージド ID への切替を視野に、アプリから認証機構を疎結合に。
まとめ
- プライマリ/セカンダリは同一ポリシーの2本の鍵。冗長回線ではない。
- 「両キー同時送信」ではふつう1回しか送られない。それはクライアント実装の挙動であり、Event Hubs が重複を捨てているわけではない。
- ゼロダウンタイムの鍵更新は片方ずつ更新し、常に1本は有効に保つ。
- 高可用性はクライアント冗長化・リトライ・監視で担保する。鍵の併用は HA ではない。
- 重複を扱うのはアプリの責務。冪等性を確保し、再処理に強いパイプラインを設計する。

コメント