キャッシュについてまとめ

キャッシュについてまとめ

基本

リクエストが増えるとIO待ちが増えて処理時間の低下を引き起こす 多くのWebサービスでは少数のファイルにリクエストが集中する この参照頻度の偏りを利用する - 頻度の多いファイル⇒高速なデバイス - その他⇒ふつうのデバイ

用語 - キャッシュサーバ - オリジンサーバ

キャッシュサーバとしての設定と クライアントにキャッシュさせる場合の設定がある。 (ヘッダフィールドの指定等)

基本的にキャッシュとオリジナルコンテンツと同じサーバにある場合その有効性は限定的。 (コンテンツがすべてメモリのページキャッシュに乗っている場合キャッシュでなくてもメモリから高速に応答できるため)

キャッシュに関するディレクティブ

ngx_http_proxy_moduleに実装されている (proxyと書いてあるがnginxのみの静的ファイル配信の際にもこれを用いる)

proxy_cache_path 保存先パス keys_zone=キーゾーン名:サイズ

キャッシュの保存先とキーゾーンの設定

proxy_cache キーゾーン名|off

キャッシュに利用するキーゾーンを指定する

proxy_cache_key キャッシュキー文字列;

キャッシュのキーに使用する文字列を指定する

キャッシュの基本

proxy_cache_pathによってキャッシュファイルの保存先を指定する。 キーゾーン名やその容量などのパラメタを指定する。 どのキーゾーンに保存するかはproxy_cacheディレクティブを使用する。 proxy_cache_keyでキャッシュのキーとして使用する値を使用する。 子の値をキーとしてキャッシュの同一性を確認する。 (デフォルトはスキーム、プロキシ先のホスト名、リクエストされたURIをキーに使用する)

補足

  • プロセス間でキャッシュを共有するためキャッシュのメタデータを共有メモリに保存する。
  • ゾーンの必要容量=キャッシュの総容量÷1ファイルの平均サイズ*128B
  • キャッシュファイルの保存ディレクトリの内部階層の指定をできる

キャッシュマネージャの制御

proxy_cache_pathディレクティブのloader_files, loader_sleep, loader_threasholdでキャッシュマネージャを制御できる

有効期限の指定

  1. キーゾーンごとのキャッシュ有効期限を設定する
  2. オリジンサーバのレスポンスヘッダに有効期限を指定する
  3. レスポンスヘッダで指定されなかった場合のhttpステータスコードごとの有効期限を指定する

  4. キーゾーン
    proxy_cache_pathにinactiveパラメータを指定する
    指定した有効期限より長くリクエストされないとキャッシュファイルが削除される。
    デフォルト値は10分間

2.レスポンスヘッダ - X-Accel-Expires - Cache-Control - Expires これらのヘッダフィールドをオリジンサーバからのレスポンスに付加することでキャッシュの有効期限を指定できる 3.ステータスコード毎に有効期限を設定 proxy_cache_valid [HTTPステータスコード] 有効期限; これを設定することでステータスコードごとに有効期限を指定できる

キャッシュ条件の指定

nginxはデフォルトでGET, HEADメソッドのレスポンスをキャッシュする。 (上記の設定をした場合の話) - GET,HEAD以外もキャッシュしたい場合 proxy_cache_methodsディレクティブを使う - URLでキャッシュするか決めたい場合locationディレクティブを使う - それ以外の時proxy_cache_bypassとproxy_no_cacheを使う proxy_cache_bypass 1 のときレスポンスをキャッシュから取得しない proxy_no_cache 1 の時 レスポンスをキャッシュに保存しない (変数を使って1 0 を切り替える)

一時ファイルの保存先の指定

nginxはキャッシュするレスポンスを一旦一時ファイルに保存し、その後指定したフォルダに移動する そのため、一時保存先と保存先が違うデバイスだとファイルをコピーする必要がありIOが発生する この一時ファイルの保存先はデフォルトはコンパイル時に指定 明示的に指定する場合proxy_temp_pathを使用する proxy_temp_path 一時ファイルの保存先パス

キャッシュ更新負荷の削減

キャッシュを利用した場合リクエスト数を減らすことができる しかし失効した瞬間にリクエストがバーストする。 キャッシュ失効は大きなネットワーク負荷やディスクIOの原因になりうる。

  • proxy_cache_revalidate on|off; キャッシュ更新の際にオリジンサーバに if-Modifed-Since, If-None-Matchヘッダフィールドで送って 更新されていなかった場合304が帰ってくるので、帯域負荷を抑えられる。

  • proxy_cache_lock on|off もう一つの方法としてキャッシュロックがある。 これを有効にすると同じURIに対してのアクセスを一つ目以外ブロックする。 これによりアップストリーム鯖への問い合わせ回数を制限できる このブロックのタイムアウトはproxy_cache_lock_timeoutで指定できて、デフォルトは5秒になっている。

    クライアントにキャッシュさせる場合の設定


クライアントにレスポンスをキャッシュさせるための設定はヘッダフィールドに設定を付加して行う。

  • add_header ヘッダフィールド名 値 [always] alwaysを付加すると404,500と行ったエラーの場合にもヘッダフィールドを付加できる。 (ヘッダの追加のみ行うためアップストリームで設定しているフィールドをaddしてしまうと重複して出力される) (重複されている場合の挙動は定義されていないため不具合の原因になる可能性がある)

  • 関係するヘッダフィールド

    • X-Accel-Expires 秒数 キャッシュサーバのnginxがコンテンツキャッシュの有効期限として解釈する (一般のブラウザでは解釈されない!!!)
    • Cache-Control
    • Expires HTTp/1.1ではこの二つのヘッダでキャッシュの制御を行う
  • Expiresヘッダの制御

    expires [modified] 有効期限;
    expires epoch|max|off;
    

    HHTP/1.1に準拠するヘッダはexpiresディレクティブで行う

    expires 10d; #10日間キャッシュを行う。
    

    デフォルトではリクエストされた時点の時刻を起点に計算される modifiedをつけるとファイルの最終更新時刻からの有効期限になる。

    expires modified 5d; #更新日時から5日までキャッシュされる
    

    epochを指定した場合Expiresヘッダフィールドの値が1970/00:00:00になる(最古の値) maxを指定した場合もっとも未来の値になる。 常に無効にしたい場合epoch、常に有効にしたい場合maxに指定する 通常はexpiresでExpriresを設定するだけで期限を設定できるが、他の細かい制御をするには Cache-Controlヘッダが必要なため場合によってはadd_headerでCache-Controlを指定する必要がある。

  • Cache-Controlヘッダ

    • max-age=秒数
    • no-cache #キャッシュしない
    • max-age=315360000 #未来の最大値(expiresのmaxと同じ)
    • private #キャッシュの共有を禁止(ブラウザレベルでしかキャッシュしないようにする)

    条件付きリクエスト


ブラウザ、キャッシュサーバのキャッシュ以外にトラフィックを最小化する手法として条件付きリクエストが存在する。 キャッシュの期限が切れた際にキャッシュが有効か確認をし、無効であれば新しいレスポンスを受け取る方法。

条件付きリクエストには以下のヘッダが付与されている - If-Modified-Since: "レスポンスのLast-Modifiedヘッダ" - If-None-Match: "レスポンスのETagヘッダフィールド"

サーバはこれらの値を確認し送信するファイルの内容が前回のキャッシュと変更されていないと判断できれば、 304(Not Modified)を送信しファイル送信を省略できる。 もし変更されていれば通常通りファイルの内容を返す。

一般的なブラウザは条件付きリクエストに対応しており、nginxでもproxy?cache_revalidateを使用すれば、 条件付きリクエストによるキャッシュの有効性確認ができる。

Last-Modified

リソースの最終更新日時を指定する nginxが静的リソースを配信する場合は自動的に付与される。 Webアプリケーションの出力などには自動的には付与されない。

ETag

リソースを一意に識別できるユニークな文字列を指定する nginxではetagディレクティブで制御できる。

etag on|off; #(デフォルトon)

nginxではファイルの更新日時とファイルサイズから自動的に計算される。 基本的にはEtagを増やした状態で構わない

秘伝のタレの解説

 location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
     try_files $uri @app;
     expires max;
     add_header Pragma public;
     add_header Cache-Control "public, must-revalidate, proxy-revalidate";
     etag off;
 }

画像ファイルのとき パスに存在すればそれを配信 クライアント側で永遠にキャッシュする privateの逆 privateの逆 必ずサーバに再認証を行う キャッシュサーバに対して再認証を要求 etagをつけない(←!?

etagがなくても条件付きリクエストをやるのかもしれない

nginxをよむ

ソースコードリーディングが何ぼのもんじゃい

core/nginx.c

  if (ngx_process == NGX_PROCESS_SINGLE) {
        ngx_single_process_cycle(cycle);

    } else {
        ngx_master_process_cycle(cycle);
    }

os/ngx_process_cycle.c

 ngx_start_worker_processes(cycle, ccf->worker_processes,
                               NGX_PROCESS_RESPAWN);
    ngx_start_cache_manager_processes(cycle, 0);

//後ろの方にはシグナルとかrestartの処理が書いてあった

static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
 ngx_spawn_process(cycle,ngx_worker_process_cycle,(void *) (intptr_t) i, "worker process", type);

}

os/unix/ngx_process.c

 ngx_pid_t
  ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
      char *name, ngx_int_t respawn){

//省略

    pid = fork();

    switch (pid) {

    case -1:
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
        ngx_close_channel(ngx_processes[s].channel, cycle->log);
        return NGX_INVALID_PID;

    case 0:
        ngx_pid = ngx_getpid();
        proc(cycle, data);
        break;

    default:
        break;
    }

//省略

}

procが関数として呼び出されている
procの型はngx_spawn_proc_pt(なんだこりゃ

os/unix/ngx_process.h

typedef void (*ngx_spawn_proc_pt) (ngx_cycle_t *cycle, void *data);

ngx_spawn_proc_ptは上記二つの引数をとる関数の型

呼び出し元を見るとos/unix/ngx_process_cycle.cのngx_worker_process_cycle

os/unix/ngx_process_cycle.c


static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
//~
 ngx_process_events_and_timers(cycle);
//~
}
./event/ngx_event.c:194

void
ngx_process_events_and_timers(ngx_cycle_t *cycle){
//~
  (void) ngx_process_events(cycle, timer, flags);
//~
}
./event/ngx_event.h:408

#define ngx_process_events   ngx_event_actions.process_events

わった

検索結果

[root@hote src]# grep -rne ngx_event_actions ./
./event/modules/ngx_win32_select_module.c:106:    ngx_event_actions = ngx_select_module_ctx.actions;
./event/modules/ngx_devpoll_module.c:186:    ngx_event_actions = ngx_devpoll_module_ctx.actions;
./event/modules/ngx_epoll_module.c:368:    ngx_event_actions = ngx_epoll_module_ctx.actions;
./event/modules/ngx_eventport_module.c:279:    ngx_event_actions = ngx_eventport_module_ctx.actions;
./event/modules/ngx_kqueue_module.c:224:    ngx_event_actions = ngx_kqueue_module_ctx.actions;
./event/modules/ngx_poll_module.c:96:    ngx_event_actions = ngx_poll_module_ctx.actions;
./event/modules/ngx_select_module.c:105:    ngx_event_actions = ngx_select_module_ctx.actions;
./event/ngx_event.c:44:ngx_event_actions_t   ngx_event_actions;

環境と方式によって違うメソッドを実行している

/event/modules/ngx_eventport_module.c:279

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
//~
ngx_event_actions = ngx_epoll_module_ctx.actions;
//~
}


/event/ngx_event.c

static char *
ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_event_conf_t  *ecf = conf;

#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
    int                  fd;
#endif
    ngx_int_t            i;
    ngx_module_t        *module;
    ngx_event_module_t  *event_module;

    module = NULL;

#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)

    fd = epoll_create(100);

    if (fd != -1) {
        (void) close(fd);
        module = &ngx_epoll_module;

    } else if (ngx_errno != NGX_ENOSYS) {
        module = &ngx_epoll_module;
    }
}
|||<
切り替えをしているのはこの部分
(パラメータで切り替えるのになぜマクロ?)
(気力が失せた)


module_epoll
>||
ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
#if (NGX_HAVE_EVENTFD)
        ngx_epoll_notify,                /* trigger a notify */
#else
        NULL,                            /* trigger a notify */
#endif
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};



このうちのactionsに対応する部分を実行している

events.h

typedef struct {
    ngx_str_t              *name;

    void                 *(*create_conf)(ngx_cycle_t *cycle);
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);

    ngx_event_actions_t     actions;
} ngx_event_module_t;

actionsに相当するのは関数のリストであるぞ

このリストの中でメインループなのはprocess_events
なかでepoll掘って完了したイベントごとに何かしている感じ