Skip to content
shunter1112 edited this page Sep 16, 2013 · 2 revisions

多くの製品版のウェブサイトで主力となるソリューションであるにも関わらず、 Memcacheはしばしば完全な潜在能力を引き出されないまま使われていることがあります。 ほとんどの開発者はget,set,そしてdeleteのオペレーションについてしか知りません。 しかしながら、Memcacheは開発者により進んだアプリケーションをより少ないコードと 更なるパフォーマンス改善とともに構築することを助ける事が出来る、多くのオペレーションセット を持っています。

この記事では一連の現実世界のユースケースとともに、より使いやすい、進んだMemcacheのオペレーション についての概要に触れ、アプリケーションの実装とパフォーマンスに対する影響を 見せたいと思います。

前提条件/事前準備

この記事は、以下の物を持っている事を仮定しています:

テスト環境

以下のアプリケーション をHeroku上に、MemCachierアドオン と、進んだMemcacheのオペレーションが自分自身で実行できるようなコマンドラインが 完全にそろった状態になるように準備してください。直接コマンドを入力することは、 これからの説明を読んで、シンタックスを括弧な物にし、よりあなたのMemcacheに対する親和性が 高まることへ取って代わるでしょう。

この[ワンクリック Memcache 環境設定](http://cloner.heroku.com/apps/memcache-rails-sandbox)を つかって、実際のMemcacheのHeroku上でのオペレーションを試してみてください。

今、あなたのHerokuアカウントにデプロイされているテストアプリケーションのコピーを持っているはずです。 あなたのアプリケーションのURLをメモしてください。http://serene-mesa-2821.herokuapp.com/ の用な形をしていると思います、serene-mesa-2821はHeroku上でのあなたのアプリケーションの名前です。 シェルでのやり取りを確立するために、この名前が必要になってきます。

シェルでのやり取り

あなたのターミナルからheroku run consoleとあなたのアプリケーション名を使って、 Herokuで実行中のアプリケーションのインスタンスに接続します。Memcacheのクライアント のライブラリをロードして、setgetの基本コマンドを実行して設定が行われたか 確認してください。

:::ruby
$ heroku run console -a app-name
irb> cache = Dalli::Client.new
irb> cache.set("foo", "bar")
=> true
irb> cache.get("foo")
=> "bar"

この例では、インタラクティブなRubyのシェルを、Dalliクライアントと共に使い、Memcacheを 読み込み、やり取りを行います。これは単にデモの目的であって、多くの言語のmemcacheドライバは 似たようなコマンドをサポートしているはずです。

このガイドのこれからは、、あなたがこのシェルを実行していて、クライアントライブラリが ロードされていることを前提とします。

キャッシュの有効期限

Memcacheを使っているときに最もチャレンジングなことは、きれいなコードを書いている段階で キャッシュの腐敗を防ぐ事です。ほとんどの開発者はMemcacheにデータを保存し、 それに変更が合った場合に更新したり削除したりします。この戦略はとても早く散らかる傾向にあります。 Memcacheのコードはアプリケーション上のあらゆるところで一杯になります。 RailsのSweepers はこの問題に対して役立ちますが、他の言語やフレームワークは似たような代替案がありません。

コードが複雑にならないようにするための一つのシンプルな戦略は、データをMemcacheに追加するときに 有効期限を設けることです。有効期限付きのデータは自動的に期限か来ると破棄されます。 ほとんどのアプリケーションは、静的なアセット、ヘッダ、フッタ、ブログのポストなどの ほとんど変更しない内容について時間ベースのキャッシュ有効期限によって利点を得る事が出来ます。

サンドボックスのシェルでは、以下のコマンドを実行する事で10秒後に有効期限がくるように設定できます。

有効期限を"0"に設定する事は、値の有効期限が永遠に失効しない事を意味します。
:::ruby
irb> cache.set("expires", "bar", ttl=10.seconds)
irb> cache.get("expires")
=> "bar"
.. wait 10 seconds ..
irb> cache.get("expires")
=> nil

与えられた内容に対して失効させるために明示的なアクションが 必要ないのがわかると思います。ttlの値の分だけ時間が過ぎたあと、 そのキーのgetの値は単にnilを返すようになります。

有効期限を30日かそれ以上で秒数で指定した場合、Memcacheは有効期限を Unixエポック時間に変換し、絶対時間として扱います。 気をつけてください、40日を秒数で指定する事は 有効期限を1970年に設定する事に なってしまい、不明な結果を生成する事になります。

キャッシュのクリア

開発者はしばしばキャッシュの戦略を、新しいキャッシュのコードを書く度に 何度か変更することがあります。頻繁にキャッシュの戦略を変更する事は、 キャッシュを汚し、デバッグを困難な物にします。開発中にキャッシュの 戦略が変更される場合はいつでも、キャッシュの全ての値をクリアするために flushコマンドが実行されるべきです。

サンドボックスのコンソールの中で、flushを実験するために、以下のコマンドを します :

:::ruby
irb> cache.set("foo", "bar")
irb> cache.get("foo")
=> "bar"
irb> cache.flush
irb> cache.get("foo")
=> nil

flush はまた、プロダクション環境にデプロイするときも使われます。しかし気をつけてください。 アプリケーションがキャッシュフラッシュに耐えられないかもしれません。 大きいキャッシュと多くのトラフィックがあるアプリケーションでは、プロダクション環境で はフラッシュを実行しない、もしくはトラフィックがキャッシュなしでもコントロール可能な くらい少ない状態の時に実行するようにしましょう。

MemCachier Heroku アドオンは フラッシュが実行できる Web のダッシュボードを持っています。あなたのHerokuのダッシュボードで アドオンをクリックするか、`heroku addons:open memcachier`をコマンドライン から実行する事で MemCachier のダッシュボードにアクセスできます。

計量カウンタ

Memcacheに保持されている軽量なカウンタは、あなたのアプリケーションの パフォーマンスを下げる事なく、あるイベントについての発生回数を細くするのに 役立つでしょう。カウンターはデバッグ、プロファイリング、そしてトラッキングに 使われます。

例えば、サードパーティ製のAPIに頼っているアプリケーションの場合、何回その APIが使えなかったり、悪いデータを返しているのかを知りたい事があります。 Memcacheのカウンターは、ページの読み込みにほとんど影響なく、データベースも 追加での読み込みを行わないため、理想的な解決策と言えるでしょう。

サンドボックスコンソールでは、以下のコマンドでincr(increment)とdecr(decrement) の実験ができます :

:::ruby
irb> cache.incr("my_counter", 1, nil, 0)
irb> cache.get("my_counter")
=> "0"
irb> cache.incr("my_counter")
irb> cache.get("my_counter")
=> "1"
irb> cache.incr("my_counter", amt=5)
irb> cache.get("my_counter")
=> "6"
irb> cache.decr("my_counter")
irb> cache.get("my_counter")
=> "5"

incrdecrは手動のgetsetによる変更より使われるべきです。なぜなら、 incrdecrはスレッドセーフであり、TCPのRTTがより短く設定されているためです。 incrの属性については、Dalliのドキュメント. を確認してください。

リスト管理

Memcache内のシンプルなリストの保持は、非正規化されている関係を維持するのに 役立ちます。例えば、E-コマースのサイトは最近の購入履歴の小さなテーブルが 必要になるでしょう。シリアライズされたリストをMemcache内に保持し、 新しい購入が合った場合に再計算するよりも、appendprependは データベースのクエリをさけながら、非正規化されたデータを保持するのに 使う事が出来ます。

顧客の最近の購入履歴のリストを更新するのに、かつてのsetのオペレーション を使うかわりに :

:::ruby
cache.set("user_1_recent_purchases", Purchases.recent)

prepend をただ新しいデータを使うだけで、同じ効果を得ながら使う事が出来ます:

:::ruby
cache.prepend("user_1_recent_purchases", product.name + "||")

このやり方は、Memcacheの占有領域をより小さくし、データベースのクエリが すべてのユーザの最近の購入履歴を取得することを防ぎます。

サンドボックスコンソール内で、appendprependを以下のコマンドを実行する事で 実験できます。:

`ttl=0` はキーの有効期限が切れない事を意味し、`:raw => true` は値が生の バイト値で保存される事を指定しています。これは`append`と`prepend`を使うときに 必要です。
:::ruby
irb> cache.set("my_list", "foo", ttl=0, options={:raw => true})
irb> cache.get("my_list")
=> "foo"
irb> cache.prepend("my_list", "bar||")
irb> cache.get("my_list")
=> "bar||foo"
irb> cache.append("my_list", "||baz")
irb> cache.get("my_list")
=> "bar||foo||baz"

この例は、任意の区切り文字(`||`)を使っています。この区切り文字は、 リストのアイテムが区切り文字を決して含まないように、リスト内の 予想されるそれぞれのアイテムの値から決定されるべきです。 加えて、文字列の長さが決まった状態の実装がアプリケーションに されたほうが良いでしょう。さらなるMemcacheのリストに関する情報は [こちら](http://code.google.com/p/Memcached/wiki/NewProgrammingTricks#Managing_lists_with_append_/_prepend) をご覧ください。

appendprependgetsetでの手動の変更よりも使われるべきでしょう。 なぜならappendprependはスレッドセーフだからです。

Memcacheは最大で1MBしかサポートされていません。許されている値の大きさよりも 大きく育つようなリストの作成には気をつけてください。Dalliを含む、 いくつかのクライアントは圧縮をサポートしています。Dalliの場合は、 Dalliに接続時、`:compress`オプションを`true`に設定します。

スレッドセーフ設定

Memcache内で保存されているシンプルなJSONハッシュは、頻繁にサクセスされる 設定の維持に役立ちます。例えば、ウェブサイトが現在どの機能が有効になっているのかを トラッキンングしたい場合、またはどちらのABテストが実行されているかなどです。 しばしばこれらの設定は、便利なように一緒にJSONハッシュの中に 保持されています。

appendprependはJSONハッシュとの関連はありません、なぜならハッシュは 整列されていないからです。そしてsetは、二つのJSONハッシュに対する変更が同時に発生した場合、 一つの変更が失われるかもしれないからです。

比較(Compare)と取り替え(Swap)、つまりcasというオペレーションを使って、 元の値と新しい値の比較を行い、古い値が別の人によって変更されていない場合に限り 値を取り替えます。言い換えるならば、casはスレッドセーフな setです。

サンドボックスのコンソール内では、以下のコマンドを実行するとcasの実験ができます :

この例の中の`cache.cas`はブロックを必要としていますが、これは現在使っている RubyのMemcacheクライアントであるDalliが必要としています。他のクライアント はこの方法に従っているとは限りません。

:::ruby
irb> cache.set("my_json", "{}")
irb> cache.get("my_json")
=> "{}"
irb> cache.cas("my_json") { {key: "val"}.to_json }
irb> cache.get("my_json")
=> "{\"key\":\"val\"}"

Memcacheプロトコルは直接`cas`オペレーションを実装しているわけではありません。 そのかわり、バージョン番号付きの`set`をサポートしています。`set`が、 もしバージョン番号がMemcacheに保持されているキーのバージョンと一致しているときに 限り実行されます。`cas`が実行されているのに対してバージョンを直接プロトコル内で 使う事は、[ABA問題](http://en.wikipedia.org/wiki/ABA_problem)の 対策になります。

いくつかのクライアントは`cas`を`set`のバージョンを渡しながら手動で実装しなければ 行けなくなるでしょう。

Reference

ここで議論されているそれぞれのMemcacheオペレーションは、いかにクイックリファレンス として並べています。APIは詳細まで書いていません。なぜなら、それぞれのクライアントと 言語は別のAPIを持つだろうためです。

オペレーション 説明
有効期限付きset キーとバリューに秒で書かれた有効期限を儲けます。 一度キーの有効期限が失効されたら、キャッシュから削除されます。30日間までの有効期限は現在からの時間の差で計算され、 逆に30日間以上の有効期限は絶対的なUnix時間として計算されます。
flush キャッシュから全てのデータを削除します。プロダクション環境でのこのコマンドの使用は注意してください。-- プロダクション環境のアプリケーションでは、もしキャッシュがない事がDBに対して負荷をかける場合に、ダウンタイムに陥る可能性があります。
incr 整数の値を指定した量だけ増やします。スレッドセーフです。
decr 整数の値を指定した量だけ減らします。スレッドセーフです
append 値の最後に付加します. 元の値はクライアントで生のバイト値として保持されている必要があります。スレッドセーフです。
prepend 値の始めに表れます. 元の値はクライアントで生のバイト値として保持されている必要があります。スレッドセーフです。
cas (もしくはバージョンを使ったset) 他のプロセスによって値が変更されていなかった場合のみ、新しい値をセットします。スレッドセーフです。
Clone this wiki locally