FINAL FANTASY Record Keeperを支えたGolang

Preview:

Citation preview

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

GoCon 2015 Winter

FINAL FANTASY Record Keeper を支えたGolang

2015/12/06GDI 渋川よしき

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

お前だれよ? 前職

⁃ 自動車会社の社内 SE 現職

⁃ 社内ゲームエンジン用のフレームワーク( クライアント用もサーバ用も ) 作ったり、開発支援ツール作ったりいわゆる社内 SE

⁃ たまにゲーム開発を手伝ったりもします。

渋川 よしき

プログラミング C++ とか Python とか Golang とか JavaScript とか

本 つまみぐい勉強法、アート・オブ・コミュニティ ( 翻訳 ) 、

Mobage を支える技術、オブジェクト指向 JavaScript( 翻訳 ) 、ポモドーロ・テクニック入門 ( 翻訳 ) 、 etc

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

©SQUARE ENIX CO., LTD. ©DeNA Co., Ltd.

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

前回までのあらすじ

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

FFRK マスターデータ運用改善

http://www.slideshare.net/dena_study/final-fantasy-record-keeper

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

まとめ : どういうところで使ったか?

ゲームのマスターデータ⁃ マスターデータというのはプログラムでも絵でも音でもない要素⁃ キャラクタや敵や技のパラメータ、ダンジョンの難易度、

シナリオの文言、チュートリアル設計 etc⁃ 3D の世界になると、レベルデザイナーなどさらに分業化してい

るが、 DeNA では 3 つの役割に分担している• エンジニア : プログラム

• アーティスト : 絵とかアニメーションとか UI デザイン

• プランナー : マスターデータ

Google Drive 上にあるスプレッドシートに入ったマスターデータの元データからゲームサーバが使えるフォーマットに変換するツールをGolang で書いたら、 Google Apps Script よりも 100 倍早くなったよ

今日は実装部分の話をします

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

ラフな全体像

ファイル一覧のシート

取得

ファイル一覧で指定された名前のファイ

ルのキーのリストを作

フォルダの全子要素のキーと名前を取得

ファイルをダウンロード

ファイルをロードし、JSONSheet /

JSONFormat を分析

•RefListTemplate を元に値の置き換え•CSV 出力

Jenkins 上で実行されるコマンドラインプログラム

⁃ 開発者の手元でも実行できる

他にもいくつかサポートのプログラムが何本かいる ( 差分レポート作成とか )

同名のシートでグループ化

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

今日のレシピ

使っている開発環境・ライブラリ 並列処理の実装と並列処理のログ出力 Google API のアクセス頻度を制御する diff( 文字単位、行単位、 blame の実装 ) xlsx ファイルの読み込みと高速化

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

使っている開発環境・ライブラリ

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

使っている環境・ライブラリ

IntelliJ IDEA + golang-idea-plugin⁃ https://github.com/go-lang-plugin-org/go-lang-idea-plugin⁃ Vim はなるべくデフォルトにしておきたいので IDE⁃ plugin はどんどん改善されたりしている ( が不安定な部分も )⁃ 英語のスペルチェッカーが地味便利

Sphinx⁃ ドキュメントツール (http://sphinx-users.jp)⁃ 使い方を説明する 4 択クイズも作れる! ( 作った )

github.com/jessevdk/go-flags (MIT)⁃ コマンドライン引数のパース⁃ short/long 、同じ引数を複数回指定、きれいなヘルプテキスト出

力あたりが良かった⁃ 構造体のタグで指定する• 多言語化しにくいという欠点があるので言語ごとに構造体を作って切り替

えという苦肉の策

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

github.com/google/google-api-go-client (MIT)⁃ Google API にアクセスするためのライブラリ

github.com/tealeg/xlsx (BSD style)⁃ xlsx を読み書きできる Pure Go ライブラリ

github.com/sergi/go-diff (MIT/ オリジナルは APL v2)⁃ Google の Diff, Match and Patch ライブラリの Golang 移植

github.com/bkaradzic/go-lz4 (BSD)⁃ 高速なファイル圧縮・展開ライブラリの Pure Go 版

github.com/OneOfOne/xxhash (APL v2)⁃ lz4 内でも使われている高速ハッシュアルゴリズムの Pure Go 実

装。 github.com/ugorji/go/codec (MIT)

⁃ msgpack/json とかいろいろなシリアライズのライブラリ

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

github.com/mattn/go-colorable (MIT)⁃ 信頼と安心の mattn プロダクツ⁃ Windows でも他の環境でもコンソール出力に色が付く!

github.com/mgutz/ansi (MIT)⁃ mattn プロダクツと一緒に使える色付け用の便利関数

github.com/jteeuwen/go-bindata (Public Domain)⁃ バイナリの中にファイルを入れちゃうツール⁃ HTML テンプレートと google API アクセストークン、デフォル

ト値の JSON あたりを連結してます

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

golang.org/x/oauth2⁃ OAuth2

golang.org/x/text/unicode/norm⁃ ユニコードの正規化 (NFD から NFC)関連⁃ Mac OS X を嫌いになる前に

github.com/xeipuuv/gojsonschema (APL v2)⁃ JSON スキーマのバリデーションライブラリ⁃ ツールが戦魂チームで魔改造されたときに追加されてた

github.com/nicksnyder/go-i18n (MIT)⁃ 国際化するためのライブラリ⁃ JSON で翻訳を指定する。単数・複数で訳文の使い分けもできる⁃ http://qiita.com/shibukawa/items/f0e4df597e62372fe7d5

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

並列処理の実装とログ出力

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

並列処理

汎用のスレッドプールを作ってみて、大量アクセス時はそれだけを使っている⁃ Google Drive でフォルダの中の情報のアクセス⁃ xlsx のダウンロード⁃ xlsx の読み込み⁃ 同名のシートごとにデータ分析・変換⁃ 同名のシートごとに CSV/JSON/TSV/HTML等で出力

ジョブを途中から追加できるようにして貪欲的に処理を行えるようにしたがその機能は使わず⁃ OOM キラーに殺されまくったので、メモリ使用量の削減のため

に処理を適度の中断してそれぞれの段階で枝刈りを積極的に行う方向に方針転換

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

使っているところのイメージ

この中の処理を全タスクに対して適当にスレッドプールを使って並列で処理する

これがジョブ一覧の入ったキュー

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

並列処理のログ出力

そのまま出力しても順序が混ざってしまって読みにくくなってしまう グループ情報付きのエラー出力関数を作って、一旦メモリに貯めてお

いてグループごとに分類して出力するようにした。 API アクセスエラーとか、入力ファイル名間違いなどの致命的な問題

は log.Fatal() するが、読み込んだ後は基本的に最後まで完走する設計にしている。

コンソールに出力したら Jenkins がそのまま表示してくれるので出しっぱなし。 Jenkins の Text Finder プラグインで WARNING の文字を見つけたら不安定 (Unstable)扱いにしている。

グループ

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

Google API のアクセス制御

ユーザごとの秒間アクセス数を Google Developer Console で設定しても、それより大分下のアクセス頻度で 403 User Rate Limit Exceeded が出る件⁃ 昔は数千でも設定できたけど、最近 10 回 /秒までしか増やせなく

なったっぽい。ちなみに 10億 / 日なのでフルにアクセスしても0.1% しか行かない!

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

time.Ticker を使って、アクセス頻度の制御を行うようにした API アクセスする関数 ( スレッドプールで並列で動いていても ) の中で、下記の APIThrottling()関数を呼び出すと、呼び出しが適当に間引かれて実行されるようになった

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

diff の実装 ( 文字単位 /blame の実装 )

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

Diff, Match, Patch

Google製のライブラリ。基本的にはこれを使っておけば間違いない⁃ https://neil.fraser.name/software/diff_match_patch/svn/trun

k/demos/demo_diff.html このライブラリの基本設計としては、まずできるだけ細かい文字単位

diff を計算して、その後適度な粒度になるようにパラメータをチューニングしながらクリーンナップするという 2段階で実行する方式のAPI になっている

なんというかクセがあるからサンプルに従うのが良さそう

今日私は朝ごはんを食べました。

明日私はお酒を飲みに行きます。

今明日私は朝ごはんお酒を食べ飲みに行きましたす。

今明日私は朝ごはんを食べましたお酒を飲みに行きます。

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

使い方

文字単位 diff

セマンティックに従って diff をマージ

行単位 diff⁃ http://qiita.com/shibukawa/items/dd75ad01e623c4c1166b

before := “ 今日私は朝ごはんを食べました。”after := “ 明日私はお酒を飲みに行きます。”

dmp := diffmatchpatch.New()result := dmp.DiffMain(before, after, true)

dmp := diffmatchpatch.New()tmp := dmp.DiffMain(before, after, true)result := dmp.DiffCleanupEfficiency(tmp)

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

最終的な出力は diffmatchpatch.Diff 構造体の配列⁃ テキストと、操作 (そのまま、追加、削除 ) の情報を持つ

Diff 構造体配列を抜き出して見やすい出力にする⁃ ↓変化しない項目と、削除された項目だけを表示。削除された項目には text-decoration: line-through; と赤色を付ける。

⁃ ↑変化しない項目と、追加された項目だけを表示。追加された項目には緑色を付ける

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

diff の応用 : blame コマンド

スプレッドシートでも git blame したくなるってことあるよね! シートの行ごとに、最終編集者と変更日時を表示させることも可能で

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

blame の実装

1. まずは Google Drive にアクセスして落とせるだけの全リビジョンのスプレッドシートを xlsx形式で落とします。

2. 新・旧ファイルを CSV 化して、行区切りの文字列配列を作ります⁃ ( 実際はオンメモリ処理です , 説明のため 1 シートだけ扱います )

1000, “ たたかう” , “ 敵 1 体を通常攻撃”1001, “ケアルガ” , “対象の HP を回復”1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 小 )

1000, “ たたかう” , “ 敵 1 体を通常攻撃”1001, “ケアルガ” , “対象の HP を回復”1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 中 )

1000, “ たたかう” , “ 敵 1 体を通常攻撃”1001, “ケアルガ” , “対象の HP を回復”1002, “ ファイア” , “炎属性魔法攻撃 (効果 : 小 )1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 中 )

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

3. 古い方からの 2 つ取り出して行単位差分を取ります

4. 追加されていればその行は新 (index: 1) で追加された、維持であれば旧 (index: 0) で更新されたとみなし (削除は無視 ) 、その行がどのファイル由来のものかを記憶していく

5. 新しいファイルと、その次のファイルを取り出し、同じように行のインデックスを更新していく。これを最後のファイルまで続けていく

1000, “ たたかう” , “ 敵 1 体を通常攻撃”1001, “ケアルガ” , “対象の HP を回復”1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 小 )1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 中 )

0: 1000, “ たたかう” , “ 敵 1 体を通常攻撃”0: 1001, “ケアルガ” , “対象の HP を回復” 1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 小 )1: 1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 中 )

0: 1000, “ たたかう” , “ 敵 1 体を通常攻撃”0: 1001, “ケアルガ” , “対象の HP を回復”2: 1002, “ ファイア” , “炎属性魔法攻撃 (効果 : 小 )1: 1003, “ ファイラ” , “炎属性魔法攻撃 (効果 : 中 )

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

xlsx の読み込みと高速化

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

xlsx のロード

github.com/tealeg/xlsx がオンリーワンの選択肢。これで OK?⁃ ではなかった。• ポインタで持つべき所を実体で大量に持ってメモリを食っていたり、構造

体のメンバーが初期化されてないところがあったり、先頭の空行が勝手に省略されて行がずれてたり、生成された xlsx ファイルが Excel で読み込めなかったり・・・

⁃ バグ修正の Pull Request を送り、ついでにセルのタイプ ( 数値型や Boolean型など ) を取得できるように機能拡張もした。

⁃ 今もたくさんユーザがいて、スタイルなども扱えるように修正され続けているし、基本的に良いライブラリです

⁃ xlsx のライブラリで困ったら Python の openpyxl を参照すると良いです。 MS製の XML スキーマを使ったユニットテストもしていて、困ったときのリファレンス実装として役立ちます。

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

しかし、 xlsx のパースは重い⁃ xlsx のロードがツールのボトルネックに (90%以上の時間 )⁃ プロファイラで計測しても、標準ライブラリの中ばかり・・・• zip の展開は重い

• XML のパースは重い

• XML タグ構造体のインスタンス作成が重い

• golang の構造体にマッピングする encoding/xml は遅い?

⁃ タグのパースなどを事前にやることで高速だよ!という JSON パーサもあるぐらい

• ついでに xlsx ライブラリがセルごとに構造体を作っちゃうのも遅い

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

軽量 xlsx的なモジュールを作成

API は tealeg/xlsx とは少し違うけどなるべく同じに⁃ セルは構造体を作らず行のオブジェクトに配列でデータを持たせ

る github.com/ugorji/go/codec を使ってファイルとの読み書きをする

⁃ これだけで msgpack, binc, cbor, json の読み書きができる⁃ サイズを減らすために lz4 で圧縮もかけた

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

処理時間の推移

オリジナルの xlsx のまま読み書き⁃ ダウンロードをキャッシュ済みの場合でも 40秒ほど

JSON + lz4 で圧縮のキャッシュ化⁃ スタイル情報とかもなくなるのでファイルサイズも 1/2〜 1/5⁃ メモリ使用量も 1/3 ぐらいに⁃ 処理時間は 15秒ぐらいに

MessagePack + lz4 で圧縮のキャッシュ化⁃ ファイルサイズは JSON+lz4 とほぼ同じ⁃ 処理時間は 6秒ぐらいに

Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

まとめ

Excel/Google Spreadsheet を中心としたテキストの分析・加工のコードなら Go でガンガン書ける⁃ 表計算は非プログラマとのコミュニケーションツール• 読み書きソロバン Excel の時代

⁃ 使える人は多いし、 Excel の読み書きができれば最速ではないかもしれないけど 90% のビジネスは回る

⁃ Go は速いので業務上のボトルネックを全力で叩き潰すのに便利⁃ 文字列中心の処理でも C++ほどやる気が消耗しない

Pure Go のライブラリも数多くでてきて、言語の後方互換性も高いので、使える資産がどんどん増えている⁃ リファレンスしかなくて使い方が良くわからないライブラリも多

いので、 Qiita とかでどんどん発信しましょう⁃ まだまだ成熟してないライブラリも多いので、いっぱい PR 出そ