Meeting】(以下、GDM)。Vol.39」では、DeNAのモバイルゲーム開発で実践されている、サーバーコードをゲームタイトルの開発チームが極力書かずにクライアント側にできるだけロジックを置く、サーバー”コード”レスアーキテクチャを実現する手法などが紹介されました。本記事では、当日使用されたスライドを公開するとともに、各項目について一部抜粋したレポートを紹介します。と今後の展望」の3つのテーマに分類して、大竹より詳しく説明されました。株式会社ディー・エヌ・エー2009年にWeb動画配信サービス会社に入社。複数の新規Webサービス、ゲーム機/家電向けのサービスの立ち上げに携わる。2013年にDeNAに入社し、ブラウザゲーム運営、新規アプリ開発を経験。成果物の技術資産化を推し進める過程で、ライブラリなどのゲーム基盤開発業務に携わるようになる。現在はゲーム開発者の声に耳を傾けながら、ゲーム開発の課題をより良い、効率的な方法で解決できるような基盤をさまざまな方面で模索している。背景:サーバー”コード”レスアーキテクチャとは何かまず、クライアントとサーバーがどのように連携してゲームが進行する設計にしているのかからセッションがスタート。モバイルゲームで主流なアーキテクチャは、プレイヤーデータを直接改変する権利を持たず、サーバーが持つ情報を必要に応じて取得する「クライアント」と、プレイヤーデータを直接改変する権利を持ち、更新や取得の内容に応じてAPIを実装する「サーバー」に分かれます。このアーキテクチャの長所は、多くのエンジニアに馴染みがあり、サーバー側でプレイヤーデータの変更を行うため、ユーザーの手元でのデータ改変が難しく、チート対策にもなるということ。短所(弱点)について、サーバーとクライアントの実装の分断が発生し、サーバーロジックだけでは完結しないため「どこまでサーバーでやるのか?」といった判断が難しいことなどが挙げられました。DeNAでは、非ブラウザのネイティブアプリ開発時代から、そのような一般的な作りにとらわれない手法でのモバイルゲーム開発を実践しており、この仕組みを一言で説明すると「クライアントコードを中心に開発するアーキテクチャ」になると大竹は述べました。どのようなアーキテクチャなのか「クライアントコードを中心に開発するアーキテクチャ」とは、クライアントに責任を寄せることで、クライアントはサーバーからプレイヤーデータそのものを取得し、プレイヤーが更新後のプレイヤーデータそのものを生成して、その内容をサーバー側にAPIを通じて保存する仕組みとのこと。このアーキテクチャはゲーム用mBaaSに近い手法ですが、大きな違いはターゲットが異なること。簡単に動くものを作るのではなく、グローバル展開を見越して大規模タイトルも手がけることが可能なようです。共通ゲームサーバ「Sakasho」このサーバー”コード”レスが実現するのは、DeNAのモバイルゲームのためのプラットフォーム「Sakasho」を利用しているからであり、専用のゲームサーバーを作らずにモバイルゲーム開発が実現しているとのことです。サーバーレスにサーバーがあるのと同様に、サーバーコードが存在しないわけではなく、クライアントに寄せた作りをした上で、サーバーの機能を共通化しています。本勉強会内で使用された「プレイヤーデータ」とは、プレイヤーに関わる全データのことではなく、クライアントが更新の責任を追うデータのことを指すとのこと。Store、1回のAPIのリクエスト単位でアトミックな更新をします。サーバーからはプレイヤーデータを直接編集しないことを徹底し、プレイヤーに何らかのリソースを付与する場合は、すべてプレゼントボックスを経由。サーバーで処理すべき状況として、時刻や確率に従う形で付与する「ガチャ・ログインボーナス」、付与する経路や総量を固定化する「仮想通貨の付与、ミッション報酬」、運営側の行動として処理する「お詫び配布」などが挙げられました。プレゼントボックスへの集約サーバ上で付与したアイテムはプレゼントボックスに一元化。サーバ上ではプレゼントボックスの中身の意味が理解できないように作られています。プレイヤーデータとサーバー管理のリソースの交換プレゼントボックスの内容や、プレイヤーデータを引き換える処理をついては、プレゼントボックスのアイテムの受け取り済みフラグと、アイテム取得状態のプレイヤーデータを同時にサーバーに渡して、更新する仕組みになっています。プレゼントボックスの中身が実際にどのようなプレイヤーデータに引き換えられるかについては、クライアント側で制御。この仕組みにより、APIは何度も通信しているが、途中でアプリが終了指定もプレイヤーが不利益を被らないことを保証することが目的が達成できていると、と大竹は説明しました。サーバー”コード”レスによる効果このアーキテクチャを利用する大きな効果として、各々のゲームとしての固有の処理の大部分を、クライアント側だけで実装でき、スケーラブルなゲーム開発が実現できること。これまでいくつもの大規模タイトルのリリースを大きな障害なく、乗り切っていることが挙げられました。続いて実践編として、その大きな目的を「サーバー側で担保・処理していたことの一部をクライアント側で実現できるようにする」と掲げ、5つの抑えるべきポイントや失敗事例が細かく紹介されました。2015年にリリースされたゲームタイトル『戦魂』は、Unityでのサーバー”コード”レス実現した最初の内製ゲームとなりましたが、当時の開発や運用時に問題が頻出したようです。その後、複数タイトルの開発を経て、サーバー”コード”レスでクライアントを作る際に気をつける点、抑えるべきポイントが明らかになったとのこと。【抑えるべき点1】プレイヤーデータのテーブル設計プレイヤーデータの定義はクライアント側で実行、RDBMSの構造(表)以上の表現力があり、安全に扱える形でスキーマ設計を行い、プレイヤーデータは必要なものだけを読み込めるような構造にします。失敗事例として、エンジニアが手作業でテーブルごとのシリアライズ処理を書いており、それにより実装上の凡ミスにより不具合などが発生したとのこと。起動時に過去のイベントデータをロードしていたため、起動が遅くなりユーザー体験が悪化したり、回線の速度制限に引っかかったプレイヤーがタイムアウト秒数に処理が終えず、ゲーム継続不可能になった事例もあったようです。【抑えるべき点2】プレイヤーデータの一貫性の保証プレイヤーデータの更新時に、一貫性が担保されなければ重大な不具合につながるため、更新を伴うリクエストには冪等性が必要です。更新時にクライアントから更新の成功を100%検知することが不可能なため、成功していた更新を再度実行しても、問題ないような仕組みが必要となります。リトライ時に意図せず二重に仮想通貨を消費してしまうこと。複数テーブルに跨ったプレイヤーデータの更新が、片方のテーブルだけ更新されてしまって整合性が崩壊した、といった失敗事例が明かされました。【抑えるべき点3】ドメインロジックのレイヤー設計クライアントが持つロジックの量が必然的に重くなり、ひどい設計や実装をした際の影響が相対的に大きく、データの破損などプレイヤーからの信頼を既存する恐れのある不具合に直結することも、問題となります。コントローラに処理が集中して、プレイヤーデータの操作とUIが密結合になるといった失敗事例が発生しています。【抑えるべき点4】プレイヤーデータのチート対策サーバー”コード”レスを採用すると、クライアントチートの危険性が高いことが分かっています。メモリをチートツールで書き換えて保存されると、そのまま結果がサーバー上に反映可能で、クライアントは実行バイナリが手元にあるため、逆アセンブルなどでの解析が必要となっているようです。DeNAにはセキュリティ部が存在し、メモリ書き換えの検知、デバッガ/エミュレータの検出などを実施しているとのことです。【抑えるべき点5】時刻の正確性の保証サーバー”コード”レスでクライアント側で処理をすると、信頼できる時計が存在しなくなる問題が発生します。サーバー側の時間を知ることができるのは、サーバーと通信を行った瞬間のみで、リアルタイムに時間を知りたい場合に頼りにならず、毎回サーバー問い合わせすることも非現実的です。Library)」が開発されました。D4Lはこれまでのゲームタイトルの開発用に作られた草の根ライブラリが基になっており、累計約10タイトル、約5年の歴史を持つそうです。SDKの一部として提供されていることが明かされました。Object)と、そのLoaderを自動生成するモジュール「D4L.Definition.Compiler」を利用して、クライアントから読み込むマスターデータも定義できるようになっています。テーブルの自動生成DAOSchema属性にテーブルや種別を記述し、フィールドにSchemaProperty属性を付け、Context属性によりロード時の単位を指定します。Object」「シリアライザ」「読み込み単位を規定するDataContext」「データのコンバータ(主にマスターデータ用)」となります。スキーマ定義に使える型スキーマの定義に使えるのは、C#のプリミティブな型、C#のプリミティブな型に対応するメモリ改ざん保護型、構造体、enumなどになります。Storeへのマッピングについて、キーは「テーブルの識別子」「分割用のキー」「格納される行の主キー」の三要素となり、複数のキーでひとつのテーブルが成り立ちます。Contextの単位でプレイヤーデータを読み込み・アンロードすることでプレイヤーデータ全体をクライアントが常時所持しなくても良い状態にしています。2軸でのテーブル分割が可能に。Contextに所属できるのも大きな特徴となっているとのこと。Setといったクラスを1単位として、トランザクションを形成。Contextのアクセサクラスから更新用のDAOを取得、プロパティを更新、内容が確定したらStorageChangeSetというクラスに登録した時点でシリアライズされます。StorageChangeSetをAPIの引数に変換して、サーバー側に送信、ApplyToDaoの呼び出しでメモリ内の参照用のDAOに更新が反映されます。StorageChangeSetは、サーバーがなくても動くため、開発の初期に有用とのことです。D4Lによるレイヤー設計D4Lは軽量DDDによる設計を推奨しており、Repository/ValueObject/Specificationの実装のための基礎実装が存在します。個別のビジネスロジックはDataContextやDAOに直接アクセスしないことが推奨され、Repositoryを経由させて、ビジネスロジックから扱うEntityとDAOを分離します。これはプレイヤーデータを運用していると頻繁にテーブルの種類が使いされるためで、ゲームタイトルによって準拠度はまちまちなので強く強制はしないが、考慮しないければならないと大竹は述べました。D4Lによるプレイヤーデータのチート対策ヒープ上に常時乗る値に気軽にメモリ改ざん保護をかけられるようなライブラリを作り、バンドルしています。プリミティブ型の代わりに使うだけで、改ざんを検出した瞬間にコールバックが叩かれ、検知でき、スキーマ定義時に使用することで、メモリに乗った段階から破棄するまで一貫して保護可能です。改ざんを検知してもすぐに強制終了はしないことで、改ざんが成功したことを攻撃者に悟らせにくくすることを目的としています。クライアントでは一見成功しているように見えて、実はセーブデータが保存されていないので、アプリを落とすとデータが巻き戻ります。D4L以外によるチート対策さらに社内のセキュリティ部と協力し、チート対策を徹底的に実施していることも紹介されました。その一部としてエミュレータ/ルート化の検出、署名の検出、実行バイナリのダイジェスト検証などが実施されています。チートユーザーの動向を観察して、継続的な対策も実施しており、チート検出を回避されたら回避策の対処を検討し、イタチごっこを繰り返して攻撃者が音を上げるまで地道に対応していることも明かされました。信頼できる時間軸を扱うライブラリ「D4L.Moment」を使用し、実行中の自プロセスの経過時間(ProcessTime)を信頼する仕組みになっており、理由としては、時間が十分な精度で書き換えられずに進行する相対時刻を採用しているからとのこと。サーバー時間からの時間軸の取得するには、RTT分の誤差が発生する可能性があるため、サーバーからのレスポンスに対して「リクエスト受信開始時のサーバータイムスタンプ」「レスポンス送信開始時のサーバータイムスタンプ」の情報を付与しています。誤差をすぐに補正してしまうと、時間の巻戻りや早送りで意図せぬ不具合が発生する可能性があるため、時間の流れを調整して、時間をかけて時間を修正しています。最後に、サーバー”コード”レスアーキテクチャの臨界点や、共通ゲームサーバー「Sakasho」の後継となるプロダクト「Takasho」を現在開発していることを明かし、D4Lもそれに合わせて再設計されていることなどが説明され、本勉強会は終了しました。クリスマスにピッタリの豪華なデザート今回のデザートは、渋谷スクランブルスクエアにオープンしたパティスリーの高級モンブランをご用意。RANKING宍倉さん、ぶっちゃけDeNAと組んでどうだった?──『メギド72』開発うら...【DeNAマーケティング部特集vol.1】ROIだけが判断軸ではない。