【Linux】スワップファイルでサーバのメモリ不足を補う

## はじめに
さくらVPSの最小プランでインフラを構築する際に、メモリ不足で色々と不具合が起こった際にスワップファイルを利用することで無理矢理解決したので、メモとして残します！

## VPSの性能
- **OS**: Debian 12  
- **アーキテクチャ**: amd64（64ビット版）  
- **リージョン**: 東京第2（データセンターの場所）  
- **メモリ**: 512MB  

## インフラ構成
すごくざっくりですが、こんなイメージです。
```
    さくらVPS
 ┌───────────────┐
 │               │
 │   ┌───────┐   │
 │   │ MySQL │   │
 │   └───────┘   │
 │               │
 │   Docker      │
 │ ┌───────────┐ │
 │ │           │ │
 │ │  Rails    │ │
 │ │ (API      │ │
 │ │  Server)  │ │
 │ └───────────┘ │
 └───────────────┘

接続：Rails (Docker) ⇔ MySQL (VPS上)
```

## MySQLのインストール、起動時に問題発生
上記のインフラを構築するため
VPSを借りて、MySQLをaptでインストールし、起動する際に下記のエラーが発生しました。
```bash
sudo apt install mysql-server
```

```bash
dpkg: error processing package mysql-community-server (--configure): installed mysql-community-server package post-installation script subprocess returned error exit status 137
```

137のexit statusコードが吐き出されているので、まずこれが何か調べてみます。

## 137 exit statusコードとは？

https://www.stackstate.com/blog/understanding-and-troubleshooting-out-of-memory-error-code-137/#what-is-exit-code-137

> Exit code 137 typically indicates that a process inside a container was killed by the system due to an out-of-memory (OOM) condition. When the operating system detects that it's running out of memory, it invokes the OOM killer, which terminates processes to free up memory. In the context of containers, this often results in the following error messages:

> 終了コード137は通常、コンテナ内のプロセスがメモリ不足（OOM）状態のためにシステムによって強制終了されたことを示す。

どうやらメモリ不足により、終了を表すコードのようです。

## topコマンドで確認してみる
実行中のメモリの使用量をtopコマンドで見てみました
```bash
top
```

```
MiB Mem :    457.6 total,      6.2 free,    444.9 used,     16.6 buff/cache
```
6.2MBしか余裕がないですね笑
この後、137 exit codeが吐き出されるので、メモリ不足には間違いなさそうです。

## 解決方法
一番簡単な解決方法はスケールアップすることです。
ただ、当然料金が嵩みます。
利用者の限られるアプリに使用するため、できるだけコストは抑えたいです。
色々と調べた結果、処理速度等は物理メモリに比べて低下しますが**スワップ領域**を使用して、ハードディスクの一部をメモリのように扱うことで不足を補えるようなので、そのアプローチをとってみることにします。

https://docs.redhat.com/ja/documentation/red_hat_enterprise_linux/7/html/storage_administration_guide/ch-swapspace#ch-swapspace

> Linux の スワップ領域 は、物理メモリー (RAM) が不足すると使用されます。システムに多くのメモリーリソースが必要で、RAM が不足すると、メモリーの非アクティブなページがスワップ領域に移動します。

使われていないメモリの内容を割り当てたハードディスクの領域(スワップ領域)に移動させることで、RAMの不足を補うといった仕組みのようですね。

### スワップ領域の追加
さくらVPSの公式にやり方が記載されていたのでこちらを参照しました。

https://manual.sakura.ad.jp/vps/os-packages/add-swapfile.html

#### 1.空き容量の確認
```bash
df -h /
```

いくつかファイルシステムが表示されますが`/`(ルート)がマウントされている/dev/vda2の空き容量を確認します。
Availが18Gなので十分な容量があることが確認できました。
```bash
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda2        25G  3.7G   18G  15% /
```

---

#### 2.スワップファイルの作成
```bash
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
```
- `sudo`：管理者権限で実行
- `dd`：指定したサイズのファイルを作成するコマンド
- `if=/dev/zero`：`/dev/zero`はデバイスファイルで、ゼロ（0）が無限に出力される。これによりゼロで埋められたデータをスワップファイルに書き込む
- `of=/swapfile`：出力ファイルにスワップファイルを指定しています
- `bs=1M`：ブロックサイズを1MBに設定
- `count=2048`：上記で指定した1Mのブロックの数を2048に設定し、合計2GB（1MB * 2048 = 2GB）のスワップファイルを作成

これで`/`直下に`swapfile`が作成されます。
0で埋めてスワップ領域を確保するイメージですね。

#### 3. スワップファイルのアクセス権限の設定
```bash
sudo chmod 600 /swapfile
```
これで作成したswapfileに600の権限を設定します
600はファイルの所有者にのみ読み書き許可を付与する設定です
所有者以外はスワップファイルを読み書きできなくすることで、セキュリティを強化しています。

Linuxの権限については、下記の記事にまとめています！

https://qiita.com/rgj0830/items/552b0f96c26c963f1ee3

#### 4. スワップファイルの初期化
作成しただけでは、まだ使えません。
作成したファイルをスワップファイルとして認識させる必要があります
mkswapコマンドが指定したファイルをスワップ領域として初期化するコマンドになります
引数に先ほど作成したswapfileのパスを指定します。

```bash
sudo mkswap /swapfile
```

このコマンドでスワップファイルに必要なメタデータが設定され、システムがスワップ領域として認識できるようになります

#### 5.swapファイルの有効化
次に認識させたswapファイルを有効化します
```
sudo swapon /swapfile
```
これでメモリが不足した際に、作成したswapfileを使用するようになります。

設定ができているか確認
```bash
free
```

```bash
Swap:        2097148           0     2097148
```
上記のように　Swap領域が表示されるようになっていれば完了です！

#### 6.バックアップの作成
```bash
sudo cp -p /etc/fstab /etc/fstab.org
```

#### 7.swapの設定を永続化
```bash
echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab:
```

ehcoで出力した`/swapfile none swap sw 0 0`
設定ファイルに記載したい文字列をteeコマンドに渡して`/etc/fstav`ファイルの末尾に追加しています。

設定の意味を下記の通り
- /swapfile: スワップ領域として利用するファイル
- none: デバイス名ではなくファイルを使用するため、none を指定
- swap: スワップ領域であることを示す
- sw: スワップ領域に適用するオプション
- 0 0: ディスクのバックアップやチェックの設定（スワップ領域には適用しないため 0 を指定）

#### 再度MySQLのインストール、起動に挑戦
念の為、一度アンインストールしてから行います。

関連するパッケージ、依存関係の削除
```bash
sudo apt-get remove --purge mysql-server mysql-client mysql-common
sudo apt-get autoremove
sudo apt-get autoclean
```

パッケージリストを更新
```bash
sudo apt-get update
```
再度インストール
```bash
sudo apt-get install mysql-server
```

```bash
sudo systemctl status mysql
```

起動状態の確認
```bash
● mysql.service - MySQL Community Server
     Loaded: loaded (/lib/systemd/system/mysql.service; enabled; preset: enabled)
     Active: active (running) since Fri 2024-10-25 08:40:08 JST; 0 days ago
       Docs: man:mysqld(8)
             http://dev.mysql.com/doc/refman/en/using-systemd.html
    Process: 29759 ExecStartPre=/usr/share/mysql-8.0/mysql-systemd-start pre (code=exited, status=0/SUCCESS)
   Main PID: 29795 (mysqld)
     Status: "Server is operational"
      Tasks: 41 (limit: 504)
     Memory: 50.4M
        CPU: 29min 27.873s
     CGroup: /system.slice/mysql.service
             └─29795 /usr/sbin/mysqld

```

無事インストールし、起動することができました！

## まとめ
スワップファイルを作成して有効化することで、なんとかメモリ不足をカバーでき、MySQLも無事にインストール・起動できました。少し手間はかかりますが、物理メモリが増やせない環境での応急処置の一つの方法として勉強になりました！