前提
OpenSSLがbrewで入っている.
例えば `logout` みたいなユーザ名を取得されてしまうと,ユーザ側からすると不気味に見えるし,URL設計が終了している時などに脆弱性になり得る (とは言うものの,そもそもそういった脆弱性は根本的に防ぐべきだし,URL設計を終了させてはならない).
ので,タイトルのようなことをpostしたら知見がモリモリ集まってきた.ありがとうございます.
取得させたくないアカウント名を集めてあるライブラリ欲しい気がする.authとかlogoutとかdataとか……
— アドセンスクリックお願いします太郎 (@moznion) 2017年4月10日
@moznion 所得させたくないアカウント名リストならこういうレポジトリがあります https://t.co/uyYJxaFbrX
— kosuge (@9m) 2017年4月10日
@moznion このリストに追加で日本向けに regist っていうアカウント名も入れておきたい
— kosuge (@9m) 2017年4月10日
@moznion こういうのもありました https://t.co/5LTShvgBUw
— へたくそな口ぶえ (@uasi) 2017年4月10日
@moznion 既にいろいろ情報集まっているようですが, 昔そういうの欲しくなった時は https://t.co/wSsUe73bUu これを参考にしてやっていました. ご参考まで...
— papixのPはピンチのP (@__papix__) 2017年4月10日
めっちゃ便利!!!!!
タイトルは「Webアプリケーションのキャッシュ戦略とそのパターン 」です.
告知で書いたように,ここ1・2年は規模感のあるWebアプリケーションを開発していて,なおかつキャッシュ周りの設計・開発・運用をモリモリやっていたので,その関連で学んだこと,感じたことをまとめて発表したという感じです.聞きに来てくださった皆さんありがとうございます.内容についてはスライドをご覧いただければご理解頂けるかと存じます.
ところでトーク中に言い忘れたこととして,「ランキングの構造を返すJSON」みたいなものはえてして大きくなりがち,かつランキングをバッチで構築している場合は或る単位時間内に変化することが少ない (あるいは無い) ので Cache-Control
を付けてJSONを返してしまうと負荷が大きく下がって便利,みたいな話題もありました.しっかりした原稿を作っていないとこういううっかりがあるものですね.反省します.
トーク中に「Varnishを使ってないんですすみません」という話をしましたところ,懇親会等でVarnish周りで色々なフィードバックを頂戴することが出来てたいへん有益でした.
ざっくりとまとめると;
という感じで,つまり
といった手法が現代では可能なので,割と安全にVarnishを活用できるのだなあという認識に落ち着いてきました.あとはVCLが便利とか.Varnish自体に関しては利用できる箇所であれば利用したいところなので,今後適所に取り入れていきたいと思います.
トークをするとこういった情報が集まってきて嬉しいですね.ありがとうございます.
表題のとおりです.喋ります.詳細は以下のリンクを参照ください.
http://yapcjapan.org/2017kansai/talks.html#talk-22
ここ1,2年ずっとキャッシュと密にやっていく仕事 (サービス) をやっていて,それにより給料と言う名のキャッシュを得て生活していたわけですが,ここらでそこらへんの知見らしきものをまとめ,ついでにアウトプットしようという狙いでトーク応募をしましたら採択されましたから喋ります.役に立つ話が出来ると嬉しいなあと思っています.ぜひ来てください!!!
あとdankogaiさんのライブコーディングの実況・解説という珍ロールで登壇もします.こちらも絶対面白いので来てください!!!
mcrouterという,Facebookが作っているmemcachedの為のルータがあります.「ミクルーター」と発音するようです *1.
mcrouterは多機能なルータであり,シンプルなルーティング (例えば乱択やhash basedなど) からfailoverを前提とした大規模なクラスタのルーティングまで様々なルーティングを行うことが可能です (Facebook内の数千台規模のmemcachedクラスタでも運用されているようです.参照: Facebookの数千台規模のmemcached運用について - ゆううきブログ).
さて,mcrouterの詳細や使い方の説明については他の資料に譲るとして,本記事ではmcrouterがmemcachedの get-multi
リクエストを各々複数の get
リクエストに分解して宛先のmemcachedに送る挙動をするという話をします.
Multi-key GET Behavior · Issue #40 · facebook/mcrouter · GitHub
このissueそのままではあるんですが,検証してみましょう.
{ "pools": { "sample": { "servers": [ "localhost:11211" ] } }, "route": { "type": "AllAsyncRoute", "children": [ "PoolRoute|sample" ] } }
この設定ファイルを食わせてmcrouterを起動して (e.g. mcrouter -f ./config.json -p 22122
),更に宛先のmemcachedを起動します.memcachedはmemcached -vv
というふうに起動すると検証が楽 (Ref: memcached おすすめ起動オプションまとめ - blog.nomadscafe.jp).
しかる後に以下のようなコードを流し込んでみる.
use IO::Socket; # mcrouter に get-multi my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 22122, Proto => 'tcp', ); my $keys = join " ", map { $_ } 1..5; # 5件multi-get $socket->print("get $keys\r\nquit\r\n"); my @lines = $socket->getlines; $socket->close;
さてこの時にmemcachedのログを確認してみると
<30 get 1 >30 END <30 get 2 >30 END <30 get 3 >30 END <30 get 4 >30 END <30 get 5 >30 END
という感じで,get-multiリクエストではなく個別のgetリクエストが来ていることがわかります.
一方,memcachedに直接繋いだ時は当然get-multiでリクエストが飛びます.
use IO::Socket; # memcached に get-multi my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 11211, Proto => 'tcp', ); my $keys = join " ", map { $_ } 1..5; # 5件multi-get $socket->print("get $keys\r\nquit\r\n"); my @lines = $socket->getlines; $socket->close;
<31 get 1 2 3 4 5 >31 END
つまりmcrouterはget-multiリクエストを個別のgetリクエストにバラしてから宛先memcachedに送りつけているのだ!! (正確にはget-multiに限らず,multi系のオペレーションは全部分解される)
主に関連する部分
詳細な実装に踏み込んで説明すると大変なので端折りますが
という挙動をしているようです.
つまり先程の例 (1から5のkeyをget-multiする場合) では get 1\r\nget 2\r\nget 3\r\nget 4\r\nget 5\r\n
というコマンドの塊をソケット越しで送るという動きになるようです.
memcachedのconn_yieldsはどういう時に増えるのか - その手の平は尻もつかめるさ
前回の記事で書いたように,1回のソケットで大量のコマンドを発行するとしきい値を超えた場合にはconn_yieldsが発生します.conn_yieldsが発生するとそのバッチリクエストの性能が劣化するという懸念があります.
従ってmemcached client側がget-multiで送っていると思い込んでいたコマンドは,実はmcrouter側で個別のgetリクエストに変換されており,key数が多い場合はconn_yieldsが多発するというような予期せぬ事態に陥ることがあります.
さてconn_yieldsによって本当に性能劣化するのか? という検証については次回行うこととします.
memcachedの conn_yields
はどのような時に増えるのかという話です.なおmemcachedはテキストプロトコルかつデフォルトの設定で実行しているものとします (なお,この記事で重要なのは -R
がデフォルト,つまり20ということです).
memcached の conn_yields について - tokuhirom's blog
1つのコネクションでコマンドを発行しまくっている場合にここに到達するようだ
と,上記記事にあるとおりなんですが,実際にmemcachedのコードを読みながら試してみたという記録です.
1回のソケットで大量のコマンドを発行している場合にincrementされる.
get 1
,get 2
,get 3
とクエリを何度も発行していくパターン.
use IO::Socket; my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 11211, Proto => 'tcp', ); $socket->print("get $_\r\nquit\r\n") for 1..100; my @lines = $socket->getlines; $socket->close;
この時は conn_yields
がincrementされない.
get 1 2 3...
と1つのgetで大量のエントリをひいてくるパターン.
use IO::Socket; my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 11211, Proto => 'tcp', ); my $keys = join ' ', map { $_ } 1..100; $socket->print("get $keys\r\nquit\r\n"); my @lines = $socket->getlines; $socket->close;
この時も conn_yields
はincrementされない.
get 1\r\nget 2\r\nget 3\r\n...
と1回のリクエストに複数コマンドを詰めるパターン.
use IO::Socket; my $socket = IO::Socket::INET->new( PeerAddr => 'localhost', PeerPort => 11211, Proto => 'tcp', ); my $gets = join "\r\n", map {"get $_"} 1..100; $socket->print("$gets\r\nquit\r\n"); my @lines = $socket->getlines; $socket->close;
この場合は conn_yields
はincrementされる (memcachedのデフォルトの最大コマンド数の閾値は20なので,その場合は (100 - 20)/20 = 4
ということで conn_yields
は4増える).
基本的に memcached.c
を読むとわかる.
memcached/memcached.c at 12ea2e4b50a3222412e9e1cffb1253d907c56cd5 · memcached/memcached · GitHub
注目すべきはこのwhileループで conn_yields
はこのループ内のこの部分でしかincrementされない.
で,どういう時に conn_yields
がincrementされるかというと,
nreqs
という変数が0を下回っていてconn_new_cmd
の時にループが回ったという時にされる (このnreqs
というのは「1つのコネクションで何個のコマンドを許容するか」という閾値.memcachedコマンドの -R
オプション相当).
nreqs
はstateが conn_new_cmd
の時にループが回るとdecrement される(実装はこの辺).
つまりざっくり言うと,stateが conn_new_cmd
の時に何度もループが回ると conn_yields
が増加するという事になる.
stateが conn_new_cmd
になるのはどういう時かというと色々あるのだけれど,頻度が多いものとしては「コマンド実行後 (正確には結果出力後)」がある.
1コネクション中で大量のコマンドが送られてきた場合,
1. 送られてきた文字列をバッファしつつ読み込みparseする
2. コマンドを実行
3. stateがconn_new_cmdになる (nreqsのdecrement,条件を満足している時は conn_yields
のincrement)
4. まだ読み込んでいない文字列がある場合は1に戻る
という挙動をする (実装はこの辺).
例として get 1\r\nget2\r\n
という文字列が送られてきた時は get 1\r\n
を読み込み・実行した後に get 2\r\n
を読み込み,実行する.この時コマンドは2度実行されるので, nreqs
は -2
されている.
上記の不発だった例のように「getコマンドを何度も発行してみる」という場合はそれぞれのコマンド実行が別のコネクションになっているので,conn_new_cmd
時のループは高々1度しか回らない.よってnreqs
は-1しかされないので conn_yields
はincrementされない.
また「multi-getで大量のエントリを取得してみる」という場合は少し特殊で,送られてきた文字列を読み込む時にバッファ内に収まるのであればそれは通常のコマンド1発のように扱われる.バッファ内に収まらない時は一回読み込んでからstateを conn_waiting
に遷移し,更にそこから conn_read
に遷移することで続きの文字列を読み込む (読み込み切るまでこれを繰り返す).いくら文字列が長くてもコマンド1発としてみなされるので nreqs
は-1しかされない.よって conn_yields
はincrementされないということになる (実装はこの辺).
認識が足りなくて,getコマンドを何度も発行してみたり,multi-getで大量のエントリを取得してみたりした時でも conn_yields
がincrementされると勘違いしていて,動作検証時にハマったので調べてみました.
Perlの場合,Cache::MemcachedやCache::Memcached::Fastを使っていれば1つのコネクションで複数コマンドを発行するケースは無さそうなので, conn_yields
のことは考えなくても良さそう?
表題の通りです.Perl5の話です.
CPANにもあげてあります.
https://metacpan.org/pod/Test::Fluent::Logger
何をするモジュールかというと,useするだけでFluent::Loggerのpost及びpost_with_timeの内容をインターセプトして,本来送るべき宛先には送らずに内部のリストにそれを貯めるという動作をするモジュールです.そしてそのリストをgetしてきて中身を確認したり,またリストを空にする (clear) こともできます.
使い方についてはSYNOPSISを読んでいただければわかると思います.とくに難しいことはありません.
本来はMockみたいな名前をつけるべきだったのかもしれませんが,こんなのテスト用途以外に誰が使うんだということでこの名前になっています.
毎度毎度この手のコードを書いていて,良い加減飽きたなという動機でこのたびモジュール化した次第.久々にCPANに新しいモジュールをアップロードしたんですが,昔のあの日のまま変わっていなくて少し安心しました.
ご利用くださいませ.