GitLabのメモリ使用量を約2GB抑えて安定稼働させるために行ったメモリチューニングの方法

# はじめに

私は社内でプロジェクトのGitLabのホスティング支援を行っています。
GitLabを個人や検証で使ってもらう分には問題なかったのですが、実プロジェクトで使ってもらうと様々な問題が発生しました。

## GitLabはメモリの使用量が多く安定しない

一番困った問題としてGitLabのメモリ使用量が多く、そして安定しないことでした。
GitLabは **最低4GB** の空きメモリを用意することを推奨しています。(参考：[Requirements - GitLab Documentation](https://docs.gitlab.com/ce/install/requirements.html#memory))
OSや他のプロセスが使用するメモリを考えると、8GB以上のメモリを積んでいるサーバが望ましいです。
しかし、小規模なプロジェクトですとそのスペックのサーバを用意することが難しく、
8GBを用意できたとしても、運用しているとサーバのメモリ不足が発生（[後述](https://qiita.com/k_nakayama/items/9f083a4700915d02104a#%EF%BC%93postgresql%E3%81%AEprepared-statements%E3%81%AE%E3%83%AA%E3%83%BC%E3%82%AF%E3%82%92%E8%B5%B7%E3%81%8D%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B)していますが **GitLabの障害によりメモリリークが発生** ）してしまいGitLabのレスポンスが悪い状態でした。

## GitLabのメモリチューニングを行いメモリ使用量を約2GB抑えて安定稼働させることができた！

そのため、GitLabのメモリ使用量を抑える方法を調査し、現在はメモリ使用量を **4.4GB（チューニング前） → 2.2GB（チューニング後）** に抑えて安定稼働させることができるようになりました。
プロジェクトで長期運用しても、安定してGitLabを稼働できるようになりました。
今回は私がプロジェクトにて行ったGitLabのメモリチューニングの方法を共有します。

# GitLabの推奨メモリサイズについて

GitLabの推奨メモリサイズは現在（2018/03/22）下記の通りです。
引用：https://docs.gitlab.com/ce/install/requirements.html#memory
> You need at least 4GB of addressable memory (RAM + swap) to install and use GitLab! The operating system and any other running applications will also be using memory so keep in mind that you need at least 4GB available before running GitLab. With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
> 
> - 1GB RAM + 3GB of swap is the absolute minimum but we strongly advise against this amount of memory. See the unicorn worker section below for more advice.
> - 2GB RAM + 2GB swap supports up to 100 users but it will be very slow
> - 4GB RAM is the recommended memory size for all installations and supports up to 100 users
> - 8GB RAM supports up to 1,000 users
> - 16GB RAM supports up to 2,000 users
> - 32GB RAM supports up to 4,000 users
> - 64GB RAM supports up to 8,000 users
> - 128GB RAM supports up to 16,000 users
> - 256GB RAM supports up to 32,000 users
> - More users? Run it on multiple application servers

私が注目したのは **4GBは100ユーザで使う時の推奨** だということです。
小規模なプロジェクトだと10~30ユーザぐらいだったため、若干オーバースペックです。
また、100ユーザ前後であれば4GBさえ用意できれば問題ないと言えます。
その点に注目してメモリチューニングを行いました。

# GitLabのメモリチューニングの方法

## １．UnicornのWorkerプロセスの起動数を3つに減らす

サーバのCPUコア数が3以上の場合に有効な設定になります。GitLabはUnicornのWorkerプロセスの起動数をデフォルトでは「サーバのCPUコア数 + 1」で設定します。
参考：https://docs.gitlab.com/ce/install/requirements.html#unicorn-workers
例えば、4コアのサーバの場合は5つのWorkerプロセスがGitLabコンテナ内で起動します。Workerプロセスは **最大512MB** のメモリを使用する設定になっており、5つのWorkerプロセスが起動した場合は **最大2.5GB** を使用します。 このWorkerプロセスの起動数を減らし、メモリ使用量を削減することがこの設定の目的です。GitLabはUnicornのWorkerプロセスを最低3つは起動することを推奨しているため3を設定します。

```:gitlab.rb
unicorn['worker_processes'] = 3
```

## ２．PostgreSQLのshared_buffersのサイズを1GBに減らす

サーバのメモリサイズが5GB以上の場合に有効な設定になります。GitLabはPostgreSQLのshared_buffersのサイズをデフォルトでは「サーバのメモリサイズ / 4」で設定します。 
参考：https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb#L410
例えば、8GBのメモリサイズのサーバの場合はshared_buffersが **2GB** に設定されたPostgreSQLがGitLabコンテナ内で起動します。 このshared_buffersのサイズを減らし、メモリ使用量を削減することがこの設定の目的です。GitLabは100ユーザだと4GBのメモリを推奨しているため、その4分の1である1GBを設定します。

```:gitlab.rb
postgresql['shared_buffers'] = "1024MB"
```

## ３．PostgreSQLのPrepared Statementsのリークを起きないようにする

GitLabのバージョンが10.2.0未満の場合に有効な設定になります。Gitlabが使用するrailsのバージョンにPostgreSQLの **メモリリークを引き起こす障害** があります。

- GitLabのissues：https://gitlab.com/gitlab-org/gitlab-ce/issues/25505
- railsのissues：https://github.com/rails/rails/issues/14645

要約すると下記のような障害です。

- GitLabはrailsの4.x系を利用している
- rails 4.x系 は rails経由でPreparedStatementからクエリを作ると、可変部分を正しく可変部分として取り扱わない(固定文字列としてSQLを作ってしまう)バグがある
- PreparedStatememtを何回も使いまわして、可変部分の値を変更したクエリを作ると、何個も似たようなSQLを生成してキャッシュしてしまい、メモリをあほみたいに使う
- GitLabはこのrailsのバグを踏んでしまい、結果メモリリークが発生している

この障害を回避するため、GitlabのPreparedStatementの使用設定を無効化します。

```:gitlab.rb
gitlab_rails['db_prepared_statements'] = false
```

PostgreSQLのプロセスのメモリ使用量推移 設定前
![before.png](https://qiita-image-store.s3.amazonaws.com/0/117949/a3c39baf-7d14-ab60-e8ec-8ba292594d8d.png)

PostgreSQLのプロセスのメモリ使用量推移 設定後
![after.png](https://qiita-image-store.s3.amazonaws.com/0/117949/d3654680-4298-ab40-c86d-7a9e1754fd0d.png)

# まとめ

上記の設定を行うことで、最終的にGitLabのメモリ使用量を約2GB抑え、かつメモリリークも起きないようにできました。今回は100ユーザの場合は想定して値を設定しましたが、ユーザ規模に沿って値を変更することでどのような環境でも応用可能だと思います。是非、参考にして一度設定してみてください。

※ コメントや編集リクエスト歓迎です！「他にもこういうメモリ設定があるぞ！」等ありましたら教えていただけると嬉しいです。

# 参考：サーバのメモリ枯渇により問題が発生しているか確認する方法

システムログを確認し、DockerコンテナのヘルスチェックやOOMによるプロセスのKillが発生していないか確認します。 `/var/log/messages` を"Health check"と"Killed"でgrepして表示します。

```
grep -e 'Health check' -e 'Killed' /var/log/messages
Jan 22 11:27:53 XXX dockerd: time="2018-01-22T11:27:53.104123235+09:00" level=warning msg="Health check for container 862917efbea54276fd1ba9789d8048caf4919256249a84dad4e34f2b0bb7b077 error: context cancelled"
Jan 22 11:27:53 XXX kernel: Killed process 26246 (yes) total-vm:4376kB, anon-rss:76kB, file-rss:0kB, shmem-rss:0kB
```

## Dockerコンテナのヘルスチェック

Dockerコンテナのヘルスチェックとは、Dockerコンテナやコンテナ内のサービスが正しく動作しているかを監視するDockerの機能です。 Dockerコンテナのヘルスチェックのログが出ている場合、何らかの原因でコンテナが異常な状態になっている可能性が高いと判断できます。 特にGitLabコンテナはメモリを多く必要とするため、GitLabコンテナでヘルスチェックが出ている場合はメモリ枯渇が原因の可能性が高いです。

## OOM Killer
Linuxではメモリが不足してシステムが停止する恐れがある際、メモリリソースを多く消費しているプロセスを強制的にkillします。この仕組みをOOM Killerといいます。 OOM Killerが動作している場合、メモリ枯渇が起きていると判断できます。