地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Linux】JVM系言語以外でも暖気運転は効果があるのかの考察

記事概要

JVM言語で話題の暖気運転についてJVM系言語(JIT)以外でも暖気運転は効果があるのかを考察した記事です。何かしらのベンチマークを取ったとかではないのでご注意ください。

JVMとの比較にはPythonとかPerlとかLL系言語かつpreforkで動くウェブアプリケーションを想定しています。

結論

結論としては効果があるケースもあるがJVM系言語ほどの効果は得られない(はず)

そもそも暖気運転とは

暖気運転とはコンパイル済みの状態で本番トラフィックの処理が可能になるよう、サービスインの前にアプリケーションを実行すること。サービス起動時にある程度のリクエストを飛ばしておくことで実際のユーザアクセス時にプログラムのコンパイル処理を事前に行うという仕組み

techblog.zozo.com

b.chiroito.dev

mod_なんちゃらとは

一方LL言語であるPerlをwebで動かす際にmodperlなんかを使ったりする。modperlを使うとインタプリタ的な実行ではなくプログラムは機械語に変換された後、メモリに常駐するようになる。JVM系言語との違いとしてプログラムの実行時にコンパイル処理は不要となっている。

JVMが行う暖気運転の目的であるプログラムの実行時にコンパイルを行う処理がないLL言語でも暖気運転が効果あるのかを考察していく

LL言語で暖気を行うメリット

LL言語で暖気を行うメリットはぱっと思いつく感じだと以下。それぞれ考察を書いていく。

  • ディスクアクセスを減らせる
  • 外部サービスとのコネクションを事前に張れる
  • TCPでのスロースタート問題に対応できる

ディスクアクセスを減らせる

Linuxの基本として一度アクセスしたファイルはメモリが許す限りはページキャッシュというキャッシュに乗ります。一般的にHDDから読むのとメモリから読むのでは(SATA接続HDDとDDR4構成)約100倍程度のRead/Writeに速度差があるのでファイル読み込み時は極力メモリから読むようにしたいです。

qiita.com

webアプリにおける静的ファイルのアクセスではテンプレートファイルなんかが挙げられます。仮に暖気を行っていない場合はプロセスはディスクアクセスを行ってファルを読み込むが暖気を行っておけばメモリ読み出しのみで済むので有利です(プロセスから外部プロセスを生成する際のバイナリ読み込みみたいなケースで大きめのサイズのファイルの場合は体感しやすいかも)。

また、テンプレートファイルが特定ディレクトリの一階層に大量のファイル数が存在する場合は暖気で全てをreadをかけてページキャッシュに乗せなくてもディレクトリをstat(2)しておくのも良さそう。この場合dentryなんかをキャッシュしておけばアクセス時はディスクアクセスは発生するもののファイルシステムのルックアップ処理は軽減できたりするのでメモリ使用量がシビアならそれだけでも効果的(なはず)

monoist.atmarkit.co.jp

外部サービスとのコネクションを事前に張れる

modperlもmodpythonもmodphpもpreforkで動かす場合はプロセス空間が独立していて各プロセスがそれぞれ外部サービスとのコネクションを管理します。connectを暖気で行ってプロセス内で永続化しておけばユーザリクエストは全てconenct済のプロセスが処理するのでconnect分の高速化を行うことができる。(サービスディスカバリ前提のアーキテクチャなんかだと名前解決のコストも馬鹿にならない)

(1) ソケット生成   socket()
(2) ソケット接続要求    connect()
=== ここまでを暖気でやっておけばユーザアクセスは(3)だけで済む
(3) 外部サービスのやりとり

例えばMySQLを例に挙げるとTCP 3-way handshake、データベース層のハンドシェイク、データベース層の認証、データベースプロセス側の接続用プロセス/スレッド生成なんかをMySQL側で行った状態でユーザアクセスが来るといった仕組みを作れる。

(ただこれはポスグレみたいなマルチプロセスモデルのDBの場合はDB側に永続化された分だけプロセスが生成されてしまうので負荷量を計測してからやるなどの考慮が必要そう。)

LL言語でpreforkが前提のモデルだとコネクションプールする仕組みが乏しいためこの辺は暖気が効果的な感じがする。この後書いているTCPレイヤでの輻輳制御にも有利となる。MySQL以外でもhttpレイヤでのkeepaliveをしたりしておくことでその辺の認証も飛ばせたりも可能となったりで意外と効果的な感じはする。(ただこの辺は結局どのミドルウェアもオーバヘッドを最小限にする実装してるだろうし正直計測しないと効果の程は全く読めない。。)

blog.yuuk.io

TCPでのスロースタート問題に対応できる

TCPではでコネクションを確立した後、データの送信量を徐々に増やすことで効率的に通信するための仕組みが有ります。コネクト直後はウィンドウサイズを小さく送って輻輳が起きなければ次はもっと大きなデータを送るといった感じで通信していきます。

暖気を行うことで前述した外部サービス連携で先にコネクションを貼ってある程度通信しておけばこのスロースタートをバイパスすることが可能となる。

gihyo.jp

またmodperlやらmodpythonを直接出してるケースじゃなければLBやらproxyサーバとの通信もTCPを使ってるはずなのでインターナルなサービス間でもこのメリットは活きてくる。(keepaliveをOFFってない限りは)

まとめ

結局、大局的にみたらwebシステムの場合はC-S間のネットワークなり描画なりがボトルネックになって詰まるだろうし正直全体の数%を改善したところでって感じはしますが多分それなりに意味はあると思う。ただ暖気が必要なほどシビアならここで挙げたメリットは対策方法が他にもあるのでそっちの方が良いかも。

具体的には

  • ディスクアクセスを減らせる -> 静的ファイルはtmpfs使ってメモリに乗せておく
  • 外部サービスとのコネクションを事前に張れる -> サービス起動シーケンスに入れよう
  • TCPでのスロースタート問題に対応できる -> 同上

構成を変えずに暖気を行いたいケースがあれば使えそう。(多分)

【Linux】ImageMagickのTIPS

画像にボーダーをつける

色に迷ったらWebセーフカラーから

convert _01.png -bordercolor "#CCCCCC" -border 2x2 01.png

PNGのdpiを変更する

convert _result.png -density 144 -units PixelsPerInch result.png

from: https://saba.omnioo.com/note/2040/2040/

画像を結合する

https://qiita.com/foo9/items/f930358d379c0fa9ceb5

【Redis】memtier_benchmarkでredisのベンチマークを取る

github.com

memtier_benchmarkを使うとredisやmemcachedベンチマークを取得できるらしく使ってみました。

dockerを使ってさくっとやってみます。-tでスレッド数-cでコネクション数-nでそれぞれのコネクションでどの程度のリクエストを投げるかを調整できます。

docker run --rm redislabs/memtier_benchmark:latest -s ${SERVER_IP} -p 6379 -a test12345 -t 4 -c 30 -n 10000
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 100%, 285 secs]  0 threads:     1200000 ops,    5476 (avg:    4208) ops/sec, 233.43KB/sec (avg: 178.51KB/sec), 22.04 (avg: 1746.48) msec latencyncy

4         Threads
30        Connections per thread
10000     Requests per client


ALL STATS
=========================================================================
Type         Ops/sec     Hits/sec   Misses/sec      Latency       KB/sec
-------------------------------------------------------------------------
Sets          383.16          ---          ---   1955.85700        29.51
Gets         3827.39         0.00      3827.39   1725.52400       149.09
Waits           0.00          ---          ---      0.00000          ---
Totals       4210.55         0.00      3827.39   1746.48400       178.60

見るべきなのはOps/SecでしょうかGetとSetがそれぞれどの程度でているかを確認できます。

pmapをみてみる

特に面白いものは見れなかった。arm特有の何かしらを期待したけど特にはなさそう。anonになってるあたりが恐らくredisのデータ領域でしょうか。read onlyなanonの用途はさっぱりわからず。

Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000    1276     672       0 r-x--  redis-server
000000000073f000      24      24      24 rw---  redis-server
0000000000745000      92      92      92 rw---    [ anon ]
00000000011a3000     132      56      56 rw---    [ anon ]
00000039fa800000     128      40       0 r-x--  ld-2.12.so
00000039faa20000       4       4       4 r----  ld-2.12.so
00000039faa21000       4       4       4 rw---  ld-2.12.so
00000039faa22000       4       4       4 rw---    [ anon ]
00000039fac00000       8       4       0 r-x--  libdl-2.12.so
00000039fac02000    2048       0       0 -----  libdl-2.12.so
00000039fae02000       4       4       4 r----  libdl-2.12.so
00000039fae03000       4       4       4 rw---  libdl-2.12.so
00000039fb000000    1576     508       0 r-x--  libc-2.12.so
00000039fb18a000    2048       0       0 -----  libc-2.12.so
00000039fb38a000      16      16       8 r----  libc-2.12.so
00000039fb38e000       8       8       8 rw---  libc-2.12.so
00000039fb390000      16      12      12 rw---    [ anon ]
00000039fb400000      92      72       0 r-x--  libpthread-2.12.so
00000039fb417000    2048       0       0 -----  libpthread-2.12.so
00000039fb617000       4       4       4 r----  libpthread-2.12.so
00000039fb618000       4       4       4 rw---  libpthread-2.12.so
00000039fb619000      16       4       4 rw---    [ anon ]
00000039fc000000     524       4       0 r-x--  libm-2.12.so
00000039fc083000    2044       0       0 -----  libm-2.12.so
00000039fc282000       4       4       4 r----  libm-2.12.so
00000039fc283000       4       4       4 rw---  libm-2.12.so
00007fa14b600000  370688  331656  331656 rw---    [ anon ]
00007fa1621fd000       4       0       0 -----    [ anon ]
00007fa1621fe000   10240       8       8 rw---    [ anon ]
00007fa162bfe000       4       0       0 -----    [ anon ]
00007fa162bff000   10240       8       8 rw---    [ anon ]
00007fa1635ff000       4       0       0 -----    [ anon ]
00007fa163600000   10240    2048    2048 rw---    [ anon ]
00007fa164000000    2048    2048    2048 rw---    [ anon ]
00007fa16436b000   96852       0       0 r----  locale-archive
00007fa16a200000    2048    2048    2048 rw---    [ anon ]
00007fa16a5f9000      16      16      16 rw---    [ anon ]
00007fa16a60f000       4       4       4 rw---    [ anon ]
00007fff55af5000      84      20      20 rw---    [ stack ]
00007fff55b8f000       4       4       0 r-x--    [ anon ]
ffffffffff600000       4       0       0 r-x--    [ anon ]

armだけではないけどlibpthread.soを読んでるのは若干不思議だったけどredisはシングルスレッド動作というのはメモリの管理のみでバックグラウンドで行うAOFとかメモリのevictは別スレッドでやるらしい。6.0以外でもスレッドが複数いるのはこれが原因。AOF書きながらクライアント応答もすると思いったけどそんなわけはなかった。(計算量の多い処理があると処理がブロックされるのは変わらないので注意が必要)

blog.orz.at

エラーはどういう時に出るのか

公式FAQにあったが基本的には性能低下はネットワークが原因になることが多いらしい。次にメモリでメモリの使用率が高くなると書き込みたいメモリのサイズの探索が走ったりswapが走ったりするので遅くなる。

redis-documentasion-japanese.readthedocs.io

evictするのも良さそうだけどevictに追いつくくらいのメモリ使用率とかだとそれはそれで性能は低下しそう(未検証)

メモリの使用率とかを見てみる

infoとかで見れるやつ。この辺は何が違うのかを理解して見ていく必要があると感じた。

redis.io

used_memory

Redisがアロケータ (標準libc, jemalloc あるいは tcmallocなどの代替のアロケータ)を使用して割り当てた総バイト数

実装はこの辺

// この辺数をredisはatomicにメモリサイズとして使っている
static redisAtomic size_t used_memory = 0;

// updateはこの関数
#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))

// この関数を読んで取得
size_t zmalloc_used_memory(void) {
    size_t um;
    atomicGet(used_memory,um);
    return um;
}
used_memory_rss

オペレーティングシステムから見たRedisが割り当てたバイト数 (別名 常駐セットサイズ)。これは top(1) と ps(1)のようなツールによって報告された数です

psなどで出力されるrssの値なのでバイナリ自身や共有ライブラリもこの値に含まれる。

実装はこの辺。procfsをそのまま除くという感じだった。_SC_PAGESIZEとかやってるけど特殊環境で走らせるとこの辺の数値が4kを返さなかったりするからバグりそう。

size_t zmalloc_get_rss(void) {
    int page = sysconf(_SC_PAGESIZE);
    size_t rss;
    int fd, count;
    char *p, *x;

    snprintf(filename,256,"/proc/%ld/stat",(long) getpid());
    if ((fd = open(filename,O_RDONLY)) == -1) return 0;
    if (read(fd,buf,4096) <= 0) {
        close(fd);
        return 0;
    }
    return rss;
}

以下はそれぞれの関係式をまとめてみた。

used_memory_rss > used_memory

関係が一番良い動作。バイナリとかライブラリ以外は全てredisのメモリアロケーション配下になっている

used_memory > used_memory_rss

場合はswapしている領域をredisが使っている可能性がある。rssにはswap領域は加わらない

used_memory_rss >>> used_memorn

redisが管理しているメモリよりもOS的にはもっと使っているように見えている(メモリの断片化)。

used_memory_peak

Redisによって消費されたピークメモリ (バイト単位)

used_memoryの過去の最大値。rssの最大値ではないので注意

実装はredisServerというバカでかい構造体で保持している。仕事以外では読みたくないぐらいでかいw

github.com

mem_fragmentation_ratio

used_memory_rssとused_memory間の比率

【k8s】プロダクションで使うためのマニフェストの書き方

概要

kubernetesをプロダクションで使うならネットに転がっているマニフェストをコピペだけで運用していくのは設定が足りなくなるケースがある。そのために何が必要かをまとめたTIPS的な記事

Pod

  • リソースの割り当てを行う。(requestsはサーバのスケジューリングに使われるため)

ライフサイクルの設定

  • Entorypointで事前処理を行う
  • init Containersを利用する
  • Sidecarパターンを使う
  • postStartを使う
  • preStopを使う
  • リスタートのポリシーを書く
  • terminationGracePeriodsSecondsを設定する
  • ローリングアップデートなりでダウンタイムがない設定を行う
  • [Node|Pod]のアフィニティを使う(Nodeセレクターなども)

イメージ

  • タグを必ず設定する
  • latestタグは使わない
  • イメージの元は信頼できるサイトを使っているか

死活監視

  • livenessProbeを使う
  • readinessProbeを使う
  • startupProbeを使う

セキュリティ

  • allowPrivilegeEscationsがfalseになっているか
  • runAsUserでrootを設定しない
  • capabilitiesが不用意に設定されていないか

その他

  • ローカルdirとか使ってないか
  • HPAが設定されているかどうか

参考