2.9K Views
February 05, 25
スライド概要
健康管理サービス「Kencom」のリアーキテクチャは、9年目のサービス継続と向上を目指す中で避けられないチャレンジでした。
本セッションでは、リアーキテクチャが必須となった背景を明らかにし、どのように技術的課題を克服してきたかを具体的な事例とともに紹介します。
また、段階的リリースの成功を支える戦略、そしてデータ移行や非同期処理における独自のアプローチについても説明します。
このセッションを通じて、類似のプロジェクトに取り組む皆様がプロジェクトを効率的に進行させるヒントを提供します。
◆ チャンネル登録はこちら↓
https://www.youtube.com/c/denatech?sub_confirmation=1
◆ X(旧Twitter)
https://x.com/denaxtech
◆ DeNA Engineering
https://engineering.dena.com/
◆ DeNA Engineer Blog
https://engineering.dena.com/blog/
◆ DeNA × AI Day ‖ DeNA TechCon 2025 公式サイト
https://techcon2025.dena.dev/
DeNA が社会の技術向上に貢献するため、業務で得た知見を積極的に外部に発信する、DeNA 公式のアカウントです。DeNA エンジニアの登壇資料をお届けします。
kencomリアーキテクチャの道のりと 技術的な取り組み 1 © DeNA Co., Ltd.
⾃⼰紹介 2 Yuehong Wang Ryoma Kawahara Naoji Mitsumoto 王 岳宏 川原 遼馬 滿本 尚士 © DeNA Co., Ltd.
サービス紹介 3 © DeNA Co., Ltd.
4 © DeNA Co., Ltd.
発表内容 ‧リアーキテクチャの背景 ‧リアーキテクチャにおける課題と解決策 ‧まとめ 5 © DeNA Co., Ltd.
リアーキテクチャの背景 6 © DeNA Co., Ltd.
リアーキテクチャの背景 ‧より広い顧客ニーズに応えるため、アカウント体系など、 サービスのコア部分の変更が必要 ‧しかし当時のシステムは⻑年のサービス運営により、 技術的負債が蓄積 ‧その状態でコア部分の機能変更‧追加を⾏うと、 さらなる開発⽣産性の低下や不具合のリスクが発⽣ 7 © DeNA Co., Ltd.
リアーキテクチャにおける課題と解決策 8 © DeNA Co., Ltd.
リアーキテクチャにおける課題と解決策 ‧サービス運⽤の課題 ‧アーキテクチャの課題 ‧技術的な課題 ‧開発体験の課題 9 © DeNA Co., Ltd.
サービス運⽤の課題と解決策 課題 ‧ユーザーが利⽤しており⻑期のサービス停⽌はできない ‧上記を踏まえて、どうリアーキテクチャを進めるか 解決策 ‧段階的リリース ‧システムメンテナンスの実施 10 © DeNA Co., Ltd.
段階的リリース ‧ビッグバンリリースではなく、段階的リリースにすることで メンテナンス時間を短縮 ‧リスクの極⼩化を考慮し、影響度の⼩さいシンプルな機能 から複雑な機能へ Phase1 ‧インフラ、アプリケーション基盤整備 ‧FAQ機能 ‧静的画⾯ Phase2 ‧お知らせ機能 ... 11 © DeNA Co., Ltd.
段階的リリース スコープ‧順序を精査し、スケジュールを決定 12 © DeNA Co., Ltd.
段階的リリース アーキテクチャを⾒直し(後述)、機能別に段階的リリースを実施 実施内容 ‧仕様整理 ‧DB‧API再設計 ‧ネイティブ‧フロント‧サーバー開発 ‧システムメンテナンスを挟みDB移⾏、機能リリース ‧ロードバランサーにより、対象機能のトラフィックを 旧システムから新システムに振り分け ‧サービス再開 13 © DeNA Co., Ltd.
ゼロダウンタイムリリースの検討 データの整合性担保や移⾏のコストを考慮し、ゼロダウンタイム ではなくシステムメンテナンスの実施を選択 14 © DeNA Co., Ltd.
システムメンテナンスの実施 ‧安全にデータを移⾏するため、外部及び内部からDB 書き込みが発⽣しないようアクセスを遮断 ‧デプロイ及びデータ移⾏後、動作確認のため内部向け にアクセスを許可 15 © DeNA Co., Ltd.
アーキテクチャの課題 旧アーキテクチャの問題 ‧複数モノリス ‧複数マイクロサービス ‧複数リポジトリ ‧構成複雑、運⽤コスト⾼ 16 © DeNA Co., Ltd.
アーキテクチャの課題 旧アーキテクチャの問題 ‧機能が複数サービスに分散 ‧ロジックが複数layerに分散 ‧modelの相互依存 ‧認知負荷⾼ 17 © DeNA Co., Ltd.
アーキテクチャの課題 旧アーキテクチャの問題 ‧内部管理システム複数存在 ‧別々の認証⽅法 ‧複雑な運⽤ 18 © DeNA Co., Ltd.
新アーキテクチャ 19 © DeNA Co., Ltd.
新アーキテクチャ 統⼀ ‧monorepoによりフロントとサーバーのコードを統⼀管理 ‧モジュラーモノリスで全機能を統⼀デプロイ ‧内部管理システムは⼀つに統合 20 © DeNA Co., Ltd.
新アーキテクチャ 責務の分離 ‧モジュールごとに機能とDB schemaを分離 ‧モジュールはビジネスロジック実現 ‧bffとapi gatewayはビジネスロジック実現しない 21 © DeNA Co., Ltd.
新アーキテクチャ 責務の分離 ‧モジュール内でレイヤーを分離 ‧参考:MonorepoとOneTeamで Microservicesの課題に挑む 22 © DeNA Co., Ltd.
新旧システムの共存 新システムの構成 ● GCP採⽤、フルマネージド サービス活⽤ ● AWSとGCP間に内部通信構築 ● TerraformによるIaC 23 © DeNA Co., Ltd.
リアーキテクチャ後のシステム構成(想定) 24 © DeNA Co., Ltd.
アーキテクチャの課題と解決策 まとめ ‧複数モノリス、マイクロサービスをモジュラーモノリスに移⾏ ‧認知負荷が減り、開発速度が向上 ‧内部管理システムの統合 ‧オペレーションの複雑度を低減 ‧GCPのフルマネージドサービス活⽤、Terraform導⼊ ‧インフラ運⽤コストの低減 25 © DeNA Co., Ltd.
技術的な課題と解決策 26 © DeNA Co., Ltd.
技術的な課題と解決策 今回話す内容 ‧機能間のデータ整合性担保 ‧⾼速かつ安全なデータ移⾏ その他にも ‧データの暗号化 ‧新旧システムのネットワーク構築 ‧バッチ処理 ... 27 © DeNA Co., Ltd.
機能間のデータ整合性担保 28 © DeNA Co., Ltd.
背景 ‧イベント駆動で機能間データ連携 ‧分散トランザクションを回避 ‧結果整合性を確保 29 © DeNA Co., Ltd.
課題 ‧DB永続化後、Message/Task Queueへのイベント送信失敗 ‧機能間データ不整合‧処理漏れが発⽣ 30 © DeNA Co., Ltd.
以前の対策と課題 ‧バッチ処理で検知‧⾃動補填 ‧⼈⼒で本番調査‧⼿動対応 ‧複雑、⾼コスト、継続困難 31 © DeNA Co., Ltd.
リアーキテクチャの対策 ‧Outboxパターン導⼊ ‧仕組み上で根本対応 32 © DeNA Co., Ltd.
Outboxパターンの原理 ‧業務データとイベントメッセージを同 ⼀トランザクションでDB永続化 ‧⾮同期でメッセージをMessage/Task Queueに転送 33 © DeNA Co., Ltd.
⾮同期転送の⼀般的な実現 ‧DBポーリング(Polling) ‧DB CDC(Change Data Capture) 34 © DeNA Co., Ltd.
kencomのやり⽅ ‧ポーリング、CDC⽅式を採⽤せず ‧独⾃⼿法で⾮同期転送を実現 35 © DeNA Co., Ltd.
コンポーネント構成 ‧Dispatcher: DB更新後、メッセージをworkerに転送 ‧Worker: メッセージを送信 ‧Rescuer: 送信失敗のメッセージを後から再送 36 © DeNA Co., Ltd.
Dispatcher ‧DBコミット後 ‧⾮同期でメモリキュー経由で メッセージをWorkerに転送 ‧キューが満杯の場合は即時ス キップ 37 © DeNA Co., Ltd.
Worker ‧Dispatcherと同じプロセスで稼働 ‧メモリのキューからメッセージを取得 ‧送信後にDBからメッセージを削除 ‧送信失敗時は即時スキップ ‧送信タイムアウト時も即時スキップ 38 © DeNA Co., Ltd.
Rescuer ‧定期バッチで起動 ‧送信失敗‧スキップされたメッセージを 救済 ‧⼀定時間以上未送信のメッセージをDB から取得して再送 39 © DeNA Co., Ltd.
kencom Outboxの特徴 ‧特定のDB機能やツールに依存しない、汎⽤性⾼ ‧転送はDBポーリングやDB取得処理が不要、DB負荷低 ‧goroutine+channel活⽤で実装容易、サーバー負荷低 ‧Rescuerは並列処理せず、競合対策不要で簡単 40 © DeNA Co., Ltd.
トレードオフ ‧スキップや送信失敗時、再送による遅延増加 ‧事前に発⽣頻度を把握、許容できると判断 41 © DeNA Co., Ltd.
導⼊効果 ‧Outbox経由での送信件数: 数百万件/⽇ ‧数百万回のDBポーリング‧ロック削減 ‧Rescuerでの再送件数: 数件〜数⼗件/⽇ ‧本番障害を未然防⽌ 42 © DeNA Co., Ltd.
イベント消費処理の注意点 43 © DeNA Co., Ltd.
イベント遅延の発⽣ ‧Outbox⾮同期転送処理での障害‧遅延 ‧Message/Task Queue⾃体の障害‧遅延 ‧消費側の処理失敗‧再試⾏ 44 © DeNA Co., Ltd.
イベント順序の⾮保証 ‧Outboxの再送により順序変動 ‧Message/Task Queueの順序保証は仕様依存 ‧消費側の並⾏実⾏により順序が乱れる ‧消費側の失敗‧再試⾏で順序が変わる 45 © DeNA Co., Ltd.
イベント重複送信‧同時送信 ‧Outboxで応答確認(ACK)失敗により同⼀イベント再送 ‧Message/Task Queueの1回限り配信は仕様依存 ‧消費処理完了ACK失敗により再送発⽣ ‧重複送信と並列消費により同⼀イベントが同時に処理開始 46 © DeNA Co., Ltd.
冪等性担保の消費処理が必要 47 © DeNA Co., Ltd.
まとめ ‧Outbox導⼊により機能間整合性を根本的に確保 ‧DB負荷軽減、運⽤コスト削減、障害予防を実現 ‧冪等性担保の消費処理ロジックが必要 48 © DeNA Co., Ltd.
kencomにおける数億レコードの ⾼速かつ安全なデータ移⾏ 49 © DeNA Co., Ltd.
kencomのデータ移⾏構成図 50 © DeNA Co., Ltd.
kencomのリリースとデータ移⾏ ポイントのデータ移⾏について 詳しく紹介します 51 © DeNA Co., Ltd.
kencomのデータ移⾏スケジュール 52 © DeNA Co., Ltd.
kencomにおける数億レコードの ⾼速かつ安全な移⾏ 53 © DeNA Co., Ltd.
数億レコードの⾼速な移⾏ ● ポイント取引履歴 ● ポイント⾃動付与履歴 ● など の数億レコードあるテーブル 過去実績から単純計算で10 h以上 →停⽌リリースの時間増⼤の問題 54 © DeNA Co., Ltd.
数億レコードの⾼速な移⾏ 55 © DeNA Co., Ltd.
数億レコードの⾼速な移⾏ 56 © DeNA Co., Ltd.
goroutineを⽤いた実装の課題 57 © DeNA Co., Ltd.
goroutineを⽤いた実装の課題
● 同じような実装の実装コス
ト
● バグ発⽣の可能性
58
// 旧テーブルから取得し続けるgoroutineを起動
eg.Go(func() error {
defer close(ch)
return c.legacyRepo.StreamMigrated(ctx, ch)
})
// データを受け取ってBulk Insertするgoroutineを起動
for i := 0; i < parallels; i++ {
eg.Go(func() error {
// 溜まったらDBに保存する処理
})
}
© DeNA Co., Ltd.
goroutineを⽤いた実装の課題 59 © DeNA Co., Ltd.
goroutineを⽤いた実装の課題
● データ取得
● データ保存
の関数を定義して、ライブラ
リに渡せば、並列処理を実⾏
してくれる
60
err := util.StreamForkBuffer(
ctx,
streamIn, // データ取得処理関数
parallels,
process, // データ保存処理関数
)
if err != nil {
return fmt.Errorf("util.StreamForkBuffer: %w", err)
}
© DeNA Co., Ltd.
goroutineを⽤いた実装の課題
// 旧テーブルから取得し続けるgoroutineを起動
eg.Go(func() error {
defer close(ch)
return c.legacyRepo.StreamMigrated(ctx, ch)
})
// データを受け取ってBulk Insertするgoroutineを起動
for i := 0; i < parallels; i++ {
eg.Go(func() error {
// 溜まったらDBに保存する処理
})
}
err := util.StreamForkBuffer(
ctx,
streamIn, // データ取得処理関数
parallels,
process, // データ保存処理関数
)
if err != nil {
return fmt.Errorf("util.StreamForkBuffer: %w", err)
}
61
© DeNA Co., Ltd.
COPY FROMコマンドの活⽤ ● 数千万レコード:Bulk Insertで問題なかった ● 数億レコード:データ移⾏の時間が⻑い ⼿元環境で5000万レコードを Bulk Insert VS COPY FROM で1/5の時間短縮になった 62 © DeNA Co., Ltd.
COPY FROMコマンドの活⽤ "github.com/jackc/pgx/v5" の実装があったので これを活⽤した 63 _, err := tx.CopyFrom( ctx, pgx.Identifier{tableName}, columns, pgx.CopyFromRows(rows), ) © DeNA Co., Ltd.
数億レコードとの戦いの結果 ● 並列処理 ● COPY FROM の活⽤で 10 h → 3.5 h 64 © DeNA Co., Ltd.
kencomにおける数億レコードの 安全な移⾏ ⾼速かつ 65 © DeNA Co., Ltd.
kencomのデータ移⾏スケジュール 66 © DeNA Co., Ltd.
テーブルのデータ検証 テーブル設計も変更したので、⼊念な検証をしないと 障害発⽣の可能性 67 © DeNA Co., Ltd.
⼀テーブル → ⼀テーブル ● 旧テーブルのIDをlegacy_idとして保存 ● データ変換の際にドメイン層のvalidation 68 © DeNA Co., Ltd.
⼀テーブル → 複数テーブル 69 © DeNA Co., Ltd.
複数テーブル → ⼀テーブル 70 © DeNA Co., Ltd.
データ移⾏の安全性を⾼める取り組み 71 © DeNA Co., Ltd.
数億レコードのテーブルのデータ検証 通常テーブルだと新旧DBの ● 件数チェック ● フルスキャンしてチェック 数億レコードテーブルでフルスキャンのチェックは 数時間かかる計算になった 72 © DeNA Co., Ltd.
TABLESAMPLE句の活⽤ ● ランダムサンプリングしてSELECT可能 ○ BERNOULLI: フルスキャンしてランダムサンプリング ○ SYSTEM: 物理ブロック単位でランダムサンプリング ● REPEATABLE(シード値) ○ シード値固定することで、毎回同じ結果セットが 得られる ○ →分割取得に利⽤ 73 © DeNA Co., Ltd.
数億レコードのテーブルのデータ検証 Cursorパターンと TABLESAMPLE句を組み合 わせ SELECT * FROM hoges TABLESAMPLE SYSTEM(5) REPEATABLE(1) WHERE id > 53434 LIMIT 10000 1回の取得を1万件にしつつ 数百万件のレコードを検証 した 74 © DeNA Co., Ltd.
数億レコードの⾼速かつ安全な移⾏ ⾼速性 ● 並列処理のパッケージ化 ● COPY FROMコマンド 安全性 ● TABLESAMPLE句 数億レコードを4.0 hで安全に移⾏/検証 75 © DeNA Co., Ltd.
まとめ 76 © DeNA Co., Ltd.
達成したこと(アーキテクチャ) ● 複数モノリス、マイクロサービスで⾼い認知負荷 ○ 型安全な⾔語への統⼀ ○ レイヤーの責務の明確化 ○ 開発速度の向上 ● ⾮同期処理のデータ不整合性 ○ outbox pattern 77 © DeNA Co., Ltd.
達成したこと(インフラ) ● 複雑なインフラ構成により、別チームに依存 ○ Terraform管理でリードタイム短縮 ○ マネージドサービスで⼈件費の削減 ○ スペック可変なサービスでインフラコスト削減 78 © DeNA Co., Ltd.
今後の展望 将来的なマイクロサービス化を強く意識したモジュラモノリ ス構成になっているため、以下のようなシンプルな 構成を検討したい。 ● 通信:モジュール間のgRPC通信廃⽌し、単純なメソッ ドコールに切り替え ● デプロイ:gatewayとモジュール群をまとめてデプロイ 79 © DeNA Co., Ltd.