案件で得た小ネタのような知見

ある案件の機能開発、保守をしていましたが、
サービス終了という事で小ネタのような知見を共有しようと思います。
~~知見というかただの小ネタだった事に気付いた。~~
パフォーマンスチューニングだったり外部システム連携だったり色々やりました。

# しょうもない話
2017年3月くらいからこの案件に関わり始めて、9ヵ月も経ちました。
案件の終わりがサービス終了だととても微妙な気分です。
関わり始めた頃は**Go難しすぎる、コンパイル一生通らない**とか思っていましたが、
今は**学習コスト低いしそれなりに速度も出るし書きやすい**と思ってたり考えがわりと変わりました。
gitlabとかbacklog見直しながら書いてますが、思ったのは成長出来る案件ではあったけどやっぱり保守だと技術的な中身が薄いなあと思いました。
今の環境だと周りもあまり技術に興味ある人が多いわけではないし、案件的にも成長が厳しいと感じているので転職がしたいです。
*どこか誘ってください*

# システム概要
ECシステムと連携し、顧客情報や購買情報、アクセス情報に基づいてメール配信やプッシュ通知を送ったりするシステムです。
主に触っていたのがサーバなので、フロントはあんまり触れないでおきます。

|ECシステム|連携バッチ|Nginx|このシステム|
|:------------------:|:------------------:|:------------------:|:------------------:|
|PHP|PHP| =>| Node|
||Go||Go|
|MySQL | || MongoDB|

# 外部連携(SendGrid)

## レスポンスの仕様が変わって動かなくなる
Marketing Campaignsという機能を使っていて、メールが送られていないという連絡を受けてから確認したところ
レスポンスの形式が変わっていた事が原因でした。
(SendGridのサポートにも確認済み)
外部システムに連携する際にはこういう事にも注意しようと思いました。

# GO

## Too many open files
プッシュ通知に外部システムを使っていて、外部システムにリクエストを送っている途中でこのエラーが出るみたいな事がありました。
以下のように変えたらエラーが出る事はなくなったので、`defer`を使っていた事で起きる問題でした。

- 変更前

```Go
resp, err := client.Do(req)
if err != nil {
    panic(err)
}
defer resp.Body.Close()
// 処理
```

- 変更後

```Go
resp, err := client.Do(req)
if err != nil {
    panic(err)
}
//resp.Bodyから必要な値だけ取得
resp.Body.Close()
//処理
```

## jsonでfalseとか0とかを扱う時
EC側のデータをJSONにして、Goで受け取り、データ更新を行っていました。
EC側であるカラムの値をfalseにしても更新されないという事がありました。
型をポインタ型にするだけで扱えるようになります。

- 変更前

```Go
flag bool `json:"flag,omitempty" bson:"flag,omitempty"`
```

- 変更後

```Go
flag *bool `json:"flag,omitempty" bson:"flag,omitempty"`
```

# Node

## pm2.logを見ると勝手に再起動している
原因は**unhandledRejection**でした。
該当箇所を修正したら起きなくなりました。

## フロントで使うデータは絞るべき
思い返すとわりと当たり前のことだけど。
顧客等の検索結果をCSVに出力する機能があります。
CSVに出力するのは3000件までと決まっているのに、
サーバから10万件返してフロントで3000件にしていました。
サーバ側で3000件までにしたら早くなりました。

## データを取ってくるのが遅い
coを使っている前提ですが、並列にする事で早くできました。

- 変更前

```js
let list = [];
for (let i = 0; i < length; i++) {
    let db_data = yield get() //MongoDBから取ってくる
    list = list.concat(db_data)
}
```

- 変更後

```js
let list = [];
for (let i = 0; i < length; i+=3) {
    let db_data = yield [get(), get(), get()] //並列にする
    for (let j = 0; j < db_data.length; j++) {
        list = list.concat(db_data[j])
    } 
}
```

## 時間のパディングにlodashを使う事で起きていた問題
これについてはテストしてたの？という感じです。
lodashには`pad`と`padStart`という関数があり、以下のような動作をします。
N日後のM時という動作を元々`pad`でやっていたのですが、以下のように90となり、
3日後の18時という動作になり問題になりました。
`padStart`を使う事で解決しました。

```js
const {pad, padStart} = require("lodash");

pad("9", 2, "0") //90
padStart("9", 2, "0") //09
```

## Can't set headers after they are sent.
原因はそのままで、以下の修正で起きなくなりましたが、
対応としては微妙だったなあという感じがあります。

- 変更前

```coffeescript
if flag
  return res.status(204).json().end()
#処理
res.status(200)
```

- 変更後

```coffeescript
if flag
  return
#処理
res.status(200)
```

# MongoDB

## 勝手に落ちる
原因は`Out of memory: Kill process 25921 (mongod)`でした。
インデックス貼りまくったり、クエリ投げまくると起きます。
システム側の修正もありましたが、結局はメモリを4GBから8GBに増やす事で解決しました。

## レプリケーション時に落ちる
原因ちゃんとわかっていませんが、インデックスの構築中に落ちました。
再起動させると同期できていたデータ全部消されて最初からになります。
これもメモリが少ないのが原因だと思っています。

## 普通に遅い
SSDにしたら早くなりました。

## 100万件程度のデータを取ってくるのが遅い
100万件を一回で取ってくるよりも10万件を10回の方が早かったです。

# MySQL

## LEFT JOINが遅い
外部結合なので遅いです。
解決方法は色々ありますが、unionを使い別クエリに分ける事で早くする事ができました。
約6秒から約0.3秒くらいまで改善しました。

# Nginx

## URLが長すぎてエラー(414)
原因はそのままです。
`large_client_header_buffers 4 256k;`を設定に追加で解決しました。

# 終わり