こんにちは、SCSK林です!
モダンなシステムアーキテクチャにおいて、システム間を「疎結合」に保つことはもはや定番です。AWSにおいてその中心を担うのは、Amazon SQSやAmazon Managed Streaming for Apache Kafka (MSK)といったメッセージングサービス、あるいはAmazon S3を用いたバッファ層などかと思います。
ただ、実際のエンタープライズ領域におけるデータ連携案件、特にマルチクラウド構成やオンプレミスとの閉域網接続が絡むプロジェクトでは、単に「サービスを間に挟む」だけでは解決できない課題が多いと感じています。
「どのタイミングでデータの到達を保証すべきか」
「コストとスループットの妥協点をどこに置くか」
「リトライによって発生するデータの重複をどう制御するか」
本記事では、私が携わった、毛色の異なる2つのデータ連携プロジェクトを例に、アーキテクトが直面する「キューイング・バッファリング設計」のポイントについて解説していきたいと思います。
プロジェクト事例①:異種クラウド間連携における「Pull型」MSK設計
プロジェクトの背景と課題
最初にご紹介するのは、AWS上の基幹システムで発生する変更データを、Google Cloud上のDWH基盤(BigQuery)へリアルタイムに同期する案件です。
AWS側ではデータのハブとしてAmazon MSKを採用していました。当初の検討では、MSK Connectを利用してGoogle Cloud側のエンドポイントへデータをPush送信する構成が有力でした。しかし、精査を進めると以下の3つの大きな課題が浮上しました。
- ネットワークの不確実性: AWSからGoogle Cloudへのクロスクラウド通信、かつ専用線経由という環境下で、ネットワーク瞬断時のエラーハンドリングをどこまでインフラ層に任せられるか。
- コスト効率の悪化: 同期対象となるインターフェース(Topic)は20個以上存在しました。MSK Connectはコネクタ単位でのMCU(MSK Connect Unit)課金が発生するため、1日の流量が数千件程度の比較的小規模なインターフェース群に対して個別にコネクタを立てると、データ量に対して極めて割高な固定費が発生します。
- 責任分界点の曖昧さ: 送信側(AWS)が無理に押し込む「Push型」では、受信側(Google Cloud)の負荷状況に合わせた流量制御(バックプレッシャー)が難しく、受信失敗時の再送管理が複雑化します。
独自コンシューマーによる「Pull型」への転換
結果的には、マネージドサービスであるMSK Connectの採用を見送り、Google Cloud側のCloud RunからMSKへ「Pull型」でデータを取得しに行くカスタムコンシューマー構成を提案しました。
この設計は、「責任完了のポイント」をコンシューマー側に移譲したことにあります。
- 同期的なオフセット管理 : コンシューマーはMSKからメッセージを取得し、Google Cloud側のストレージ(Pub/Sub)への書き込みが完全に完了したことを確認してから、MSKに対して「Offset」をコミットします。これにより、処理の途中でコンシューマーがダウンしても、次回の起動時に未処理のデータから確実に再開できる「At-least-once(少なくとも一回)」を担保しました。
- コストの最適化 : 20個以上の多くのインターフェースを単一または少数のCloud Runサービスに集約して処理することで、MSK Connectを利用した場合と比較してインフラコストを大幅に抑制しました。
- 重複排除の戦略的妥協 : At-least-once構成では、再送時にデータの重複が発生する可能性があります。これをインフラ層の複雑なロジックで排除しようとせず、最終的な格納先であるGoogle Cloud側(BigQuery)で、ユニークキーに基づいた「重複排除処理」を行う方針を策定しました。
技術的な「完璧さ」をインフラだけで追求するのではなく、エンドツーエンドでの整合性とコストのバランスを考慮した構成になっているかなと感じています。
※詳細はこちらのブログも参照ください。
プロジェクト事例②:S3を「バッファ」と見立てた高耐久非同期アーキテクチャ
プロジェクトの背景と課題
次にご紹介するのは、オンプレミス環境からAWSを経由し、データウェアハウスであるSnowflakeへデータをロードする基盤構築案件です。
この案件では、Direct Connect経由で送られてくるデータをAPI Gateway + Lambdaで受け取る構成をとりましたが、以下の制約が障壁となりました。
- Lambdaのペイロード制限 : API GatewayおよびLambdaには数MBから数十MBのペイロード制限があり、将来的なデータ肥大化に対応できない懸念がありました。
- Snowflakeへのロード遅延 : Snowflakeへの書き込み処理には、オーバーヘッドを含めて一定の時間が必要です。同期的な処理では、APIのタイムアウトや、オンプレミス側のクライアントを長時間待機させるリスクがありました。
- 性能要件の遵守 : 「データ発生から3分以内に分析可能にすること」という性能要件に対し、単一のプロセスで全てを完結させるのは可用性の観点から危険だと判断しました。
S3を「高耐久なキュー」として定義
私は、Amazon S3を単なるストレージではなく、「書き込みが極めて高速で、無限のキャパシティを持つキュー(バッファ)」として位置づける非同期アーキテクチャを採用しました。
- 取り込み層(受領)の軽量化: API Gatewayから起動されるLambdaの役割を「S3へのファイル保存」のみに限定しました。これにより、オンプレミス側に対しては数ミリ秒から数百ミリ秒という極めて短いレスポンスタイムで「受領完了」を返せます。
- ロード層(処理)のデカップリング: S3へのファイル作成をトリガー(S3 Event Notifications)として、後続のLambdaがSnowflakeへのロードを実行します。この構成により、Snowflake側で一時的なメンテナンスや障害が発生しても、データはS3に「滞留(キューイング)」されるだけであり、取り込み層を止める必要がなくなります。
- 枯れた技術による信頼性: Snowflakeへのロードには、あえて最新のSnowpipeではなく「LambdaによるCOPYコマンド実行」を選択しました。これは既存の資産であるシェルスクリプトのロジックを流用しやすくするためであり、またエラー時の再実行制御をより細かくハンドリングできるようにするためでした。
結果としてのパフォーマンス
この「S3バッファ」を介した非同期構成により、結果としてデータ発生からSnowflakeへの到達まで、平均して十数秒というパフォーマンスを実現しました。目標としていた「3分以内」という性能要件を大幅に上回る余裕を持った設計となりました。
※詳細はこちらのブログも参照ください。
まとめ:キューイング設計における3つのポイント
これら2つの案件を通じて、痛感した「キューイング設計のポイント」は以下の3点に集約されると感じました。
① 責任分界点(Commit Point)をどこに置くか
「データを受け取った」とみなすタイミングをどこにするかは、システムの信頼性を左右する最も重要な決断です。事例①では、宛先システムが処理を終えたタイミング。事例②では、AWS側の高耐久ストレージ(S3)に書き込んだタイミング。 これを明確に定義することで、障害発生時に「どこからリトライすべきか」が自ずと決まります。
② マネージドサービスとカスタム実装の天秤
マネージドサービスの利点は十分に理解していますが、事例①のように「インターフェース数が多いが、個々の流量が少ない」といった特殊な条件下では、マネージドサービスのコスト構造がボトルネックになることがあります。 「何でもマネージド」ではなく、ランニングコストと運用負荷(メンテナンス性)を天秤にかけ、時にはカスタムコンシューマー(手組のプログラム)を選択する勇気も必要です。
③ 冪等性の確保
キューイングを導入する以上、リトライによる「重複」は避けられません。インフラ側で「Exact-once(正確に一回)」を実現しようとすると、アーキテクチャは極めて複雑になり、パフォーマンスも低下します。 「重複は発生するもの」と割り切り、アプリケーション層やデータベースのレイヤーで重複排除を行う設計にすることで、システム全体の堅牢性とシンプルさを両立させることができます。
おわりに
AWSはじめ各クラウドサービスには、データ連携を支える強力なサービス群が揃っています。しかし、それらを組み合わせるだけで優れたシステムが出来上がるわけではないと改めて感じました。
今回の事例では、「あえてマネージドサービスを使わない」「あえて非同期にする」といった、ある種のデザインチョイス(選択と集中)です。ビジネス要件、コスト制約、そしてネットワークの物理的な限界を直視し、どこで妥協し、どこで妥協するか。その判断こそが難しいポイントだなと思いました。
今回の構成、事例がどなたかのお役に立つと幸いです。


