memcached-cli で Memcached の Slabs Reassign と LRU Crawler を試してみる

この記事では、[Gotanda.pm #8](http://gotanda-pm.connpass.com/event/28535/) でデモをする予定だった内容の手順と結果を紹介します。
勉強会では、発表時間が足りずデモが実施できませんでした。

何かの参考になれば幸いです。

## はじめに

[memcached-cli](https://github.com/key-amb/perl5-App-Memcached-CLI) は筆者が開発した Perl 製の Memcached クライアントです。
テキストプロトコルでできる操作はすべて実行でき、TELNET よりもユーザーにやさしいインタフェースとなっております。
依存も少なく、Perl 5.8 以上であれば動作するように作ったつもりなので、Memcached をお使いの方はぜひ使ってみてください。

### Memcached Slabs Reassign と LRU Crawler について

デモの手順に入る前に、今回触れる Memcached の機能について、かんたんに説明しておきます。

そもそもの話ですが、Memcached はデータをサイズに応じて異なる Slab Class に配置します。
各 Slab Class には 1MB のページ単位でメモリが割当てられます。一度ページが確保されると、通常はたとえ当該 Class のデータ量が減ったとしても、ページが解放されることはありません。[^1]
Slabs Reassign はこの異なる Slab Class 間のページ割当てを変更できる機能です。

[^1]: 余ったページを自動で開放する Slabs Automove というオプションもあります。

次に LRU Crawler ですが、これは Expire したデータをなめてメモリ領域を解放してくれるものです。
こちらも、Expire したデータを `get` したり、同じ Slab Class にデータを `set` するなりしなければ、通常は解放されません。

## デモ手順と結果

環境は MacOSX で、Homebrew で Memcached 1.4.22 をインストールしています。

### 1. Memcached をメモリ少なめで起動

あふれるところが見たいので、Memcached の `-m` オプションを4MBとすごくメモリ少なめで起動します。
同時に、`-o` オプションで `slab_reassign` と `lru_crawler` を有効にしておきます。
また、後で `stats detail dump` の結果を見たいので、 `-D ":"` でネームスペースごとの統計情報収集機能を有効にしておきます。

```bash
% memcached -m 4 -o slab_reassign,lru_crawler -D ":"
```

### 2. 2種類のデータサイズでデータを生成し、あふれさせる

まずは memcached-cli で接続します。
さっきの memcached の起動コマンドではフォアグラウンドで立ち上がるので、別窓で操作して下さい。

```bash
% memcached-cli localhost
Type '\h' or 'help' to show help.

memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
```

サーバは起動したばかりなので、 `display` コマンドの結果は空です。
`randomset` コマンドでデータを生成して Memcached に `set` していきます。

```
// 500B のデータを "sample1" ネームスペース下に5000個生成
memcached@localhost:11211> randomset 5000 500 500 sample1
Random Generate. [....................]
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B         7s       3    5000     yes        0        0    0

// 1kB のデータを "sample2" ネームスペース下に1000個生成
memcached@localhost:11211> randomset 1000 1000 1000 sample2
Random Generate. [....................]
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B        29s       3    5000     yes        0        0    0
 12     1.2K         8s       1     885     yes      115        1    0

// 1kB のデータを "sample3" ネームスペース下に1000個生成
memcached@localhost:11211> randomset 1000 1000 1000 sample3
Random Generate. [....................]
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B        35s       3    5000     yes        0        0    0
 12     1.2K         2s       1     885     yes     1115        1    0
```

Slab Class #9 に3ページのメモリが確保されたので、 #12 には1ページしか確保できません。
\#12 には 885 個以上データを set することができず、あふれて eviction してしまいました。

### 3. データを全て無効化し、LRU Crawler で掃除する

`flush_all` コマンドで全てのデータを期限切れにします。
ただし、これだけでは確保されたページが解放されるわけではないので、Slab Class #12 には 885 個以上データを set することができません。

```
memcached@localhost:11211> flush_all
OK
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B       466s       3    5000     yes        0        0    0
 12     1.2K       433s       1     885     yes     1115        1    0
memcached@localhost:11211> randomset 1000 1000 1000 sample3
Random Generate. [....................]
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B       486s       3    5000     yes        0        0    0
 12     1.2K         2s       1     885     yes     1230        1    0
```

そこで、Slabs Reassign の出番です。
データは無効化したので、このままページ割当てを #9 => #12 に変えて行っても特に問題は無いのですが、今回はその前に LRU Crawler で #9 のデータをクリアしておきます。

```
// LRU Crawler を Slab Class #9 に対して手動実行する
memcached@localhost:11211> call lru_crawler crawl 9
OK
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B         0s       3       0     yes        0        0    0
 12     1.2K        18s       1     885     yes     1230        1    0

// Slabs Reassign でページ割当てを #9 => #12 に移していく
memcached@localhost:11211> call slabs reassign 9 12
OK
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B         0s       2       0     yes        0        0    0
 12     1.2K        38s       2     885     yes     1230        1    0
memcached@localhost:11211> call slabs reassign 9 12
OK
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B         0s       1       0     yes        0        0    0
 12     1.2K        42s       3     885     yes     1230        1    0
```

以上、Pages の数が #9 => #12 に移動しているのがおわかりいただけるでしょうか。

これで、 #12 には 3MB 分データを set できるはずです。
やってみましょう。

```
// ネームスペースを変えながら、1000個ずつ計3000個のデータ set を試みる
memcached@localhost:11211> randomset 1000 1000 1000 sample3
Random Generate. [....................]
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B         0s       1       0     yes        0        0    0
 12     1.2K         4s       3    1000     yes     1230        1    0
memcached@localhost:11211> randomset 1000 1000 1000 sample4
Random Generate. [....................]
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B         0s       1       0     yes        0        0    0
 12     1.2K        17s       3    2000     yes     1230        1    0
memcached@localhost:11211> randomset 1000 1000 1000 sample5
Random Generate. [....................]
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  9     600B         0s       1       0     yes        0        0    0
 12     1.2K        21s       3    2655     yes     1575       20    0
```

最後は少々あふれてしまいましたが、1ページしかなかった時と比べて、3倍のデータを格納することができました。

### ネームスペースごとの統計を見てみる

今回のデモの主要なところは以上ですが、ついでに memcached/memcached-cli の機能をもう少し紹介していきます。

`detaildump` コマンドでは、ネームスペースごとの統計情報を見ることができます。

```
memcached@localhost:11211> detaildump
PREFIX sample4 get 0 hit 0 set 1000 del 0
PREFIX sample3 get 0 hit 0 set 3000 del 0
PREFIX sample5 get 0 hit 0 set 1000 del 0
PREFIX sample2 get 0 hit 0 set 1000 del 0
PREFIX sample1 get 0 hit 0 set 5000 del 0
```

もし、Memcached 起動時に統計情報の収集を有効にしていなかった場合、memcached-cli の `detail on` コマンドで有効化できます。 
逆に、`detail off` コマンドで無効化もできます。

### データを dump/restore してみる

memcached-cli では今のところ(〜v0.94)、dump をファイル保存する際は、バッチモードで動かす必要があります。
ので、一旦 `\q` で対話モードを終えて下さい。

```
memcached@localhost:11211> \q
// シェル上で memcached-cli の dump_all コマンドを実行し、出力をファイルにリダイレクト
% memcached-cli localhost dump_all > dump-memd.txt
Dumping memcache contents
  Number of buckets: 1
  Number of items  : 2655
Dumping bucket 12 - 2655 total items
% ll -h dump-memd.txt
-rw-r--r--  1 key-amb  1522739515   2.6M  3 31 01:19 dump-memd.txt
% wc -l dump-memd.txt
    5310 dump-memd.txt
```

dump が取れました。
では、別窓で動かしている Memcached を再起動します。

```bash
% memcached -m 4 -o slab_reassign,lru_crawler -D ":"
^CSignal handled: Interrupt: 2.
% memcached -m 4 -o slab_reassign,lru_crawler -D ":"

// 別窓で memcached-cli を立ち上げる
% memcached-cli localhost
Type '\h' or 'help' to show help.

memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
```

再起動したので、データが空っぽになっています。
これに、先ほど取った dump データをリストアします。

```
memcached@localhost:11211> restore_dump dump-memd.txt
    ...loaded 200 lines
    ...loaded 400 lines
    ...loaded 600 lines
    ...loaded 800 lines
               :
    ...loaded 5000 lines
    ...loaded 5200 lines
Complete.
memcached@localhost:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
 12     1.2K         5s       3    2655     yes        0        0    0
```

無事にデータをリストアすることができました。

## 終わりに

以上、memcached-cliを用いて Memcached の Slabs Reassign と LRU Crawler の機能を体験してみました。

Memcached をお使いの方は、ぜひ memcached-cli をお試し下さい。

何かありましたら GitHub 上で Issue や Pull Request をお待ちしています。

- https://github.com/key-amb/perl5-App-Memcached-CLI

それでは、Enjoy!

## 参考

- [memcached/protocol.txt at master · memcached/memcached](https://github.com/memcached/memcached/blob/master/doc/protocol.txt)
  - 今回紹介した Memcached の機能の仕様を詳しく知りたい方はこちら。
- [Gotanda.pm #8 で memcached-cli について喋ってきた #gotandapm #memcached - weblog of key_amb](http://keyamb.hatenablog.com/entry/2016/03/31/080000)
  - Gotanda.pm #8 についての筆者のブログ記事です。発表したスライドも掲載しています。

## 脚注