memcashed,Slab Allocator

memcashed

多くのWebアプリケーションは,RDBMSにデータを格納し,アプリケーションサーバでそのデータを引き出してブラウザ等に表示させている。しかしデータが大量になったり,アクセスが集中すると,RDBMSの負荷があがり,データベースのレスポンスが悪化し,Webサイトの表示が遅延するなど大きな影響がでてしまう。

memcachedは高性能な分散メモリキャッシュサーバである。通常,データベースへの問い合わせ結果を一時的にキャッシュすることで,データベースへのアクセス回数を減らし,動的なウェブアプリケーションの高速化やスケーラビリティの向上のために利用されている。

f:id:wanwan_bowbow_ilovecat:20181015181316p:plain


キャッシュサーバ

キャッシュ サーバとは、インターネット上のWebサイト等で提供されているコンテンツの複製を保存し、ユーザからの要求があった場合に、本来コンテンツを提供すべきサーバ(コンテンツ サーバ)に代わってコンテンツを配信するサーバのことである。

memcachedの特徴

memcachedは非常に高速に動作する分散キャッシュサーバで,特徴としては以下のような点が上げられる。

1.シンプルなプロトコル
2.libeventによるイベントハンドリング
3.内蔵のオンメモリストレージ
4.memcached同士での通信は行わない分散方式


シンプルなプロトコル

memcachedのサーバとクライアントの通信は,XMLなどの複雑なフォーマットは利用せず,シンプルな行ベースのプロトコルによって行われる。行ベースのプロトコルなのでtelnetmemcachedにデータを保存して,保存したデータを取得することもできる。

$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
set foo 0 0 3     (保存コマンド)
bar               (データ)
STORED            (結果)
get foo           (取得コマンド)
VALUE foo 0 3     (データ)
bar               (データ)


libeventによるイベントハンドリング

libeventとは,Linuxではepoll,BSD系のOSではkqueueなどのイベントハンドリング機能(特定のイベントに反応して特定の処理を行う機能)をラップして,サーバに対するコネクション数の増加に対してもO(1)のパフォーマンスを発揮する共通仕様で利用できるようにしたライブラリである。memcachedはこのlibevetのライブラリを利用しているので、高いパフォーマンスをLinuxBSDSolarisなどのOS上で発揮することができる。

内蔵のオンメモリストレージ

memcachedに保存したデータは,パフォーマンスを向上させるように設計されたmemcached内蔵のメモリストレージに貯められる。データはすべてメモリ上にのみ存在するので,memcachedを再起動したり,OSを再起動するとすべてのデータが消えることになる。またメモリが指定された容量に達すると,LRU(Least Recently Used)に基づいて利用されないキャッシュから自動的に削除されていく。memcached自体キャッシュのためのサーバとして設計されているので,データの永続化についてはあまり考慮されていない。

memcached同士での通信は行わない分散方式

memcachedは「分散」キャッシュサーバだが,分散に関しての機能はサーバ側には備わっていない。memcached同士で通信を行って情報をシェアすることはない。どのようにして分散を行うかというと,すべてクライアント側の実装に依存するようになっている。
f:id:wanwan_bowbow_ilovecat:20181015181342p:plain

メモリを整理して再利用するSlab Allocationメカニズム

昨今のmemcachedはデフォルトでSlab Allocatorというメカニズムを使ってメモリの確保・管理を行っている。このメカニズムが登場する以前のメモリ確保の戦略は,単純にすべてのレコードに対してmallocとfreeを行うといったものだった。しがしながら,このアプローチではメモリにフラグメンテーション(断片化)を発生させてしまい,OSのメモリマネージャに負荷をかけ,最悪の場合だとmemcachedのプロセスよりも重くなってしまうという問題が発覚した。この問題を克服するために生まれたのがSlab Allocatorである。

Slab Allocatorは、確保したメモリをあらかじめ決められたクラスサイズに応じた固定長の固まりに分けて,フラグメンテーション問題を完全に克服する。Slab Allocationの仕組みは簡素で,確保したメモリをさまざまなサイズの固まり(chunk)に分けて,同じサイズの固まりをクラス(chunkの集合,またはchunkのサイズを定めるクラス)に整理する。

f:id:wanwan_bowbow_ilovecat:20181015182634p:plain

また,slab allocatorには「確保したメモリは再利用する」という目標もある。したがって,memcachedは一度確保したメモリは解放せず,常にchunkを再利用する。

Slab Allocatorの主な用語

・Page
デフォルトで1MB確保され,Slabに割り当てられるメモリ領域。Slabに割り当てられた後に,slabのサイズに応じたchunkに切り分けられる。

・Chunk
レコードをキャッシュするためのメモリ領域。

・Slab Class
特定のサイズのchunkをまとめるクラス。

Slabにレコードをキャッシュする仕組み

memcachedは受けとったデータのサイズを参照し,データサイズにもっとも適したslabを選ぶ。memcachedはslab内の使用可能なchunkのフリーリストを保持しているので,このリストを基にchunkを選び,そこにキャッシュする。

f:id:wanwan_bowbow_ilovecat:20181015183231p:plain

Slab Allocatorの弱点

Slab Allocatorの開発により,当初の課題であったフラグメンテーション問題は解消されたが,新たなメカニズムにより新しい課題がmemcachedに生まれた。その問題は,固定長なメモリ確保のアプローチにより,確保したメモリを有効活用できないということである。例えば100バイトのデータを128バイトのchunkにキャッシュすると,余りの28バイトが無駄になる。

f:id:wanwan_bowbow_ilovecat:20181015183509p:plain


クライアントが送ってくるデータの共通サイズがあらかじめ解っている,もしくは同じサイズのデータしかキャッシュしないユースケースであれば,そのサイズに適したクラスのリストを使い,無駄を抑えることが可能である。ただし残念なことに,現状ではこのチューニングを行うことはできず,将来の課題として残っている。しかしながら,slab classたちのサイズ差をチューニングすることは可能である。

Growth Factorを使ったチューニング

memcachedはスタートアップ時にGrowth Factorという因子を指定して(-f オプション)⁠,slab間のサイズをある程度制御することが可能である。デフォルト値は1.25だが,このオプションが開発される以前は“⁠powers of 2⁠”戦略といって,2が固定だった。

Lazy Expiration

memcachedは内部的にレコードがexpireしたかの監視を行わない。替わりにgetする際にレコードのtimestampを見ることで,そのレコードがexpireしたかをチェックする。このテクニックをlazy(なまけた)expirationと呼ぶ。したがって,memcachedはexpireの監視にCPUタイムを消費しない。

LRU: 有効的にキャッシュからデータが消える仕組み

memcached はtimeoutしたレコードの領域を優先的に再利用するが,それでも新しいレコードを追加する領域がなかった場合はLeast Recently Used(LRU)という仕組みを使って,領域を確保する。LRUはその名の通り,「⁠最近もっとも使っていない」レコードを削除対象にする仕組みである。したがってmemcachedがメモリ不足になった場合(slab classから領域を取れなかった場合)⁠,最近参照されていないレコードを検索して,その領域を新しいレコードに割り当てる。


参考:memcachedを知り尽くす:特集|gihyo.jp … 技術評論社