nginx cache のディスク溢れ対策

nginx Advent Calendar 2015 の6日目です。

- http://qiita.com/advent-calendar/2015/nginx

# はじめに

nginx の cache でディスクが溢れては大変ですので、データ使用量の制限の仕方が気になりますよね。

- http://www.slideshare.net/Nginx/nginx-highperformance-caching/19
![image](https://qiita-image-store.s3.amazonaws.com/0/1798/6ba793f0-5f8b-3eb9-4a7a-7e822d7778a4.png)

この資料によると、以下のように消えるそうです。

- 期限の切れたキャッシュを消す (例では10分)
- max_size から溢れる場合は LRU で消す

# 疑問

- どういうタイミングで消すんだろう
- 具体的にどうやって消してるんだろう

といった所が気になるので、対応するソースを貼り付けます。

# nginx-1.9.1 のソース

## キャッシュを消すタイミング

タイマーで回してます。ただし消え具合によって間隔を増減します。

```c:src/os/unix/ngx_process_cycle.c
ngx_cache_manager_process_handler(ngx_event_t *ev)
{
    time_t        next, n;
    ngx_uint_t    i;
    ngx_path_t  **path;

    next = 60 * 60;

    path = ngx_cycle->paths.elts;
    for (i = 0; i < ngx_cycle->paths.nelts; i++) {

        if (path[i]->manager) {
            n = path[i]->manager(path[i]->data);

            next = (n <= next) ? n : next;

            ngx_time_update();
        }
    }

    if (next == 0) {
        next = 1;
    }

    ngx_add_timer(ev, next * 1000);
}
```

## キャッシュを消す処理

期限切れのファイルを削除して、max_size を超えてる場合は更に期限が切れてないファイルも削除します。

```c:src/http/ngx_http_file_cache.c
static time_t
ngx_http_file_cache_manager(void *data)
{
    ngx_http_file_cache_t  *cache = data;

    off_t   size;
    time_t  next, wait;

    next = ngx_http_file_cache_expire(cache);

    cache->last = ngx_current_msec;
    cache->files = 0;

    for ( ;; ) {
	ngx_shmtx_lock(&cache->shpool->mutex);

	size = cache->sh->size;

        ngx_shmtx_unlock(&cache->shpool->mutex);

	ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                       "http file cache size: %O", size);

        if (size < cache->max_size) {
            return next;
        }

        wait = ngx_http_file_cache_forced_expire(cache);

        if (wait > 0) {
            return wait;
	}

        if (ngx_quit || ngx_terminate) {
            return next;
        }
    }
}
```

## 期限切れのファイルを削除

ngx_http_file_cache_expire

queue に並んでいるキャッシュの最後のエントリから更新切れしてる分を削除します。

```c:ngx_http_file_cache.c
static time_t
ngx_http_file_cache_expire(ngx_http_file_cache_t *cache)
＜略＞
    for ( ;; ) {
＜略＞
        q = ngx_queue_last(&cache->sh->queue);

        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);

        wait = fcn->expire - now;

        if (wait > 0) {
            wait = wait > 10 ? 10 : wait;
            break;
        }
＜略＞
        if (fcn->count == 0) {
            ngx_http_file_cache_delete(cache, q, name);
            continue;
        }
```

## max_size を超えてる場合は更に削除

こちらは queue の最後のエントリを削除ですね。
LRU なのでアクセスされる度に queue の手前に並び直せば、それで良さそう。

```c:ngx_http_file_cache.c
static time_t
ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache)
＜略＞
    for (q = ngx_queue_last(&cache->sh->queue);
         q != ngx_queue_sentinel(&cache->sh->queue);
         q = ngx_queue_prev(q))
    {
        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);

        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                  "http file cache forced expire: #%d %d %02xd%02xd%02xd%02xd",
                  fcn->count, fcn->exists,
                  fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);

        if (fcn->count == 0) {
            ngx_http_file_cache_delete(cache, q, name);
            wait = 0;

        } else {
            if (--tries) {
                continue;
            }

            wait = 1;
        }

        break;
    }
```

# 最後に

つまり、タイマーで削除してて、サイズを超えてたら指定分に収まるまで削除という処理なので、確実に指定したサイズを超えないというのは無理に見えます。まぁでも、100%ギリギリディスクを使うと性能の問題が出てくるので(zfs なんて90%超えると途端に LA が跳ね上がるし)、普通に使う分にはそんなに気にしなくても良いでしょう。

あと、ソースを読むと改めて気になる所が出てきました。
LRU が queue なのは分かりやすいけど、expire の処理を queue でどうやって実装するの？
そもそも queue のランダムアクセスは良いとして真ん中から取り出しちゃって良いの？
queue エントリの count が大事っぽいけど、これって何？とか。

要望があれば、そのうち解説します。