NGINXでキャッシュを行う

NGINXでリバースプロキシする際、キャッシュを行いパフォーマンスを上げることができます。

メリット
・upstreamサーバへのリクエストを減らすことができる。
・NGINXとupstreamサーバ間のトラフィック削減
・レスポンス速度が早くなる

デメリット
・キャッシュの更新を考えた場合に処理が複雑になる可能性がある。
・NGINXサーバでディスク使用量の増加
・NGINXサーバでIO負荷がかかる

設定方法

現在の一時ファイルについての設定確認

# nginx -V 2>&1 | sed 's/ --/\n --/g' | fgrep proxy-temp-path
# fgrep -nr proxy_temp_path /etc/nginx/

設定

http {
  #proxy_temp_path /var/cache/nginx/proxy_temp;
  proxy_cache_path /var/cache/nginx/cache_1 levels=1:2 keys_zone=cache_zone_1:16m max_size=100m inactive=120m;

  upstream backend {
    server 192.168.0.101;
    server 192.168.0.102;
  }

  server {
    listen 80;
    server_name _;

    location / {
      proxy_pass http://backend;

      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;

      proxy_ignore_headers X-Accel-Expires Cache-Control Expires;

      proxy_cache cache_zone_1;
      proxy_cache_key "$scheme$proxy_host$request_uri";
      proxy_cache_valid 200 60m;
      proxy_cache_valid any 5m;

      add_header X-Cache-Status $upstream_cache_status;
    }
  }
}

proxy_cache_pathディレクティブ

/var/cache/nginx/cache_1:キャッシュファイル保存場所
キャッシュを行う場合、一旦レスポンスをproxy_temp_pathで指定した一時ファイルに保存してからのrenameとなるため同じデバイスになるよう注意
実ディレクトリは/var/cache/nginxまで存在していればcache_1は自動で作成されます。

levels=1:2:キャッシュを保存する階層
キャッシュファイル名はproxy_cache_keyをmd5したものになり、階層はファイル名の一部が利用されます。
md5が3858f62230ac3c915f300c664312c63fだった場合のファイルパス
/var/cache/nginx/cache_1/f/63/3858f62230ac3c915f300c664312c63f

keys_zone=cache_zone_1:16m:キャッシュゾーンの名前とメモリサイズ。
メモリサイズは1つのキャッシュにつき128バイト使われる(64bit環境)
この例だと16mになっているので約130000ファイル保存できます。
※よく使われるサイズの単位
g:ギガバイト
m:メガバイト
k:キロバイト

max_size=100m:キャッシュファイルの総容量
nginxは定期的にcache managerと呼ばれるプロセスを起動し、容量を監視しています。この時最大容量を超えていた場合は最も長い期間アクセスされなかったファイルから削除されます。
※最大容量を超えた瞬間ではなく、定期的にチェックされる=一時的に最大容量を超えた状態になる。

inactive=120m:最後にアクセスされてからキャッシュファイルが削除されるまでの時間
120mの場合、最後にアクセスされてから120分アクセスがないと削除されます。
※よく使われる時間の単位
M:月
d:日
h:時間
m:分
s:秒

proxy_cacheディレクティブ

proxy_cache_pathで設定したキャッシュゾーンの名前を指定

proxy_cache_keyディレクティブ

キャッシュのキーとして使用する値を指定

proxy_cache_validディレクティブ

ステータスごとのキャッシュの有効期限の指定
ステータスを指定しない場合は200,301,302のレスポンスのみがキャッシュされる。anyを指定した場合は明示的に指定していないすべてのステータスコードに設定することができる。

proxy_ignore_headersディレクティブ

backendからのレスポンスで無視するヘッダーを指定する
Cache-Control及びExpiresを指定することでproxy_cache_validの設定が生きる
キャッシュ設定の優先順位
X-Accel-Expiresヘッダ > Cache-Controlヘッダ > proxy_cache_valid

※Set-Cookieヘッダがある場合、通常はキャッシュされませんがproxy_ignore_headersで無視してキャッシュする事も可能です。
ただしこの場合proxy_hide_headerも指定しておかないと他人にもcookieがセットされてしまいます。

add_headerディレクティブ

サンプルの場合X-Cache-Statusというレスポンスヘッダ名でキャッシュのステータスを返しています。

簡単にNGINXのキャッシュ設定について説明しました。
NGINXの設定は奥が深いので色々試してみましょう。


本当に欲しいものをわかっているとは限らない

消費税増税や面倒しかない軽減税率が目前に迫っています。カツラです。
それに伴い個人的に今年の初めから漠然と家計簿をつけ始めましたが、思いの外長く続いているのでその中で思ったことを書きます。

最近のアプリだと手で入力するだけではなくレシートから自動で読み取ってくれたりグラフ表示があったりするので当初はアプリを使う予定でした。
ですが、自分の欲しい機能が入っているアプリを見つけるまでが長く機能も色々あるけど最終的には面倒になったので、途中で探すのをやめてスプレッドシートで管理するようにしました。

ゲームやエンタメ系であればランキングやおすすめ系はわかりやすくて良いのですが、管理系で複数アプリが存在するものは機能の一覧比較等の方がわかりやすいと思います。

そして最初は分類毎の合計金額を月単位で推移が見れればいいだけでしたが

  • PC等高額商品は別扱いにしたい
  • 食費、外食費は分けて記録するけどグラフでは統合したい
  • Kindleで購入したものは別枠で詳細が見たい

などなど家計簿をつけているうちに後付けで色々と欲しいものが見えてきた点が多く、個人的にはスプレッドシートだったので柔軟に対応できてよかったのですが、このことから家計簿をつけるのにアプリよりスプレッドシートを推奨するという意味ではありません。

この出来事を通して思ったのは自分がサービスを作る上でも

  • 本当に欲しい機能をユーザー自身が最初からわかっているとは限らない
  • なんでもかんでも機能を搭載する必要はない、むしろ邪魔になることも
  • いくら良いものでも認知されなければないものと同じ

とわかっているつもりでしたが、今回のことで再確認できた気がします。


[Solr]ドキュメントルーティングのcompositeIdのハッシュを実際に計算してみる

前回の記事に書いたように、compositeIdによるドキュメントルーティングを採用した場合、compositeIdのハッシュ値のどの範囲をどのシャードが担当するのかはSolrの管理画面で確認することができます。ただし、実際のハッシュ値を知ることは実はできません。

簡単なテストプログラムを書いて、compositeIdのハッシュ値がどのように計算されるのかを確認してみました。

まずは単独のIDでハッシュを計算してみます。

$ java -cp .:solr-solrj-8.1.0.jar CompositeIdTest '1234'
id = 1234
hash = 721c5dc3
$ java -cp .:solr-solrj-8.1.0.jar CompositeIdTest '中央区'
id = 中央区
hash = a812f493
$ java -cp .:solr-solrj-8.1.0.jar CompositeIdTest '公園'
id = 公園
hash = eb2b06c4

IDを組み合わせてみます。

$ java -cp .:solr-solrj-8.1.0.jar CompositeIdTest '中央区!1234'
id = 中央区!1234
hash = a8125dc3
$ java -cp .:solr-solrj-8.1.0.jar CompositeIdTest '中央区!公園!1234'
id = 中央区!公園!1234
hash = a82b5dc3

8ビットで16進数2桁、16ビットで16進数4桁です。
「中央区!1234」の場合は16ビット:16ビットになるので
「中央区(a812f493)」の上位4桁
「1234(721c5dc3)」の下位4桁
でa8125dc3になっていることが分かります。
「中央区!公園!1234」の場合は8ビット:8ビット:16ビットなので
「中央区(a812f493)」の上位2桁
「公園(eb2b06c4)」の3桁目と4桁目
「1234(721c5dc3)」の下位4桁
でa82b5dc3となります。

さらにビット数を指定してみます。

$ java -cp .:solr-solrj-8.1.0.jar CompositeIdTest '中央区/4!公園/4!1234'
id = 中央区/4!公園/4!1234
hash = ab1c5dc3
$ java -cp .:solr-solrj-8.1.0.jar CompositeIdTest '中央区/4!公園!1234'
id = 中央区/4!公園!1234
hash = ab2c5dc3

「中央区/4!公園/4!1234」の場合は4ビット:4ビット:24ビットなので
「中央区(a812f493)」の上位1桁
「公園(eb2b06c4)」の2桁目
「1234(721c5dc3)」の3桁目以降
でab1c5dc3となります。
「中央区/4!公園!1234」の場合に無指定の「公園」の扱いがどうなるか興味があったのですが、2階層の場合はデフォルト8ビットなので「中央区/4!公園/8!1234」と同じになるようです。
「中央区(a812f493)」の上位1桁
「公園(eb2b06c4)」の2,3桁目
「1234(721c5dc3)」の4桁目以降
でab2c5dc3となります。

簡単なプログラムではありますが、特定のレコードがどのシャードに配置されているのかを確認したい場合に便利に使えそうです。


Solrのドキュメントルーティングで複数階層のシャードキーを指定するとどうなるのか

はじめに

SolrのcompositeIdを使ったドキュメントルーティングではシャードキーを複数指定することができます。シャードキーを複数指定した場合の動作をちゃんと考えたことが無かったので調べてみました。

compositeIdを使ったドキュメントルーティング

compositeIdを使ったドキュメントルーティングではドキュメントIDとシャードキーから計算されたハッシュ値に基づいて、インデッ クス先のシャードが決まります。シャードキーが指定されなければドキュメントIDのハッシュだけが使われます。

1レベルのシャードキー

シャードキーを指定する場合、ドキュメントIDのフィールドを以下のように記述します。
“シャードキー!ドキュメントID”
以前サンプルに利用した大阪市の施設情報のデータの場合、区名に基づいてシャードを分けるには
“中央区!1234”
のようにドキュメントIDのフィールドを指定します。
こうすることで、同じ区の施設データは必ず同じシャードに配置されるようにインデックスされます。

2レベルのシャードキー

2段階目のシャード分けの条件として施設タイプを使うとすると、ドキュメントIDの指定の仕方は以下のようになります。
“中央区!官公庁!1234”
こう書けるところまではドキュメントを読めば書いてあります。ではこう書いた場合に具体的にはどのような配置になるのでしょうか。

ドキュメントIDのハッシュとシャードの対応付け

ハッシュは32ビットです。00000000からffffffffまでの値をシャード数に応じて均等に区分けします。たとえばシャード数が8の場合、以下のように分けられます。

8等分なので上位3ビットで分類するわけです。

シャード最初の3ビット(2進数)最初の1桁(16進数)
shard10000〜1
shard20012〜3
shard30104〜5
shard40116〜7
shard51008〜9
shard6101a〜b
shard7110c〜d
shard8111e〜f

シャードキーが指定されたときにハッシュがどう計算されるのか

こちらの記事によると、
シャードキーが1つの場合
ハッシュの上位16ビット:シャードキーのハッシュから
ハッシュの下位16ビット:ドキュメントIDのハッシュから

シャードキーが2つの場合
ハッシュの上位8ビット:シャードキー1のハッシュから
ハッシュの次の8ビット:シャードキー2のハッシュから
ハッシュの下位16ビット:ドキュメントIDのハッシュから
となるようです。

シャードキーを2つ指定したとしても、最上位の8ビットをシャードキー1が占めているため、シャード数が256より小さい限りはシャードキー2は影響を与えることができません。

実際に8シャードのコレクションに2パターンのデータを投入して、シャード毎のレコード数をカウントしてみました。シャードキーが1つの場合も2つの場合も同じになっていることが分かります。

$ head -3 data1.json
[
{"id":"住之江区!158","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"}
{"id":"住之江区!157","type":"官公庁","area":"住之江区","name":"大阪陸運支局なにわ自動車検査登録事務所","address":"住之江区南港東3-1-14"},
$ curl 'http://localhost:8983/solr/compositeid/update?commit=true&indent=true' --data-binary @data1.json -H 'Content-Type: application/json'
$ for i in {1..8}; do curl -s "http://localhost:8983/solr/compositeid/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done
580
509
1471
1203
2092
761
545
2077
$ head -3 data2.json
[
{"id":"住之江区!官公庁!158","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"},
{"id":"住之江区!官公庁!157","type":"官公庁","area":"住之江区","name":"大阪陸運支局なにわ自動車検査登録事務所","address":"住之江区南港東3-1-14"},
$ curl 'http://localhost:8983/solr/compositeid/update?commit=true&indent=true' --data-binary @data1.json -H 'Content-Type: application/json'
$ for i in {1..8}; do curl -s "http://localhost:8983/solr/compositeid/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done
580
509
1471
1203
2092
761
545
2077

ビット数の制限付きでシャードキーを指定する

シャードキーと共にビット数を指定することもできます。たとえば
“中央区/2!官公庁/14!1234”
とすると、
ハッシュの上位2ビット:区名のハッシュから
ハッシュの次の14ビット:施設タイプのハッシュから
ハッシュの下位16ビット:ドキュメントIDのハッシュから
となります。
2ビットということは、区名により4つに分類されるということです。
たとえば8シャード用意されている場合、特定の区は特定の2シャードのどちらかに配置されることになり、そのどちらになるのかは施設タイプにより決定される、という訳です。

このパターンで先程同様にデータを投入すると、シャードごとのレコード数が大きく変化したことが分かります。

$ head -3 data3.json
[
{"id":"住之江区/2!官公庁/4!158","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"},
{"id":"住之江区/2!官公庁/4!157","type":"官公庁","area":"住之江区","name":"大阪陸運支局なにわ自動車検査登録事務所","address":"住之江区南港東3-1-14"},
$ curl 'http://localhost:8983/solr/compositeid/update?commit=true&indent=true' --data-binary @data4.json -H 'Content-Type: application/json'
$ for i in {1..8}; do curl -s "http://localhost:8983/solr/compositeid/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done
578
511
1414
1260
1572
1281
1390
1232

それほど大規模なシステムではない場合、このように少ないビット数で制限を掛けるやり方が、現実的なシャードキーの利用方法になるでしょう。


脳トレ、始めました

こんにちわ。
リエです。

いきなりですが、「脳トレ、始めました。」(本当いきなりすぎる)
というのも脳の劣化を感じたからです。
人は年齢を重ねるたびに身体が少なからず劣化の一途をたどるものですが、使っていないと当然劣化の速度は早まります。

まぁ理由は何であれ「脳トレ、始めました。」

【脳トレ】とググるとたくさんヒットしますが、効果的な脳トレの法則は脳に負担をかけすぎないシンプルな計算、読み・書きやパズルをすることだそうです。
参考サイト:https://atamanavi.jp/specials/brain-training/

なるほどな〜と思いながら、わたしが選んだのは塗り絵です。
塗り絵アプリをiPadでやっているのですが、Apple Pencilは便利ですね。
Apple社に感謝( ̄人 ̄)

わたしが使っている塗り絵アプリは、ペンで該当部分を塗りつぶすタイプではなく、番号をタップして色付けしていくタイプです。ちなみに使っているアプリはHappy Colorというアプリでございます。

完成した絵がこちら。

着色部分が多い絵もたくさんあるので(使用色が100種類の絵とかめっちゃある)完成したときは結構達成感があります。

ほとんどの絵が30分以内に完成するので、ほぼ毎日やっております。
塗り絵もカニと一緒で無言になって黙々やっちゃいますね(;´∀`)

紙の塗り絵も好きなので、今度そっちも挑戦してみたいと思います。
ちなみにわたしは枠を塗ってから中を塗りつぶすタイプですという世界一どうでもいい情報でこのブログは終わりたいと思います。

まだまだ暑い日が続きますので、皆さまどうぞご自愛くださいませ。