Dive Deep Memcached ~ 入門から実装の確認まで ~

Memcached

Memcachedは以下の特徴を持ったインメモリのKVSとなります。

  • シンプル

  • マルチスレッド処理

  • ロジックの半分はサーバで、半分はクライアント

  • サーバ同士では通信は行わず、互いに独立

  • コマンドはO(1)

  • TTLに従った失効と、LRUによるeviction

  • libeventによるイベント駆動処理

なお、合わせてMemcachedのソースコードの各種ファイルの概要についての詳細は以下の記事をご覧ください。

MemcachedのSETコマンド実行時のソースコード中のフローの詳細については、以下の記事をご覧ください。

レポジトリはこちらにあります。

コマンド

一覧

  • コマンド 24個 (ASCIIモード。バイナリモードの場合は後述)

  1. set

  2. add/replace

  3. append/prepend

  4. get/gets

  5. gat/gats

  6. delete

  7. flush_all

  8. incr/decr

  9. cas

  10. touch

  11. stats

  12. slabs

  13. lru <tune|mode|temp_ttl>

  14. lru_crawler

  15. watch <fetchers|mutations|evictions>

  16. version

  17. verbosity

  18. misbehave

  19. quit

  • 統計確認

    • stats

    • stats items

    • stats slabs

    • stats sizes

    • stats detail on|off|dump

    • stats settings

    • stats cachedump

    • stats conns

    • stats extstore

    • stats reset

  • Slabs

    1. slabs reassign \r

    2. slabs automove <0|1|2>

  • LRU Crawler

    1. lru_crawler <enable|disable>

    2. lru_crawler sleep

    3. lru_crawler tocrawl <32u>

    4. lru_crawler crawl <classid,classid,classid|all>

    5. lru_crawler metadump <classid,classid,classid|all>

実行例

incr/decrは引数で与えた数値の分だけ値を増加/減少させる。

あるユーザーがデータを取得してから書き換えるまでに、他のユーザーによって値が書き換えられていないことを確認するためには、getsでCAS IDを取得しておき、casコマンドでCAS IDを添えて実行することで、書き換えられてないことを確認しながら値を設定できる。CAS(Check-and-Set, Compare-and-Swap)の略。

Memcachedのflush_allは、TTLを付与するだけなので、サーバーに影響は与えない。

gat(Get And Touch)コマンド、gets、touchコマンドはTTLの更新。ASCIIモードでは1.5.3で追加されました。バイナリモードでは1.4.8(1.6からのバックポート)で利用できるようになっています。

メモリ管理

Slab Allocation

動作

Slab Allocationとは、予め決められた大きさごとにSlab Classというグループを用意して、ある大きさのメモリーをSlab class毎に決められた大きさをChunkとして分割していきます。

アイテムを保存する際には、そのサイズより大きい、最も小さいChunkのサイズをもったSlab classに保存されます。Slab classの対象のページに空きChunkがあれば保存され、なければメモリープール(-mで指定したバイト数)からそのSlab class用にページ(-Iで指定したバイト数)を確保する動作となります。

確保したメモリは基本的に解放せずにchunkを再利用する挙動になります。

Chunkの様子は、memcachedコマンドの-vvオプション、もしくは-vvvオプションで確認することができます。また、付属のmemcached-toolを利用することでも確認することができます。

Slab classのChunkのサイズ

Chunkの大きさについては、最小のChunkのサイズ(chunk_size)からSlab Classが変わる毎に、前のクラスの大きさからchunk_size_growth_factorを掛けた大きさとなっていきます。 Chunkのサイズがmax_item_sizeの値を超えない最も大きいところまでクラスが用意されることになります。 なお、データとして利用できるメモリー量は、Memcached全体として利用できるメモリー(max_cache_memory)と接続に伴うオーバーヘッド(memcached_connections_overhead)を除いた分となり、そちらも考慮されます。

背景

Slab Allocation導入前のメモリ確保では、アイテムの保存の度に、mallocとfreeを行うもので、メモリーフラグメンテーションを引き起こしやすいものでした。そのため、フラグメンテーションの対策として、アプリケーションレイヤーでメモリーの管理しながらメモリーを再利用するようになりました。

上記より、メリット/デメリットとして以下のことが挙げられます。

  • メリット : メモリー再利用によるメモリーフラグメンテーションの抑制

  • デメリット : 固定長による無駄なメモリーを消費することがある

Redisではメモリ確保で頻繁にmallocの呼び出しを行っており、OS内部の状況によってはレスポンスの処理速度にブレが出てくる可能性が大きくなっていましたが、Memcachedではメモリ管理はOSに依存しすぎず自身でメモリ管理を行うようにし、フラグメンテーション肥大化防止のために取られたSlab Allocationで予め用意したスラブのメモリにデータを保存するので、レスポンスに安定感が期待できます。

Linux kernelのSlab Allocation

Linux kernelでもSlab Allocationという仕組みがあり、これはページ単位でメモリ確保を行うBuddy allocationとは別に、より小さな単位でメモリを管理を行うものとなっております。

Linuxでは3つの種類があり、現在は主にデフォルトでslub allocatorがデフォルトで使用されるようになっています。

  • slub allocator

  • slab allocator

  • slob allocator

Slab Allocator https://www.kernel.org/doc/gorman/html/understand/understand011.html Linuxカーネル4.1のSLUBアローケータ(ドラフト) https://kernhack.hatenablog.com/entry/2019/05/10/001425

LRU

Memcachedには、Redis同様にアイテムの削除はActive/Passiveな方法があり、以下の方法となります。

  • Active: Lazy Expiration

  • Passive: LRU Crawler

ただし、メモリーの再利用が間に合わず、メモリー確保の際に不足した場合には、削除対象としてLRUの挙動に従ってevictionする挙動となります。 MemcachedのLRUは、Slab class単位となるため、全体としてみるとLRUの挙動から離れることもあります。 また、MemcachedはTTLの切れたキーをevictionするようにベストエフォートで行われますが、キューの末尾の削除されることもあります。

管理方法

1.4以前

双方向Linked Listを使用。 この構成では8スレッドまでが限界で、read intensiveな状況で48スレッドまででLRUロックの都合上スケールの限界。 また、一度に数百のキーを取り出すと、mutex競合や、不均一なレイテンシー、CPUを多く使用する状況が発生した。

最適化として60秒に1回、アイテムをbumpさせ、頻繁にアクセスするアイテムのmutexロックやmutationを避ける工夫がされていました。

それ以降

サブLRUとしてHOT/WARM/COLDのようにセグメント化されたLRUを使用。 各セグメントが自身のmutexロックを持ちます。

各アイテムの状態は、以下の2つに分類されます。

  • FETCHED : キーがリクエストされたことがある。

  • ACTIVE : 1秒以内にアクセスされ、アイテムがはじき出されたか移動するときに取り除かれる

各セグメントの状態は、以下の通りとなります。これらの状態遷移の管理は、LRU Maintainerが1つのバックグラウンドスレッドとして実行します。

  • HOT : 新規アイテムが最初に分類される状態。アイテムは強い局所性と短いTTLを持つことが多いので、一度キューの最後まで達すると、WARMでアイテムがbumpされず、アイテムがACTIVEであればWARMへ移動し、INACTIVEであればCOLDに移動する。確率的なキューとして振る舞う。

  • WARM : ワークロードをスキャンするバッファとして振る舞う。2度以上アクセスされた場合にこの状態になる。ロックの競合を減らしている状態であればTTLだけ生き残る。キューの末尾まで移動したアイテムがACTIVEであればキューの先頭に移動。そうでなければCOLDに移動

  • COLD : 最もINACTIVEなアイテムを含む。HOT/WARMの状態から移動してくる。メモリーがいっぱいのときはevictされる。

また、TEMPセグメントもあります。

  • TEMP : 新規アイテムが分類できる状態。TEMPに入れられたアイテムは指定した時間だけbumpさせないように設定することができる。

上記のままでは、アクセスパターンに一貫性がない場合、キューの真ん中の状態のアイテムが失効しているとサイズが特に大きな場合にはスペースが無駄になり、サイジングが難しい。

それを克服するためにLRU Crawlerというバックグラウンドスレッドにより、非同期にアイテムを一通り確認し、失効しているものはreclaimする。 Slab Classが大きいほど、アイテム数が少ないので、すばやくスキャンできる。 LRU CrawlerはHOT状態はスキャンする価値がないことを知っていることもある一方で、HOTに大量の短いTTLのキーがあれば、比較的大きなCOLDをスキャンすることを避けている間は、消去することもある。 10秒以上TTLのある1MBのアイテムは参照されるまで、LRU Crawlerは見ない動作をします。

HOT/WARM/COLD LRUのメリット/デメリット

  • メリット

    • LRUロックの影響が小さい : アイテムが直接取り出したときにLRUのキューから他のアイテムがdumpされない。一度に数百のキーでも取り出せる。

    • アイテムが非同期にbump : 短時間のSET/GETはHOT/WARMのバランスの悪さが発生

    • 追加のmutexでwrite操作のスケール

    • アイテムあたりのメタデータサイズが増加しない

  • デメリット

    • あるSlab classで大量のSETがあったときに、COLDがオーバーフローし、“direct reclaim”モードが発生する。HOTやWARMからアイテムを移動している間、ワーカースレッドがブロックされる。

    • TTLの異なるアイテムが混在すると、アイテムが取り出されるかキューの末尾に移動するまで短いTTLのメモリーが無駄になる

Replacing the cache replacement algorithm in memcached - Dormando (October 15, 2018) https://memcached.org/blog/modern-lru/ Memcached for Dummies https://work.tinou.com/2011/04/memcached-for-dummies.html memcachedを知り尽くす http://gihyo.jp/dev/feature/01/memcached

キー分散

Memcachedでは、クラスター内の各ノードはそれぞれ独立しており、互いにレプリケーションといった挙動はしません。キャッシュノード間で、対象のキーが保存されたノードの指定は、クライアント側に依存しています。クライアント側からのキーの分散方法としては、主に以下の2つの方法が考えられます。

  • Modulo

  • Consistent Hashing

Modulo

単純なハッシュ計算で hash(key) mod n によりノードを決めます。 ノードの追加/削除時に、移動する必要があるキーの数は、n は新しいノード数として、(n - 1)/n となります。

Consistent Hashing

円上の0から2^32までの均等な点に対して、各キャッシュノードのハッシュ値を求め、配置します。キーに対しても同様にハッシュ値を求め、キーの保存の際には最も近いサーバのところに保存されます。 ノードの追加、削除の際には、そのノードに入れ替えに伴い、アイテムを移動させます。

ノードの追加/削除時に、移動する必要のあるキーの数は約 1/nとなります。

イベント駆動処理

Memcachedのイベント駆動処理には、libeventが利用されています。 主に memcached.c/thread.c で使われており、以下の種類の関数が利用されています。

関数の詳細は、ソースコード https://github.com/libevent/libevent/blob/master/include/event2/event.h https://github.com/libevent/libevent/blob/master/include/event2/event_compat.h

event.h File Reference http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/event_8h.html http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/event_8h.html

EVENT(3) http://man7.org/linux/man-pages/man3/event.3.html

libevent – an event notification library https://libevent.org/

Memcachedのマルチスレッド処理

POSIXスレッド(pthread)

Memcachedではマルチスレッド処理にpthreadが利用されており、以下の関数が使用されています。

処理内容

デフォルト状態でworkerスレッドの起動数が4つのため、以下のようにスレッドIDが順番に割り当てられていき、workerスレッド間はリクエストをラウンドロビンで割り当てられます。特に連続した処理でなければ、手元の環境ではコマンド処理がThread 3で実行されることが多いことを確認しています。

Redisのリクエストのシングルスレッドで処理する性質とは対照的に、Memcachedではリクエストをマルチスレッドで処理を行い、デフォルトでは4つのworkerスレッド(memcached_thread)を起動するようになっています。 ワーカースレッドが多すぎてもロックの取り合いで、パフォーマンスが上がるわけではありません。

Memcachedは1つの大きなHash Tableでデータを管理しているが、拡張の際に、assoc_maintenanceスレッドが使用されている。それ以外のスレッドについては後述。

info threadsの出力例

ロック種類

Memcachedではスレッドセーフな実装のために以下のロックを使い分けています。

  • tls.c

    • ssl_ctx_lock

  • logger.h/logger.c

    • mutex in _logger struct

    • logger_stack_lock

    • logger_atomics_mutex

  • extstore.c

    • mutex in _store_page

    • mutex in store_io_thread

    • mutex in store_maint_thread

    • mutex in store_engine

    • stats_mutex in store_engine

  • slabs.c

    • slabs_lock

    • slabs_rebalance_lock

  • cache.h

    • mutex in cache_t

  • memcached.h/memcached.c

    • mutex in thread_stats

    • conn_lock

  • storage.c

    • storage_write_plock

    • storage_compact_plock

    • lock in storage_compact_wrap

  • assoc.c

    • maintenance_lock

  • crawler.h/crawler.c

    • lock in crawler_expired_data

    • lru_crawler_lock

  • thread.c

    • lock in conn_queue

    • lru_locks[POWER_LARGEST]

    • conn_lock

    • atomics_mutex

    • stats_lock

    • worker_hang_lock

    • cqi_freelist_lock

    • item_locks

    • init_lock

  • items.h/items.c

    • lru_locks[POWER_LARGEST]

    • lru_maintainer_lock

    • cas_id_lock

    • stats_sizes_lock

    • mutex in _lru_bump_buf

    • bump_buf_lock

以下も参考にしていただけるかと存じます。

Multithreading in memcached was originally simple: https://github.com/memcached/memcached/blob/master/doc/threads.txt

スレッド内容

Slabs Automove

Slab class間でメモリを移動するバックグラウンドスレッド。以下のコマンドで実行。slab_maintenanceスレッドで実行。

  • 0: Slabs Automoveのスレッドの無効化

  • 1: Slab class中で、空きchunkが3ページ以上あるときに、ページをグローバルプールに返す。ページは必要に応じて他のSlab classに再割当てが行われるようになる。

  • 2: evictionがある度にメモリを移動するという非常にアグレッシブな方法。ワークロードのアクセスパターンをよく理解していない限り、こちらを有効にしたままにすることは推奨されない。

Slabs Reassign のコマンドにより手動でSlab class間でメモリの再分散させることも可能。その場合、パラメータ slab_reassign を有効にしておく必要がある。

LRU Tuning

  • ~1.4.14: 以前のLRUの管理方法は異なり、1つの双方向Linked Listにより、末尾からLRUで追い出される対象。

  • 1.4.24~: LRUをHOT,WARM,COLDのセグメントに分割して、lru_maintainerがバックグラウンドスレッドで、アイテムをセグメント間で移動させる。COLDがevictionの対象となります。HOT/WARMのセグメントの領域の割合はチューニング可能。lru_maintainer, lru_crawlerがデフォルトで無効

  • 1.4.33~: lru_maintainer, lru_crawlerがデフォルトで有効

temporary_ttlで4つ目のTEMP領域を作ることも可能。TEMP領域でLRU内のアイテムを追い出したり、他のSlab classに移動することを防ぎ、LRU crawlerの問題や負荷を減らす。

詳細は以下をご覧ください。 https://github.com/memcached/memcached/blob/master/doc/new_lru.txt

LRU Crawler

要求されたSlab classの末尾から先頭に向けてアイテムを確認していき、失効したものがあればメモリを解放します。TTLで長いものと短いものが混在している状況だが滅多にアクセスされない場合に特に有効です。こちらの機能を使用するとレイテンシーやCPU使用量が増加する可能性があります。lru_crawlerのパラメータでLRU Crawlerは有効になります。ソースコード中では、スレッド名はitem_crawlerという名前が使われています。

  • メリット: ActiveにTTLが切れたアイテムのメモリーを解放することができる。

  • デメリット: レイテンシーとCPU使用率の増加

Watchers

Memcachedへ接続し内部で起こっていることの調査に利用できる。以下のようにして有効化する。loggerスレッドで実行されている。

  • fetchers : アイテムを内部でfetchする度にログ出力。setコマンドでもアイテムの置き換え前にitem_getするので出力する。Multigetsだと複数回出力

  • mutations : アイテムを保存するほとんどの場合に出力

  • evictions : キャッシュからアイテムがevictionされたときに情報を出力。

仕組み

Memcachedでは1つのMain baseとデフォルトで4つのWorker Threadのthread baseがあり、処理が行われます。 Slabという低レイヤーでのメモリー管理とItemという高レイヤーでの管理の層に分けて管理を行い、それらをコネクションを管理する部分を通してこれらを扱います。 また、Memcachedでは処理中、内部で状態遷移の仕組みを使って各状態における処理完了後に別の状態に遷移して条件に応じた処理を行っています。

状態遷移

Memcachedで利用される状態遷移の各状態は以下のものがあります。

  • conn_listening

  • conn_new_cmd

  • conn_waiting

  • conn_read

  • conn_parse_cmd

  • conn_write

  • conn_nread

  • conn_swallow

  • conn_closing

  • conn_mwrite

  • conn_closed

  • conn_watch

  • conn_max_state

https://github.com/memcached/memcached/blob/master/memcached.h#L171-L191 上記状態は、conn_statesのenumとして、memcached.hで定義されています。

リクエスト受付時

処理の流れとしては以下の通りとなります。

  1. リクエスト受付時にはMain baseでサーバソケット上でListenしてlibevent経由でWorker Threadにコネクションを渡します。入ってきた接続はラウンドロビンで各Worker Threadのコネクションのキューへと入れます。

  2. コネクションキューに新規アイテムが追加されるとreadイベントがアクティベートされ、Worker Thread上でコールバック関数(event_handler)が呼ばれます。

  3. event_handler関数からdrive_machine関数が実行され、ここでコネクションにおける状態遷移等を管理しながら、各種コマンドの実行の呼び出し等を行います。この仕組みにより1つのWorker Thread上でも複数のコネクションを処理することができます。

コマンド処理時

main baseでの処理

main baseでのイベントループの処理について、リクエストを受け付ける際には、最初にdrive_machine関数が実行し、コネクションの状態(conn_state)がconn_listeningになっています。ここでコネクションの受付とworker threadの1つのコネクションキューへタスクの追加を行います。

Worker Threadのbaseでの処理

ここではGETコマンド処理の例とします。

  1. Worker Thread上では、ソケット受付時のreadイベント作成時、コネクションキューからタスクを取り出し後、conn_stateの状態をconn_new_cmdに変更します。

  2. その後、コネクションの枯渇を避けるために少しずつ様子を見ながら、conn_stateをconn_waitingへ変更。

  3. conn_stateをconn_readへと変更します。

  4. conn_stateがconn_readへと変更されたら、ソケットからの読み込みを行い、conn_stateをconn_parse_cmdへと変更します。

  5. conn_stateがconn_parse_cmdと変更されたら、入力されたコマンドのパース処理を行い、入力された命令毎に関数の実行を行います。

  6. その後、conn_stateがconn_mwriteへ遷移し、クライアントへリクエストされたアイテムを返す準備ができる状態となります。

  7. conn_stateがconn_mwriteへ変更されたらsendmsgのシステムコール関数を使ってクライアントへ結果を返します。

  8. conn_stateをconn_new_cmdへ変更し、新規コマンドを受け付ける状態となります。

SETコマンド処理も類似した内容となります。読み込み完了時にはcomplete_nread関数から(ASCIIモードを利用時には)complete_nread_ascii関数が実行され、ここでout_string関数を通して"STORED"の文字が返される挙動となります。

フロー

リクエスト受付処理

TCP/UDP利用時

ソケット処理時にはserver_socket関数を通してソケット関数の初期化が実行されます。具体的には以下の流れとなります。

  1. memsetでaddrinfo構造体の初期化

  2. TCPかUDPで条件分岐

    • 2-1. TCPの場合、socktypeをSOCK_STREAM

    • 2-2. UDPの場合、socktypeをSOCK_DGRAM

  3. getaddrinfo関数

  4. new_socket関数

  5. TCPかUDPで条件分岐

    • 2-1. TCPの場合、setsocketops関数

    • 2-2. UDPの場合、maximize_sndbuf関数

  6. bind関数

  7. listen関数

  8. TCPかUDPで条件分岐

    • 2-1. TCPの場合、conn_new関数

    • 2-2. UDPの場合、dispatch_conn_new関数

  9. listen_conn構造体の設定

Unix Socket

Unix Socketを利用する場合は、server_socket_unix関数を通して初期化が実行されます。クライアントとサーバが同一ホスト上にある場合はこちらを利用することで効率よく処理ができます。

  1. socket関数からnew_socket関数を実行

  2. fcntl関数でノンブロッキングO_NONBLOCKの設定

  3. 古いソケットファイルが残っている場合は、unlink関数を実行して削除

  4. flag変数の値がゼロ以外の場合は、setsockopt関数を実行して、アドレスをbindで再利用し、定期的なKeep_aliveメッセージを再送信し、送信されていないメッセージがある場合には、ソケットがクローズさせないようにする

  5. bind関数

  6. listen関数

  7. conn_new関数

コマンド実行時

conn_stateがconn_read時には以下のフローで関数が実行されます。

  1. try_read_command関数

  2. process_command関数

  3. 実行するコマンドに応じて条件分岐

    • 3-1. get/gets 4. process_get_command関数 5. item_get関数 6. add_iov関数 7. item_update関数(process_get_commandでtrue時) 8. conn_stateをconn_mwriteへ変更

    • 3-2. add/set/replace, prepend/append, cas 4. process_update_command 5. item_alloc 6. CAS IDの設定(process_get_commandでtrue時) 7. conn_stateをconn_nreadへ変更

    • 3-3. incr/decr 4. process_arithmetic_command関数 5. add_delta関数

    • 3-4. flush_all 4. item_flush_expired関数

    • 3-5. delete 4. process_delete_command関数

    • 3-6. stats 4. process_stat関数

    • 3-7. quit 4. conn_stateをconn_closingへ変更

O(1)

Memcachedのコマンドは計算量がO(1)、すなわちキーの数によらず実行時間が一定になるようにされています。一方で、全キーを操作することが出来ないデメリットもありました(*)。

(*) Memcached 1.4.18~では、LRU Crawlerを有効にしていれば、lru_crawler metadump all で取得することができます。 デバッグインターフェイスとして stats cachedump が用意されており取得可能ですが、スラブクラスを指定して取得する形となります。また、部分的な取得(上限2MB)で、かつメモリーをロックする上に取得も重いものとなります。

No reply/Quiet

No reply/QuietオプションはASCIIコマンドで利用すると、リクエストに対するエラーが適切に表示されないため使うべきではないとされています。 mutativeなコマンドでパケットが返ってくるのを待つのを避ける必要がある場面で利用することはできます。 バイナリモードでは適切に実装されています。

Commands https://github.com/memcached/memcached/wiki/Commands

stats

Memcachedでは、サーバ内部の状態を調査するツールとしてstatsコマンドを提供しています。以下コマンドの実行結果の例となります。

stats

stats items

stats sizes

各アイテムのサイズとそれに対応する数。stats sizesを行うためには、事前にstats sizes_enableを実行しておく必要があります。パラメータでtrack_sizesを指定することでも可能です。

stats slabs

(total_chunks * chunk_size) - mem_requestedがSlab class中で無駄なメモリー量となります。

stats conns

stats settings

domain_socketには、-s でUnixソケットファイルを指定した際に値が入る。(Netcatでは、-U /tmp/memcached.sock のように指定して接続する)

stats cachedump

stats cachedump 12 100 コマンドのようにSlab Class ID(stats itemsなどで確認可能)と取得数を指定します。

stats cachedumpは指定したスラブクラスの表示件数だけ出力しますが、最大で2MBでのレスポンスの制限があります。 https://github.com/memcached/memcached/blob/master/items.c#L596 また、実行時間が長く、キャッシュをロックする挙動となります。その他、Slab Class単位でしか取得できない制約もあります。

一方で、lru_crawler metadump allはサイズ制限もなくキャッシュをロックせずに全キーをリスト化して出力します。 以下、lru_crawler metadump all の実行結果となります。

statsコマンド詳細 https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L613-L1089

その他詳細 https://github.com/memcached/memcached/blob/master/memcached.c#L3518-L3603 https://github.com/memcached/memcached/blob/master/slabs.c#L516-L549

付属コマンド

./sizes

統計情報やアイテムのサイズ等を表示

./timedrun 3 memcached

第一引数で指定した時間後の第二引数で指定したプロセスを中断

./testapp

テストコード。assert関数で意図した変数の値になっているか確認

memcached-tool

dumpオプションを指定すると以下のようになります。

その他

scripts/ 以下に以下のようなコマンドがあります。

  • damemtop

  • memcached-automove

  • memcached-automove-extstore

  • memcached-init

  • memcached.sysv

  • start-memcached

ベンチマーク

ベンチマークには、memslap/memaslap/mcperf/memtier_benchmarkなどのものがあります。

memslap/memaslapはlibmemcachedで提供されています。そのため、yum install libmemcachedを実行後に利用できるようになります。

twemperf(mcperf)は以下のコマンドでインストールすることが可能です。

memtier_benchmarkのインストール方法は以下の記事をご覧ください。

memtier_benchmark の導入 (memcached-tool で Slab の内容確認) https://hayashier.com/memtier_benchmark-introduction/

バイナリプロトコル

テキストプロトコルのパース処理の省略による処理の高速化とテキスト形式である脆弱性の問題に対応するものとなっています。24バイトの固定長を利用します。

フォーマット

パケットフォーマット

上記24バイトのヘッダー部分について

リクエストヘッダー

レスポンスヘッダー

ASCIIモードでは、キー長の制限が250バイトでしたが、バイナリモードでは、2^16(=65,536)バイトまで扱えるようになっています。 また、コマンドの種類増加など拡張性も上がっています。 リクエストとレスポンスの違いは、vbucket id(以前は予約済み領域) と Status となります。

各項目

Magic : マジックナンバー。リクエストパケットは 0x80、レスポンスパケットは0x81 Opcode : コマンドを表すコード Key length : key長 Status : レスポンスのステータスコード Extras length : Extras長 Data type : 予約済み領域 vbucket id : コマンドの仮想バケット Total body length : extra + key + value のバイト数 Opaque : リクエストで送った値と同じ値をレスポンスを返すことで対応付けることに利用 Will be copied back to you in the response CAS : CAS機能で使用するデータのバージョン

詳細

Opcodeはコマンドのコードに相当し下記コマンドがあります。

レスポンスのStatusは以下の種類のものがあります。

BinaryProtocolRevamped https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped

SASL

Simple Authentication and Security Layerの略。インターネットプロトコルへの認証とデータセキュリティのためのフレームワーク。アプリケーションプロトコルから認証機構を分離し、SASLをサポートするもので利用可能 Memcachedでは、1.4.3以上で--enabled-saslオプションを付与してビルドすることで利用可能です。

以下のような定義済みのSASL機構がある。

  • EXTERNAL

  • ANONYMOUS

  • PLAIN

  • OTP

  • SKEY

  • CRAM-MD5

  • DIGEST-MD5

  • NTLM

  • GSSAPI

Memcached ではバイナリーモードのみで以下のものが提供されています。 サーバが対応している認証機構のリスト化と認証リクエスト、認証継続のコマンドがあります。

  • コマンド

    • List Mechanisms 0x20 None None

    • Start Authentication 0x21 Mechanism Auth Data

    • Authentication Step 0x22 Mechanism Auth Data

エラー文は以下の種類があります。

  • エラー

    • 0x20 認証必須/失敗

    • 0x21 追加の認証必要

SASLAuthProtocol https://github.com/memcached/memcached/wiki/SASLAuthProtocol SASLHowto https://github.com/memcached/memcached/wiki/SASLHowto

Simple Authentication and Security Layer https://ja.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer

Simple Authentication and Security Layer (SASL) https://tools.ietf.org/html/rfc4422 https://tools.ietf.org/html/rfc2222 The Kerberos V5 ("GSSAPI") Simple Authentication and Security Layer (SASL) Mechanism https://tools.ietf.org/html/rfc4752

なお、Memcached 1.5.13以上で--enable-tlsオプションを付与して、TLSの機能を利用することも可能になりました。 https://github.com/memcached/memcached/pull/440

ハッシュ関数

  • jenkins

    • Jenkins ハッシュ関数は、Bob Jenkinsによって設計されたマルチバイトキー向けの非暗号化のハッシュ関数の集合

    • 種類としては、one_at_a_time, lookup2, lookup3などがあり、Memcachedでは1997年に公開されたlookup2が使用されている。

    • hash関数で自由に使って良いとするハッシュ関数を公開している

      • http://burtleburtle.net/bob/hash/doobs.html

      • http://burtleburtle.net/bob/c/lookup2.c

    • Memcachedでの実装はjenkins_hash.c https://github.com/memcached/memcached/blob/master/jenkins_hash.c

  • murmur3

    • 一般的なハッシュベースのルックアップに適した非暗号化ハッシュ関数。2008年にAustin Applebyによって作成。SMHasherのハッシュ関数の中の一つ

    • MurmurHash3 : https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp

    • MurmurHash2 : https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp , https://sites.google.com/site/murmurhash/

    • RedisではMurmurHash2、MemcachedではMurmurHash3が採用されている。

実装

アイテム

定義

https://github.com/memcached/memcached/blob/master/memcached.h#L476-L503 Memcached中で扱われるアイテムは以下のように_stritem構造体で定義されています。ハッシュ値が衝突した場合は、チェイン法を使用し、h_next変数に相当しています。

アイテム保存/削除

https://github.com/memcached/memcached/blob/master/assoc.c#L154-L191

Slab class

定義

https://github.com/memcached/memcached/blob/master/slabs.c#L28-L41 Slab classは以下のようにslabclass_t構造体で定義されています。Slabclass毎にアイテムのリストを持っておりslots変数に相当します。また、slab_listでSlab(Chunkの集合)のリストを保持しています。

Slab初期化

https://github.com/memcached/memcached/blob/master/slabs.c#L153-L232 Slabの初期化は以下のslabs_init関数で行われています。preallocをパラメータで有効にしているかで最初の挙動が分岐しています。事前割当てのオプション(-Lオプション)指定時には初めにスラブのためにメモリーを割当てておきますが、そうでない場合は不足している場合に適宜確保していく形となります。

コネクション

定義

https://github.com/memcached/memcached/blob/master/memcached.h#L601-L706 コネクション情報はconn構造体として以下のように定義されています。

コネクションのキュー

https://github.com/memcached/memcached/blob/master/thread.c#L44-L50 受け付けたリクエストはCQ_ITEM構造体のリストとして以下のように定義されています。

CQ_ITEM構造体は以下のように定義されており、対象のアイテムやコネクション情報が格納されています。

新規コネクション受付からWorkerスレッドへの橋渡し

コネクションの初期化

https://github.com/memcached/memcached/blob/master/memcached.c#L6121-L6135 TCPリスナーの場合、server_socket関数でmain baseを引数としてconn_new関数で呼ばれています。

https://github.com/memcached/memcached/blob/master/memcached.c#L540-L709 conn_new関数で受け付けたリクエストの情報をconn構造体にセットして初期化。

https://github.com/memcached/memcached/blob/master/memcached.c#L148 listen_connはグローバルは静的変数として以下のように宣言されています。

リクエスト受付時

https://github.com/memcached/memcached/blob/master/memcached.c#L5499-L5908 リクエストを受け付けた際には、drive_machine関数中からaccept_new_conns関数が実行され、

https://github.com/memcached/memcached/blob/master/thread.c#L305-L312

https://github.com/memcached/memcached/blob/master/memcached.c#L5306-L5347 先程初期化したコネクションをlistenしています。

https://github.com/memcached/memcached/blob/master/memcached.h#L566-L585 libeventのスレッドは、LIBEVENT_THREAD構造体として以下のように定義されています。この要素中にconn_queue構造体のnew_conn_queue変数が含まれています。

https://github.com/memcached/memcached/blob/master/thread.c#L334-L337 リクエストを受け付けた際には、workerスレッドに対して以下の箇所でthread_libevent_process関数を呼び出すように設定しています。

https://github.com/memcached/memcached/blob/master/thread.c#L427 thread_libevent_process関数中で、先程のコネクションのキューからコネクションを取り出して、接続を介ししています。

リンク

memcached - a distributed memory object caching system https://memcached.org/ Memached Wiki https://github.com/memcached/memcached/wiki

Last updated