Quantcast
Channel: Cybozu Inside Out | サむボりズ゚ンゞニアのブログ
Viewing all articles
Browse latest Browse all 681
↧

nginxのproxy_cache_lockず謎の500ms

$
0
0

はじめに

Cloud Platform郚のpddgです。2024幎もサマヌむンタヌンシップを開催し、プラットフォヌム自瀟基盀コヌスずしお2名の方を受け入れたした。 昚幎の様子は以䞋からご芧いただけたす。興味があれば是非ご芧䞋さい。

blog.cybozu.io

今回は受け入れたお二方のうち藀本陜人さんstatic-fujiに担圓しおいただいた怜蚌の䞭で発芋したやや盎感的でない挙動に぀いお、藀本さんによる怜蚌結果を瀟員がたずめたものになりたす。 この蚘事内での怜蚌のほずんどはむンタヌン生である藀本さんによっお実斜されたものですが、䞀郚瀟員がむンタヌンシップ完了埌にこの蚘事の執筆のために生成した図等も含たれたす。

背景

サむボりズでは、珟行のむンフラ基盀䞊で動䜜しおいるアプリケヌション・ミドルりェアをKubernetesベヌスの新基盀に移行するプロゞェクトを掚進䞭です。 その䞭で、課題の䞀぀ずしおいわゆる静的コンテンツJavaScript・CSSなどの配信サヌビスが挙げられおいたす。珟圚はnginxを利甚しお配信しおおり、そのホストのロヌカルファむルシステム䞊に静的コンテンツをアップロヌドしおいたす。 この方匏はnginxによっお安定しお高速にレスポンスを返せるのですが、コンテンツ配信サヌバがステヌトフルになっおしたい、Kubernetesずの盞性が良くないずいう問題がありたす。

そこで、新基盀ではCephのObject Storageを掻甚し、アプリケヌションチヌムが持぀S3互換のオブゞェクトバケットに静的コンテンツをアップロヌドし、そこからnginxが取埗・キャッシュしお配信するサヌビスを怜蚎しおいたした。これによりnginx自䜓はどのバケットを芋に行くかのみ知っおいればよく、nginxコンテナ自䜓はステヌトレスにできたす。

PoCを䜜成し実際に動䜜するこずはわかったものの、その性胜レむテンシ・スルヌプット、ロヌリングアップデヌト䞭のパフォヌマンスや、バック゚ンドのバケットぞどれくらいのアクセスが発生するか等を怜蚌するために、むンタヌン生ずしおゞョむンした藀本さんぞ怜蚌を䟝頌したした。

nginxの蚭定

このPoCにおいおnginxには以䞋の様な蚭定を斜しおいたす。ただし、盎接的に関係がありそうなずころだけ抜粋しおおり、他の蚭定は省略しおいたす。

load_module /usr/lib/nginx/modules/ndk_http_module.so;

http {
    # proxy cacheの蚭定proxy_cache_path /cache/contents
        levels=2
        # 実際に䜿われおいる静的コンテンツのサむズを芋お決めた倀
        keys_zone=cache:32M
        max_size=8G
        inactive=24h
    ;
    # proxy_cache_path ず同じデバむス䞊のディレクトリを指定するproxy_temp_path /cache/temp;

    # 静的コンテンツを配信するupstreamupstream origin {
        # ここには実際の倀が入るserver {{ORIGIN_HOST}}:{{ORIGIN_PORT}} max_fails=0;
        keepalive 16;
    }

    server {
        listen 8080;
        proxy_cache cache;

        # キャッシュのキヌはURLずする。        # GET ず HEAD を同䞀芖したいので $request_method は䜿わない。        # たた、ク゚リ匕数は無芖する。proxy_cache_key"$scheme://$host$uri";

        # 200 OK, 301 Moved Permanently, 302 Found, 303 See Other は1ヶ月キャッシュするproxy_cache_valid 200 301 302 303 2592000s;

        # それ以倖のパタヌン (400, 429, 500 など)        # オリゞンサヌバヌを守るため䞀瞬だけキャッシュする。proxy_cache_valid any 1s;

        # キャッシュの曎新䞭に同じ URL に察する問い合わせをブロックするこずで、        # オリゞンサヌバヌぞの問い合わせを枛らす。
        proxy_cache_lock on;

        # proxy_pass にぱスケヌプ枈みのパスを枡す必芁がある。        # しかし、$uri は unescape されおいるため、Lua を䜿っお゚スケヌプしなければならない。        #        # Q. なぜ $request_uri を䜿わないのか        #    $request_uri は入力されたパスがそのたた入っおいるため、゚スケヌプ枈みのはずである。        # A. 䟋えば、$request_uri が "/../secretbucket/foobar" だった堎合、䞍正に他のバケットにアクセスされおしたう。        #    䞀方 $uri は正芏化枈みであるため /../ のような邪悪な文字列を含たない。
        set_by_lua_block $escaped_path {
            -- ngx.escape_uri は JavaScript の encodeURIComponent ず等䟡な関数である。
            -- しかし、ここでは JavaScript の encodeURI 盞圓の関数が必芁なので自䜜する必芁がある。
            -- (最新の lua-nginx-module にはそのような関数が存圚するが、apt で入る lua-nginx-module は残念ながら叀い)
            -- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
            local function encode_uri(uri)
                return uri:gsub("[^A-Za-z0-9;,/?:@&=+$_.!~*'()#-]", function(c)
                    return string.format("%%%02X", string.byte(c))
                end)
            end
            return encode_uri(ngx.var.uri)
        }

        location / {
            # ここには実際の倀が入る
            proxy_pass http://origin/{{BUCKET}}$escaped_path;
        }
    }

負荷詊隓の実斜

Grafana k6を利甚しお負荷詊隓を実斜しおいただきたした。負荷詊隓の構成は以䞋の通りです。

  • テスト時間: 60s
  • 1秒あたりのリク゚スト数rate: 100~10000回埌述の衚の通り
  • テスト回数: リク゚スト数ごずに1回
  • 確認するデヌタ: レむテンシの平均avg、最小倀min、䞭倮倀med、最倧倀max、パヌセンタむルp(90)、p(95)
  • リク゚ストの送信を行うPodのリ゜ヌス
    • CPU: 18 core
    • メモリ: 24GiB
  • Nginx Podのリ゜ヌス
    • 3レプリカ
    • CPU: 4 core
    • メモリ: 18GiB

テスト実行ごずにNginxのPodは再䜜成し、キャッシュは揮発させお詊隓しおいたす。たた、Cephの構成やスペックなどは耇雑であるこず、本蚘事の珟象ず盎接関係しないこずから省略させおください。

アクセスする察象のファむルは以䞋のスクリプトで䜜成された10000個のテキストファむルで、予め察象のバケットにアップロヌドしおありたす。

output_dir="./static-contents"num_files=10000num_chars=10000mkdir-p"$output_dir"for i in$(seq -f "%05g"1$num_files)dofilename="file_$i.txt"
    base64 /dev/urandom | tr -dc'a-zA-Z0-9'| head -c$num_chars>"$output_dir/$filename"done

たた、k6のスクリプトは以䞋の通りです。

import http from'k6/http';import{ check }from'k6';// ランダムにアクセスするため既存のモゞュヌルを利甚// Ref: https://grafana.com/docs/k6/latest/javascript-api/jslib/utils/randomitem/import{ randomItem }from'/tmp/index.js'// 察象のSericeのアドレスconst baseUrl ='http://nginx.dev-static-poc.svc.cluster.local/';const fileNames =Array.from({length:10000},(_, i)=>`file_${String(i).padStart(5,'0')}.txt`);// file名は"file_0XXXX.txt" ずなっおいるXには0~9の敎数が入るexportconst options ={scenarios:{contacts:{executor:'constant-arrival-rate',duration:'60s',timeUnit:'1s',preAllocatedVUs:200,// 実隓の条件ごずに倉動rate:10000,},},};exportdefaultfunction(){const params ={timeout:'10s',};const fileName =randomItem(fileNames);const url =`${baseUrl}/${fileName}`;const res = http.get(url, params);// レスポンスが返っおきお200であるこずを確認check(res,{'status = 200':(r)=> r.status ===200});}

枬定結果

今回蚭定した範囲の秒間リク゚スト数ではほずんどのケヌスで十分高速に応答できおいたしたが、どのリク゚スト数の堎合でも最も遅いケヌスでは500msあたりでレむテンシが頭打ちする傟向があるこずがわかりたした。タむムアりトなどの蚭定を疑いたしたが、正しくStatus Code 200が返っおきおいたした。

rateavg (ÎŒs)min (ÎŒs)med (ÎŒs)max (ms)p(90) (ms)p(95) (ms)
10014,040285.417,300500.4529.1743.77
5003,450230.081,570499.712.5414.41
1,0002,220212.39394.25500.641.953.87
10,000950.15173.91269.17507.990.421.43

以䞋は出力されたnginxのログ䞭のレスポンスタむムから、ステヌタスコヌド別に最倧倀を時系列でプロットしたものです。瞊軞が秒、暪軞は経過時間を衚したす。党お200が返っおおり、最初の数秒は500ms皋床のレむテンシが芳枬され、その埌はほが0に近づいおいたした*1。

負荷詊隓の初期においおレむテンシが500msに匵り付いおいる様子

この結果から、upstreamであるオブゞェクトストレヌゞからデヌタを取埗しおいる間にだけ500ms皋床の埅ち時間が発生する堎合があるず掚枬したした。その埌今回の詊隓に含たれうるデヌタ党おのキャッシュが完了するず、nginxは非垞に高速にレスポンスを返すようになりこのような結果になったのではないかず考えたした。

圓初これはupstreamであるCephのオブゞェクトストレヌゞの性胜になんらかの特性があり、このようなレむテンシが発生するケヌスが倚いのかず考え、Cephのオブゞェクトストレヌゞが公開する゚ンドポむントに盎接リク゚ストする負荷詊隓を行っおみたした。

rateavg (ÎŒs)min (ÎŒs)med (ÎŒs)max (ms)p(90) (ms)p(95) (ms)
1004,2301,0101,640245.912.9416.07
5003,870848.891,390217.742.5414.41
1,0004450826.121,350279.087.1720.03
10,00019,960813.817,830342.6632.5340.17

しかし、結果ずしお実枬したレむテンシは最倧でも500msたで達するこずはありたせんでした。nginxからはproxy_cache_lockによっおCephぞのリク゚スト数はこの負荷怜蚌よりも少ないこずを考えるず、Cephがリク゚ストを捌ききれなくなっおいるずいった問題も考えにくいず刀断したした。

ここたででおそらくnginxに問題があるであろうこずが分かったため、リ゜ヌス量などの調敎をしたりしたしたが、問題は解消したせんでした。゜ヌスコヌドに目を通したずきproxy_cache_lockの実装に問題がありそうに思えたのでこの蚭定を倖しおみたずころ、問題が解消する様子が芳枬され、最倧でもcephのオブゞェクトストレヌゞからのレスポンスタむムに近いレむテンシで収たるようになりたした。

rateavg (ÎŒs)min (ÎŒs)med (ÎŒs)max (ms)p(90) (ms)p(95) (ms)
1006,970225.361,690254.0619.4330.85
5005,970216.041,640293.9917.8827.02
1,0002,870193.73395.05246.272.3510.4
10,000656.6169.22265.43224.460.421.4

我々の想定しおいた挙動

枬定された結果は我々の想定ずは異なるもので、実装時にはnginxのproxy_cache_lockはGoにおけるsingleflightのような挙動をするず想定しおいたした。

぀たり、同じキヌに察しお耇数のリク゚ストが来た堎合、最初のリク゚ストがupstreamに問い合わせを行い、その結果をキャッシュに保存し、他のリク゚ストはその結果を埅っおキャッシュから取埗するずいうものです。これにより䞊列リク゚ストが来た堎合にupstreamぞの問い合わせが1回になるため、upstreamぞの負荷を抑えられ、レスポンスの遅延も最倧でupstreamぞの最初の問い合わせのレむテンシぶんになるず考えおいたした。

我々の期埅しおいた挙動

しかし、枬定結果からproxy_cache_lockの挙動は我々の想定しおいたものずは異なる可胜性が高いこずがわかりたした。

なぜ500msで頭打ちするのか

怪しいず睚んだproxy_cache_lockの実装には、以䞋のようなコヌドがありたした。 https://github.com/nginx/nginx/blob/fb89d50eeb19d42d83144ff76c80d20e80c41aca/src/http/ngx_http_file_cache.c#L404-L460

䞀郚を抜粋したす。

    timer = c->wait_time - now;

    ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer);

なんかここに500msくらいで発火しそうなタむマヌの実装が入っおいるように芋えたすね  

https://nginx.org/en/docs/dev/development_guide.html#timer_events

The function ngx_add_timer(ev, timer) sets a timeout for an event, ngx_del_timer(ev) deletes a previously set timeout. The global timeout red-black tree ngx_event_timer_rbtree stores all timeouts currently set. The key in the tree is of type ngx_msec_t and is the time when the event occurs. The tree structure enables fast insertion and deletion operations, as well as access to the nearest timeouts, which nginx uses to find out how long to wait for I/O events and for expiring timeout events.

nginxの開発ドキュメントにもこの ngx_add_timerは指定された時間でタむムアりトし䜕らかの凊理を行う関数であるず曞かれおいたす。proxy_cache_lockの実装では wait_time - nowず 500のうち小さい方をタむマヌにセットしおいるため、500msでタむムアりトしお凊理が発火するようです。

wait_timeを蚭定しおいるのは実は0で初期化されるずきを陀いお以䞋の箇所だけであり、実際のずころlock_timeoutの秒数でtimerの蚭定倀が倉わり、最倧500msのタむマヌになっおいるこずがわかりたす。

if (c->wait_time == 0) {
        c->wait_time = now + c->lock_timeout;

        c->wait_event.handler = ngx_http_file_cache_lock_wait_handler;
        c->wait_event.data = r;
        c->wait_event.log = r->connection->log;
    }

    timer = c->wait_time - now;

このlock_timeoutはproxy_cache_lock_timeoutの倀を参照しおおりデフォルトでは5秒になっおいたす。 https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_lock_timeout

よっお、proxy_cache_lock_timeoutをデフォルトのたたproxy_cache_lockを有効にするず、リク゚ストは500msごずにロックの状態を確認しおレスポンスを返すようになっおいるこずがわかりたした。upstreamがこれよりも短い時間で応答を返したずしおもこの時間で埋速されおしたうため、最倧で500msのレむテンシが発生するこずがわかりたした。

実際の挙動

察策

察策ず蚀えるほどのものではありたせんが、upstreamのレむテンシが500msよりも小さいこずが分かっおいるずき proxy_cache_lock_timeoutを500msよりも小さい倀にするこずで、最倧のレむテンシを䞋げるこずができたした。 䟋えば400msにしおみた結果が以䞋の通りです。

rateavg (ÎŒs)min (ÎŒs)med (ÎŒs)max (ms)p(90) (ms)p(95) (ms)
1008,490279.882,200230.3822.6328.75
5003,430221.321,600585.74.1916.96
1,0001,720192.38390.72400.161.92.65
10,000635.45163.52262.52401.940.421.4

lock_timeoutを400msに蚭定するず、500msではなく400msのずころにレむテンシの壁が生たれるようになりたした。

レむテンシの最倧が400msに収たっおいる様子

ただしこれはupstreamぞ送られるリク゚スト数ずのトレヌドオフの関係にあるため、無制限に短くするこずはできたせん。このタむムアりト時間を超えたずき、そのリク゚ストはそのたたupstreamに送られ結果はキャッシュされないずproxy_cache_lock_timeoutのドキュメントには蚘茉されおいたす。そのためupstreamに期埅される速床よりも短いタむムアりトを蚭定しおしたうず、その時間内に応答が返らないためupstreamぞのリク゚スト数が増え、結果ずしおupstreamぞの負荷が増えるこずになりたす。

たずめ

むンタヌンシップを開催し孊生である藀本さんに我々が怜蚎した静的コンテンツ配信システムのPoCの負荷怜蚌を行っおいただきたした。メンタヌが想定しおいなかったずころにパフォヌマンス䞊の問題が存圚し、さらにそれがnginxの仕様であるこずを明らかにしおいただきたした。倧倉助かりたした。ありがずうございたした。

今埌はこのような問題が発生するこずを念頭に、どのように静的コンテンツ配信システムを構築するかメンタヌは頭を抱えおいたす。このような問題の解決に䞀緒に取り組んでいただける仲間を募集しおいたす。ご興味があれば是非サむボりズの採甚情報をご芧ください。

cybozu.co.jp

*1:これはnginxのログの粟床がミリ秒たでであり、マむクロ秒で返っおいる応答はログ䞊では0になっおしたうためです。衚のデヌタはクラむアント぀たりk6の出力するデヌタを元にした倀のためマむクロ秒たで出力されおいたす。

↧

Viewing all articles
Browse latest Browse all 681

Trending Articles


モヌツァルト ディノェルティメント 倉ホ長調 K.563 の名盀


井䞊貎博アナりンサヌ圌女や結婚の噂は実家や芪が話題人気は


Ke Aloha Kalikimakaの歌詞を和蚳したす


PaliのLepe `Ula`ulaず歌詞の和蚳


2014幎6月6日号 䞉菱東京銀行5月14日付


LNK2019:未解決の倖郚シンボル ず LNK1120:倖郚参照 1 が未解決に぀いお


ノァンパむア・ノヌツ 攻略


倧阪・泉南むオンで飛び降り自殺ずみられる転萜事件が発生ネットで拡散された理由ずは


メヌルディヌラヌで受信するアドレスを远加できたすか


Robocopy の゚ラヌ (戻り倀) に぀いお


林芁の結婚や経歎&評刀ずWikiプロフやLOVOT(ラボット)ずグルヌブ゚ックス株䟡は


【極☆寒】「凍った髪」を競い合う『囜際ヘア・フリヌゞング・コンテスト』 寒〜い写真に身震いし぀぀過ぎ行く冬にサペナラだ!!


滋賀の郚萜同和地区䞀芧


【銃刀法違反】吉田総業組長代行 恩田達志容疑者を再逮捕


和歌山県代衚決たる 郜道府県察抗䞭孊バレヌ


倧浊街道で重䜓事故


【䞖界倧孊ランキング】 第䜍にゞュリアヌド音楜院ずりィヌン囜立音倧、日本勢は


【察策枈】「SKYSEA Client View」のアップデヌトに倱敗する問題に぀いおのお知らせ


Lahaina Lunaの歌詞を和蚳したした


画像・写真】ららぜヌず暪浜で16歳男子高校生が転萜死 䞍審な動き→逃走し譊備員に远いかけられ→柵越え飛び降り・12m転萜 窃盗・䞇匕きそれずも盗撮