ソシャゲサーバー開発(lumen5.4)で次こうしたいことや所感をつらつらと書く

 ~~上にちゃんと経験ある人いれて~~
 ~~趣味程度でPHPやってた人が某大手ソシャゲIPものを一人で作らされる狂気の沙汰~~

# 随時更新

1. 型宣言
ある程度変数名で分かるようにしてるが、やはりあるとわかりやすいよね。

2. Collection便利
便利なのをもっと早く気づくべきだった(whereとかgroupByとか便利)
ただしループ内でwhereを使いまくるのはNG(インデックスなんて無いから毎回データ件数分ループ回る)
インデックス貼れたりしたら便利なのに

3. bindIfとsingletonをちゃんと理解すべきだった
例

	```AppServiceProvider.php
app()->singleton("Hoge", \App\Hoge::class);
```
は`app()->Hoge`とアクセスしても一度しかHogeインスタンスは作られないが

	```AppServiceProvider.php
app()->bindIf("Hoge", function($app) {
	return new \App\Hoge();
});
```
は`app()->Hoge`とアクセスとするたびにHogeインスタンスは作られる
最初試したときにどちらも一度しか作られないって勘違いしたまま作っていって途中で気づく

4. QueryBuilderでのデータのとり方を->get()で統一すべきだった
明らかに１件しか取れないものは->first()でとってたけどこの場合はstdClassになる
対して->get()はCollectionになる
さらに戻り値の型をCollectionに統一したかったのでreturn(Collection::make(firstで帰ってきたもの));
でやっていたため、`$c['hoge']` (firstのとき)と`$c->hoge`(getでとったものをforeachで処理した際)混在する

5. Eloquent使ってみる
ORMは勝手にSQL発行していくイメージがあったので今回はQueryBuilder使ったけど、次回は検証して使ってもいいかもしれない
(型宣言でわかりやすくもなりそうなので)

6. Routerでgroupのgroup
lumen5.4では出来なかったんですが、5.5から出来るようになってるようなので使う

7. マルチインサート(バルクインサート)を利用する
負荷テストで最適化してるとき、大量のデータはバルクインサートでかなり改善された。
10件超える時があるデータ挿入ならばとっととバルクインサートにしておくのが良さそう

8. リセマラ対策しないとね
リセマラされたデータはタンスの肥やしなので新規ユーザー作成時に大量に作られるデータはある程度進んだ際にデータを作るように出来ないかを検討したい(今回はミッションデータが大量だったけど初っ端から利用されるので仕方なかったが、データの作り方次第では最小限必要なものしか作らず、特定のタイミングで一気に作るとか出来るはず)
また、規約で1ヶ月ログインしてないユーザーのデータは消すとか書いてても良かったかも

9. KVS(Redis memcache)にはserialize unserialize
最初はjson_encode json_decode使ってたけど、こっちのほうが早いらしい＆型や構造も失われないので楽(ただし多少容量は食う)

10. Carbonを使う
フレームワークとは関係ないですが日付操作ではCarbonが便利らしいので次回使う
(今回はDateTimeでなんとかしてた)

11. 日付取得周りは一つかます
テストでサーバー時間変更して確認する必要が多々あります(期間開催イベントの確認など)
その際にそう思ってかまして作ってたのですが、サーバー時間変更しておけば確実だからそうしておこうと思って封印してたのですが、
頻繁に変えるとDHCPでエラーが発生してネットワーク接続できない状態になったので、封印を解くに至る。
時間取得関数に関しては直接呼び出さずに必ず一つかますのがいいかと(new DateTimeは必ず特定の関数からしかしない)

12. 最初から複数対応にする
特定のユーザーIDからクライアント側の表示に必要なデータを取ってくるために関数を作る際、一人しかデータ必要ないからと言って
`function hoge(int userId)`と作らず`function hoge(array userIds)`にする。
たいてい開発していくと複数一気にとってこないといけないし、最適化も出来る。
HTTPリクエストパラメータにおいても同様
(フレンド申請する際に仕様で一人づつしか申請できないとなってたからといって配列で受け取っておいても問題はないし、操作していくうちに複数一気にやりたいとか言ってくる(実際にそうなった))

13. ログはGoogleやAWSに任せる
ログサーバー(ArangoDB)に流してたけど、データが多すぎるので、最初からGoogleBigQueryなりAmazon DynamoDB(あまり知らない)に流したほうが、メモリ枯渇で落ちないかヒヤヒヤ過ごすことはなくなる・・・と思う
(もう少し開発開始が遅ければArangoDB3.3になっててRocksDBにしててメモリは気にしなくて良くなってたのかもしれないが、まあデータ量が多いので任せたほうが安心と思う)

14. APIのレスポンスに関してMessagePackやらFlatBuffersを利用する
クライアントのJSONデコード時間すら最適化しないといけないようなものなら利用するべき。
型定義をちゃんとしていたらサーバー側(PHP)はそれほど難しくはない。
リクエストデータで使う必要はたぶんない
~~使う場合はちゃんとしたまともな仕様決めが必要だと思うが、そんな時間はなかったし、そんな余裕はないし、予算規模でもなかった~~
MessagePack
http://msgpack.org/ja.html
FlatBuffers
https://google.github.io/flatbuffers/

15. DBのロックは最小限に
所感なのでなんとも言えないけど、ロックはほぼほぼ使わなかった。データ追加・更新・削除の順を決めておいてその順番にしておけばだいたい大丈夫。どうしても必要なときはロック用のテーブル一個用意してそのデータの行ロック(user_idと処理内容でuniqueにしておいてそのデータ行をロック)をかければいい(と教えてもらったけど結局使わなかったテクニック)。連続アタックなどは更新件数などで対策はできる(出来ないときも当然あると思う)。
あと、空更新、空削除はデッドロックになりやすいっぽい(ネクストインデックスロックのせいらしい)。
・・・一応ですがトランザクションは当然しようね。

16. トランザクション処理はネスト対応＆例外発生でロールバック
書いてる通り、関数一つ噛ませてそう出来るようにしましょう。

17. レスポンスデータに関しては暗号化してる場合はデータ圧縮後暗号化する
暗号化したデータは圧縮したところで対して圧縮されませんので、圧縮後暗号化しましょう。
エントロピーが高いか低いか忘れたけど、まあそのせいです。

18. レプリケーション遅延は諦めるのも手
諦めたｗ(マスターDB＋バックアップDBの構成になった)
まあ予算と規模と出来る方次第だけど・・・だったらRaidなどにして壊れないようにしたほうが楽なんじゃないっていう所感
ただし、マスターデータを格納してるDBはSLAVE数台用意してます(マスターデータ読み込み負荷分散・・・多少の遅延は許されるし許されないときはメンテ挟む)

19. CollectionのwhereやらfilterでとってきたものをそのままJSONにするのは注意
`$hoge = Collection::make(hoge)->where('hoge', 1);`
やら
`$hoge = Collection::make(hoge)->filter(...);`
で`$hoge`を`json_encode`したら連想配列になるので
`$hoge = $hoge->merge([]);`をしてからすると良い。

20. KVSも振り分けれるように
KVSも複数台用意して振り分けて負荷分散しないといけないのではじめからその設計でやる。
ただし、セッション管理用は特定の１台でやる必要がある(DBでいうシャード振り分け用テーブルと同じ感じ)

21. アイテム追加の際重複エラーで更新(UpdateInsert)
MySQLではON DUPLICATE KEY UPDATEというのがあるが、今回は利用しなかった(QueryBuilderで出来るかが不明)ため、まず追加してみて重複エラーが帰ってきた際は更新をかける処理をしてたのだけど、途中でselectでデータあるか調べてあれば更新、なければ追加するようにした(中途半端な状態になってて両方存在しちゃってる)。
あとUpdateで更新件数が0ならインサートにしてるところもあった。
どっちのほうがいいのかは結局わからないがたぶんselectしたほうが良さそう・・・？？？

22. DBやらLOGやらKVSのクラスタ
数年前にクラスタしたところで対して早くならないし、ややこしいし、バックアップ取るの面倒だし・・・なんやらで使わないほうがいいと言われたので使わなかったけど、どうなんだろう・・・私的には使ってみたいけど、痛い目あってるので次もたぶん使わない(お金に物を言わせてスペックのいいもの使うにしたいｗ)

23. Joinやら外部キーやらは使わない
~~外部キーよく知らない~~
シャーディングなので使わない。(今回は水平だけだったので使おうと思えば使えたけど、垂直になるかもしれないしね)

24. NOSQLに流すデータに関して
`{ ['item000' => 1, 'item001' => 1]}`はだめ(変動するキー値はだめ)
`{ [{'item_master_id' => 'item000', 'num' => 1}, {'item_master_id' => 'item000', 'num' => 2}]}`にしよう
クライアントのデコードでも対応できないらしいし、BigQueryで保存できないらしいし

25. ログは日付ローテート
ログをBigQueryにながす際td-agent使うと思いますが、BigQueryにおいては日付でテーブル分割手法を使う関係で、日付ローテートしておいたほうがリストアしやすい