DMM.comの、一番深くておもしろいトコロ。

プロダクト改善を支えるため商品データベースを分割している話

プロダクト改善を支えるため商品データベースを分割している話

  • このエントリーをはてなブックマークに追加

はじめに

こんにちは。DMM.comのEC&デジタルコンテンツ本部 テックリード室に所属している新村(しんむら)です。社内の事業横断プロジェクトのPMや社内のチームへの支援を担当しています。

本記事ではDMMの商品データベース(以降、商品DBと記述します)が抱えていた課題と、解決のために行ったアプローチ、プロジェクトについて説明します。

商品DBとは

DMMで古くから提供しているいくつかのサービス 1 の商品と、商品に関連する様々なデータを持ったデータベースで、オンプレミス環境に構築されています。

2000年代に構築され、2022年現在も現役で稼働しています。いくつかのテーブルを確認してみると、最古と思われるレコードのtimestampは2004年でした。

これまでの会社の成長・サービス拡充を支え続けてきており、構築からの歴史の長さ・データベースの巨大さは、私が知る限りDMM内でトップクラスです。

商品DBが抱えていた課題

前項で触れた通り、複数のサービスで一つのDBを利用していました。これはサービス拡張・機能追加を最速で実現するために必要でしたが、それが長年積み上がったことで、現在のシステム・組織にとって歪みや課題を抱えることになりました。

大量のReadReplica 2

利用しているサービスそれぞれを多くのユーザ様に利用いただいており、それに応じてDBサーバのスケールアウトを重ねてきた結果、3桁台数のReadReplicaが稼働しています。

Primaryは冗長構成になっているとはいえ、台数規模も小さくないため、機器故障やデータの破損が発生した場合、全台が影響範囲となりえます。復旧作業もそれだけ時間・リソースが必要になる場合がありました。

密結合なデータ

商品DBでは一つのテーブルに複数サービスのデータが混在して管理・利用されているケースが少なくありませんでした。

そのため、取り扱うデータが追加になり、カラム追加を行おうとすると、共用している他事業との調整が必要になります。

テーブルの参考画像

テーブル定義のレビュー程度で済めばいいですが、レコード数によってはデータがロックされサービスのダウンタイムが発生します。サービス停止の影響範囲が大きく、売上損失額もこれに伴って大きくなってしまいます。

こうした事態を回避するために、不本意ではありますが、カラム追加はせず新たにテーブルを作成するといった選択を強いられる状況になっており、拡張性・変更容易性に難がありました。

密結合なアーキテクチャ

商品データは様々なプロダクトでも利用されます。この利用の仕方として、DBへ直接接続・参照するアーキテクチャになっていました。

そして、例えば電子書籍のエンジニアが関知しないところで、他のプロダクトが電子書籍の商品データを持ったテーブルを参照しているなど、影響範囲・調整先がわからない状態になってしまっていました。

従来アーキテクチャ

そのためデータ構造の変更を行った際に参照しているプロダクトへの影響がコントロールできず、変更時に思いもよらぬ不具合を引き起こす可能性があり、前項と同様に既存のデータが変更しづらい課題がありました。

メンテナンスにかかる大きな調整コスト

ここまでもデータの追加・変更に関して触れてきましたが、サーバを維持・運用しているとEOL(End Of Life)の問題は避けては通れません。当然ですがサーバOS、ミドルウェアのバージョンは継続的にアップデートが必要です。

EOL以外にも、DBのストレージエンジンや文字コード等、現状に合わせて変更が求められることもあります。

ここまでに散々影響範囲について触れていますが、こういったメンテナンスを行おうとすると、一斉に複数のサービス停止をすることになります。

一斉に行うため、1回あたりのメンテナンスの機会損失額が大きくなることもありますが、実施する場合は時間帯や停止/再開時に必要な作業の調整対象が広範にわたるため、調整コストも非常に大きくなります。

そのため、必要なアップデート対応が後手に回ってしまうことがありました。

プロジェクトの立ち上げ

前提や前置きの説明が長くなりましたが、これからプロジェクトについて触れていきます。

まずはプロジェクトを推進するため最初にやること・やらないことを大まかに定義しました。

なお、ReadReplicaのDBサーバは、接続するサービスのアプリケーション毎におおよそ分けられていました。元々分かれていたわけではなく、本プロジェクトの前に整理がなされています。

私自身はその段階では参画していなかったため詳細は割愛しますが、このプロジェクトを進める上では必要不可欠なステップでした。

やると決めたこと

大きく以下の3点を方針として定めました。

特に目新しいこと・特別なことはなく、現状こうあってほしい。ということを明確化しています。

  1. Primaryとデータを管理する事業(サービス)を1:1の関係にする
  2. 管理する事業外からのデータ参照は全てインターフェースを挟む
  3. テーブル単位で管理する(=移行対象とする)事業を明らかにする

やらないと決めたこと

大規模な改修になるため、この機に乗じて色んなことををやりたくなります。しかし、スコープを広げればそれだけ不確実性も増しますし、工数・スケジュールも膨らむことになります。

そのため、やることと同じぐらい、場合によってはそれ以上にやらないことを決めるということは重要だと考えました。

  1. データ構造の変更
  2. データ登録オペレーションの最適化
  3. ついでの機能追加

今回のプロジェクトは商品DBを分けることにフォーカスすることとし、データ構造の最適化・正規化やオペレーションの変更は明確にスコープ外としました。

「今後、今よりそれらがやりやすい状態にするので段階を踏みましょう。一気にまとめてやることはしません。」と、事業責任者を含む全てのステークホルダーに向けて説明し、合意を得ました。

移行後の構成

プロジェクトを立ち上げたところで、向き合うデータベースを知った上でどのような構成にするかを計画する必要がありました。そのため、実行フェーズの前に調査・構成検討のフェーズを挟みました。このフェーズで行ったことを簡単に説明します。

現状を分析する

まず、データベース単位でなんとなく持つ印象から、テーブル単位で網羅的に状況把握して分類することを始めました。

ここではデータ登録オペレーションを行う運用部門へのヒアリングと、ログ集計・解析の2つを並行して実施しています。

その結果、DB内のテーブルは以下の4つに分類できることがわかりました。

  1. 一つの事業のみで管理している
  2. 複数事業が管理している
    1. テーブルは共用しているが、レコード単位では各事業のデータが別
    2. レコード単位で同一データを複数事業が管理・参照する
  3. 利用されておらず、今後更新することもない

構成検討と意思決定

構成は一度決定すると後戻りした場合の影響が非常に大きいため、誰と検討するか・意思決定は誰が行うか・意思決定をどのように行うかは事前に明確にして変更リスクを低減するよう注意を払いました。

今回のプロジェクトでは以下のように定めました。

  • 最終承認 : 当時のCTO
  • ステークホルダーの意見集約・方針の決定 : PM(私)
  • 構成の案出し・比較 : 商品DBを管理する全事業それぞれのエンジニアチーム代表者
  • アドバイザー : CTO室所属エンジニア、商品DB用CMSのエンジニア
  • レビュアー : インフラ部、セキュリティ部

検討内容の詳細は伏せますが、複数案を比較・検討した結果、以下の方針で合意・承認を得て決定しました。

移行後構成

プロジェクトの実行

構成が決まったらあとは実行あるのみです。
実行に移すにあたり、プロジェクトの進め方について予めいくつかの決め事を定め、ステークホルダーへ説明しています。

  1. DBを分割するまでに以下を順に進めること
    1. APIの開発・提供
    2. 他事業のデータ直接参照の廃止(削除、もしくはAPI経由で参照)
    3. 共通データを取り込む機構の開発
    4. 通信ログを元に対応漏れを調査して潰しきる
  2. 1事業ずつ順番に分けていく。各部作業は同時並行で進行するが、衝突した場合は先行する事業のための作業を優先すること。
  3. 事業成長のための開発プロジェクトと本プロジェクト間で人員・スケジュールの取り合いになった場合、事業案件を優先すること。

特に3点目は、対象となるすべての事業責任者に対してそれぞれ丁寧に説明しました。

このプロジェクトも今後の成長のために必要ではあるものの、それを理由に、目の前のビジネスチャンスを逃して完了する頃には事業の存続自体が危うくなってしまうというようなことは、決してあってはならないためです。

ここからは順に進めたステップをそれぞれ簡単に説明します。

APIの提供・置き換え

データが必要なプロダクトに利用してもらうインターフェースとして、APIを新規で開発しました。

各々が自由に作ると利用者側が置き換えにかかる負担が大きくなることも考慮し、API規格はGraphQLを採用・統一しています。

APIを開発するエンジニアの技術スタックや利用者側の環境が類似していることから、車輪の再開発を防ぐため、CTO室のエンジニアに協力を仰ぎ以下の2つを社内OSSとして提供してもらいました。

  • GraphQLAPIのテンプレートリポジトリ
  • クライアントライブラリ

APItemplate

APIを開発するエンジニアはテンプレートからforkして必要なスキーマ・モデルを実装することで、CI/CDを含む環境構築にかける労力を節約できました。

利用は強制ではなく、他アプリケーションのリプレイス計画等があり、言語選定も別で行いたいといった事情で1から開発したケースもあります。

共通データの更新・管理

商品DBの分割は順に進めていきますが、共通データは新旧の環境に等しく更新しなければいけないため分割前の商品DBに対しても更新可能なCMSを開発しました。

DBを分割する際はQueue/APIへリクエスト・取込を移行する事業側で有効化することでデータが一元管理できます。

データ更新オペレーション

共通データの構成(更新)

更新データの取込シーケンス

共通データの構成(取込)

不要なリクエスト削除とログ調査

必要と把握しているリクエストはAPIへ置き換えできますが、長く触られていない古いコードでは意図せず参照リクエストが残り続けるため、それらはログを元に洗い出して消していきます。

ログ調査にはDBサーバ側のインバウンド通信のpcapをElasticSearchに取り込み、Kibanaで確認できるツールをCTO室エンジニアに作成してもらい利用しました。

Kibanaでは接続元/先サーバ情報とQueryが確認できますが、どのプログラムをどう通ってリクエストされているかは、ひたすらコードリーディングによる調査が必要です。

ここでの調査は私自身も入り、担当エンジニアと連携しながら進めましたが、コードの複雑度が高く行き詰まる場面も度々ありました。

ログ調査ツール

そういった際はAppDynamicsの活用、backtraceのlog出力など異なるログ出力を組み合わせて調査しています。

この工程がこのプロジェクトで最も時間がかかり、地味で、そしてしんどいものでした。(が、根気でやりきりました)

商品DBの分割・移行

ここまでやって、ようやく本番です。STG環境での移行をリハーサルと位置づけ、本番環境の移行と同じ事前準備を行って臨みました。

実際にやってみて毎回何かしらの異なる問題が起きましたが、本番環境の移行時の手順を改善するための教訓として活用することができました。本番環境の移行はサービス非停止で、データ更新のみ停止して行っています。

移行手順

影響範囲が広範でかなりドキドキしながらの作業になりましたが、ここまで下準備を入念に行ったことが功を奏したのか、障害や大きなトラブルなく全ての移行を完了できています。

さいごに

現状、6つ中5つの事業が対応を完了しており、全て完了はしていないものの、DBと管理事業が1:1にはなりました。

立ち上げから長期に渡って本当に多くの方々に協力いただき、既に会社を去った方もいらっしゃいます。この場を借りてここまでやり遂げたことの報告と御礼を申し上げます。

完了した事業は、この取り組みによって劇的に何かが変化して良くなったわけではありません。ですが、それぞれの事業が改善を考え、実行できる・しやすい状態にはなりました。

ここからそれぞれ改善が進んでこそ、このプロジェクトの成果です。ここがゴールではなく、スタートラインに立ったと考えて、改善が進んでいけばと思います。

弊社では様々なポジションで仲間を募集しています。ご興味のある方は、下記からポジション・募集要項をご確認の上、ぜひご応募ください。

DMMの採用情報


  1. オンラインレンタルや通販、動画配信・電子書籍などデジタルコンテンツ

  2. Master/Slaveの表現は本記事内ではPrimary/Read Replicaと表記します