DMMグループの一番深くておもしろいトコロ。
テクノロジー

DMM 百万長者を支える技術

DMMグループの一番深くておもしろいトコロ。

はじめに

DMM 百万長者を開発している上井(ウワイ)と申します。

DMM 百万長者は、2019 年 9 月にリリースしたばかりの新進気鋭のサービスです。
そんな DMM 百万長者のアレコレをご紹介いたします。

DMM 百万長者ってどんなサービス?

DMM 百万長者(以下、百万長者)は、確率 1/4096 で 143 万円が当たるくじを提供しているサービスです。
くじは 1 口 500 円! そして、ほぼ毎日抽せんが行われています。 平常時であれば毎回 3〜5 名が当せんしています!

「確率 1/4096 って当たりにくいんじゃ…」と思ったそこのアナタ! 私も最初は『うわっ、確率低すぎ…』と思いました!
しかし、一般的な富くじでは、100 万円が当たる確率はおよそ 1/1 万〜1/10 万だそうです。 それに比べると百万長者のくじは圧倒的な高確率!!! しかも、確率が 1/2401 になる日もあるんです!

ここまで読んで「怪しい…」と思ったそこのアナタ! 私も最初はめちゃくちゃ怪しいと思いました!
しかし、実際はとてもクリーンなサービスです。 くじの仕組みには、公営競技であるオートレースの重勝式投票券を利用しています。 つまり、某年末ドデカイ富くじや某スポーツくじと同じく、我らが日本国に認められたくじなのです。 百万長者は地方自治体の収益に貢献しています!

もちろん運営・開発も真面目にやっています!

技術をご紹介!

採用している言語・フレームワーク、クラウドプラットフォームを表にまとめました。

アプリケーション

領域 フレームワーク(言語)
フロントエンド NuxtJS(Node.js, SCSS)
バックエンド EchoGo
その他 Docker, Terraform

クラウドプラットフォームなど

領域 サービス 詳細
インフラ AWS ECS(Fargate), Step Functions, CloudFront, RDS Aurora など
監視 Datadog APM, Monitor, Logs など
ソース管理 GitHub Enterprise  
CI/CD CircleCI Enterprise + AWS CodeBuild, CodeDeploy
CMS Contentful  
メール SendGrid  
AB テスト Google Optimize  
データ分析 Google BigQuery, Data Portal, Google Analytics

次の図は、アーキテクチャの概略図です。

フロントエンドとバックエンドが明確に分離された、いかにも近年らしい構成となっています。 また、自分たちで管理するサーバーのない、いわゆるサーバーレスアーキテクチャを採用しています。 AWS Step Functions を採用しているのも特徴の一つでしょう。 百万長者は裏側で複雑な非同期処理が常に動き続けており、Step Functions がそれを支えています。

まずはフロントエンドとバックエンド、それぞれの仕組みや工夫についてご紹介いたします。

フロントエンド

Vue.js のフレームワークである NuxtJSを採用しています。SPA で SSR、そして PWA というモダンなフロントエンドとなっています。
百万長者サイトのみならず、管理画面も NuxtJS に統一しています。

NuxtJS については、私が以前に書いた記事をぜひ参照ください。内容は多少古くなっていますが、大筋は変わっていません。

Nuxt.jsとFirebaseでSPA×SSR×PWA×サーバーレスを実現する

Nuxt.js v2とGAE/SE Node.jsでSPA×SSR×PWA×サーバーレスを実現する

超快適な UX

SPA によるページ遷移の圧倒的な速度、PWA の強烈なキャッシュ機構により、非常に軽快で快適な UX を提供しています。

論より証拠、百聞は一見にしかず。ぜひ百万長者にアクセスして実際に触ってみてください。よろしければ会員登録もお願いいたします!

Headless CMS

CMS には Contentful を導入しています。
Contentful は Headless CMS の一つで、簡単に言えば CMS の管理画面と API を提供している SaaS です。 このほかにも Headless CMS の SaaS はありますが、機能や API 制限、コストなどを比較して Contentful を採用しました。
フロントエンドから Contentful の API にリクエストし、コンテンツを取得、表示する仕組みとなっています。

百万長者のお知らせやバナー管理など、運用にまつわるほぼ全てのコンテンツを Contentful で管理しています。 このおかげで運用の効率化が達成されています。料金も低価格なので嬉しい限りです。

また、百万長者をリリースした直後に、Contentful から予約投稿機能が Alpha リリースされたのも棚からぼた餅でした。それまでは予約投稿をするためのシステムを自前で開発する必要があったのですが、本当に運が良かったです。予約投稿は運用上必要ですからね。

静的ファイルの CDN 配信

NuxtJS は内部的に connect というミドルウェアが利用されており、この静的ファイル配信パフォーマンスが非常に悪いのです。 このために静的ファイル配信がサーバーに高い負荷を掛けたり、レスポンス時間の大きなボトルネックとなっていました。 アクセスがスパイクするとフロントエンドサーバーが落ちる、といった事態を避けるべく次の構成にしています。

SSR をフロントエンドサーバー、静的ファイル配信を CloudFront(ソースは S3)と、配信のインフラを分離しています。CloudFront のエッジキャッシュ機能も有効になっており、レスポンス速度の向上も図っています。
クライアントへアプリケーションを配信する流れは次のとおりです。

  1. フロントエンドサーバー(NuxtJS)が SSR した DOM をクライアントに返却
  2. クライアントは DOM を元に CloudFront から静的ファイルを取得

この構成にした結果、次の表のとおり配信パフォーマンスが大きく向上しました。4 倍のリクエスト数まで可用性は 100%を維持、レスポンス速度は 3 倍〜70 倍の改善です。

可用性

rps CloudFront なし CloudFront あり
100 100 % 100 %
200 83 % 100 %
300 44 % 100 %
400 28 % 100 %
500 12 % 88 %
600 0 % 86 %
700 0 % 82 %

 

平均レスポンス時間

 

rps CloudFront なし CloudFront あり
100 0.77 sec 0.25 sec
200 8.58 sec 0.70 sec
300 25.43 sec 1.56 sec
400 54.16 sec 2.41 sec
500 43.09 sec 0.60 sec
600 N/A 3.54 sec
700 N/A 7.30 sec

NuxtJS でアセットの CDN 配信をするには、config ファイルに設定を追加するだけで済むので簡単です。

build プロパティ - NuxtJS

これに加えて、/staticディレクトリ配下の静的ファイルについても、我々で別途設定を追加することで CDN 配信を実現しています。

静的ファイルのソース維持

静的ファイルのソースを S3 にしているのには理由があります。それは、デプロイのタイミングでクライアントとサーバー間の静的ファイルに差異が生まれ、一時的にエラーとなるのを防ぐためです。

シンプルな構成で考えてみましょう。例として、クライアントとサーバーの 1 対 1 の関係で考えます。
まず、デプロイの前にクライアントがサーバーにアクセスします。この時、クライアントとサーバー間のバージョンは一致しています。

ここでデプロイされました。すると、クライアントが旧バージョン、サーバーが新バージョンとなります。このあとクライアント側で操作があり、旧バージョンの DOM で定義されている静的ファイルを新バージョンのサーバーにリクエストされます。しかし新バージョンのサーバーにはファイルが存在しないため、ステータスコード404となりエラーが発生します。

サーバーの前段に CloudFront を置いてエッジキャッシュを有効にすれば、この現象をある程度防ぐことはできますが、それでも確実ではありません。

そのため百万長者では、S3 に過去バージョンの静的ファイルも保持するようにしています。

これであれば、クライアントとサーバー間にバージョンの差異があったとしても、クライアント側のバージョンの静的ファイルは問題なく配信されます。一時的なエラーすら発生しません。

バックエンド

API

アーキテクチャの概略図を見て分かるとおり、API はサービスの核となっています。
Go 言語を採用しています。採用理由は下記のとおりです。

  • 高パフォーマンス
  • 静的型付けによる安全性
  • DMM の社内標準言語となる潮流

百万長者では、社内でも早い時期に Go を使い始めたと思います。

実際に採用してみると、特にパフォーマンスについては目を見張るものがありました。
弊社でよく使われている PHP や Ruby といったスクリプト言語に比べて、単純にレスポンス速度だけを見ても高速です。高速なレスポンスは UX の向上にも寄与しています。 大量データの処理に関しても、百万長者は大量のくじを毎日処理していますが、高速で負荷も非常に低いです。
そのほか、アクセススパイクなどの異常時であっても、フロントエンドサーバーが高負荷やスケーリングでヒィヒィ言っている最中、API サーバーはずっと平気でした。

静的型付けに関しては安全性のみならず、IDE の静的コード解析の力をフル活用できるため開発効率が非常に良かったのも好印象です。 ちなみに、オニオンアーキテクチャをベースとしたパッケージ構成で開発しています。

AWS Step Functions

百万長者の心臓部を担っているのが AWS Step Functions です。
Step Functions はワークフローのマネジメントサービスです。

百万長者では、複雑な手続きやバッチ処理が必要なシーンで、Step Functions を幅広く活用しています。
シーンの例を挙げます。

  • 会員登録
  • くじの購入予約 / 購入確定
  • 抽せんの開始・途中経過・確定
  • 当せん金の配当
  • Twitter への抽せん情報ツイート
  • メール一斉送信

さらに一例として、くじの購入予約のワークフローがこちらです(大人の事情でボカしています)。

このような複雑な手続きを純粋なプログラムだけで管理するのは大変だということ、お分かりいただけるでしょうか。
Step Functions は手続きをワークフローとして管理・実行してくれるのです。ワークフローを利用するうえでの機能についても充実しています。

  • 分岐
  • ループ
  • 並列 / 並行処理
  • 待機
  • 例外・エラー処理(リトライなど)
  • 実行履歴・再実行
  • モニタリング

正直「これが欲しかった」と思いました。しかも、これだけの機能を備えながら料金も安いのです。この上なく重宝しています。

さらに百万長者では、このワークフローを活用しつつ、アプリケーション側でも冪等性を保つように実装しています。もし障害やバグでワークフローが失敗したとしても、修正後に AWS コンソールから再実行ボタンをポチっとするだけで障害対応完了…といったことも幾度となくありました。実行履歴とそのステータスが残っているので、「ログを漁って失敗した ID を調査」のようなことをする必要もなく、コンソールを見るとひと目で失敗した手続きが分かって 1 クリックで終わるのです。大事なことなのでもう一度書きますが、本当に重宝しています。

百万長者では、データの操作を実行するステート(1 ステップのことを呼びます)で、プロキシのように振る舞う Lambda を間に介して API サーバーにリクエストを送信する仕組みを構築しています。API からのレスポンスを元に、その後のワークフローの状態遷移が進みます。

百万長者リリース以前には、Lambda から RDS に直接接続するとデータベースのコネクションが爆発的に増える危険性をはらんでいました。そのため、DB へのアクセスは API サーバーに一任する構造になっています。
なお、2020 年 2 月 20 日現在、このコネクション問題を解消する RDS Proxy という仕組みがプレビュー版で提供されています。 これを利用すれば、Step Functions → Lambda → APIサーバー → RDSのように API サーバーを介さずとも、Step Functions → Lambda → RDSというシンプルな構成が実現できます。この場合は、Lambda に直接アプリケーションをデプロイすることになります。GA が待ち遠しい限りです。

以上が、百万長者におけるフロントエンドとバックエンドの仕組みと工夫です。

データドリブン

弊社では、CTOがデータドリブンを掲げてきました。百万長者チームも例に漏れずデータドリブン・分析に力を入れています。

データ分析には、Google が提供する 2 つのサービス「BigQuery」「Data Portal(旧:Data Studio)」を採用しています。 BigQuery はデータウェアハウス、DataPortal は BI ツールです。

データドリブンで百万長者をグロース

実際の Data Portal のダッシュボード画面がこちらです。これも大人の事情でボカしていますが、雰囲気だけでも感じていただければ幸いです。

Data Portal では、次のような多くの数値を日々追っています。

  • 日々の売上や会員登録数といった主要数値
  • ユーザーセグメントごとの主要数値
  • Google Analytics の主要数値
  • 購入パターン分析
  • コホート分析
  • チャーン分析
  • LTV 予測

さらに深堀りしてデータを分析する場合は、BigQuery で SQL を実行してデータを探索します。
非エンジニアでも BigQuery を使って分析をしています。そのための教育もチーム内で実施しています。 BigQuery はブラウザでアクセスするだけで手軽に使えるため、着手のハードルが低いことも地味ながら嬉しいポイントです。

これらのデータ分析環境を活用して、調査 → 施策企画 → 実行 → 効果測定までを一貫してデータドリブンで改善サイクルを回しています。その結果が百万長者のグロースに繋がっています。

Google サービスの採用理由

サービスのインフラは AWS ですが、データ分析に関しては GCP のプロダクトのほうが優れていると判断したためこれらを採用しました。 弊社は Google の G Suite を利用しており、社員のアカウントをそのまま使用できるため、アカウント周りの運用の手間が少ないのも私たちにとってのメリットです。 そして、なんと言っても Data Portal に関しては料金が無料(別途 BigQuery のスキャンで従量課金)なので、スモールスタートした百万長者でもハードルなく導入できることも決め手でした。

システム観点での大きな理由は、BigQuery が本番環境とは分離されたデータ基盤となることです。
当然ながら、分析に際して本番環境の RDS には一切負荷が掛からないため、もしとんでもない SQL クエリを発行してしまっても本番環境には影響せず安全です。 そもそも、データ分析のために本番の DB に直でアクセスなんてしたくないですよね。

最後に

DMM 百万長者のサービス概要、アーキテクチャ、データドリブンの取り組みをご紹介いたしました。
技術的な内容についてはアーキテクチャや仕組みの工夫点を中心に触れましたが、実装面での取り組みももちろんございます! 過去に開発メンバーが登壇した記録もございますので、こちらもぜひご覧ください。

Nuxt.jsがDMMの新サービス開発に採用された理由と成果

私も弊社開催イベントにて登壇の予定がありますので(現時点では未公開です)、さらに込み入った話はその場でさせていただきます。乞うご期待。

百万長者を開発している公営競技グループでは、エンジニアを募集しています!
ご興味のある方はぜひご応募ください!

https://dmm-corp.com/recruit/

シェア