数日前にmemcached-1.4のリリース候補が出ましたので、今日はその最新版と、それを使ったメモリ節約の運用法を紹介します。厳密にいうと、ご紹介させていただくmemcachedのメモリ節約機能は1.3のbetaから存在し、過去にこちらで取り上げました

memcached-1.4.0-rc1

1.4 RCは基本的に1.3.* betaで発見・報告されたバグの修正やコードベースの改修が主な内容です。詳しいリリースノートはこちらになります。

ダウンロードはこちらです。

新しいバージョンのmemcachedはバイナリプロトコルの導入以外に地味に生まれ変わっています。例えばコードベースを時代の流れにあわせて、シングルスレッドサーバのビルドをとり除いたり、過去から問題視されていたグローバルロックたちの一つを排除し、SMPパフォーマンスを向上させたことなどです。また、このエントリーの本題であるmixiにとって嬉しい新機能も登場しましたので、それを次に紹介します。

CASを使わない人はメモリを節約しよう

memcachedにはCASをエミュレートした機能が存在し、CASの値を64ビット整数で表現しています。この値は今まではレコードを表現する構造体のメンバーとして記録されていました。現在はこのデータをダイナミックにメモリ上でアラインできるようになっています。したがって、CASを使わない設定 (-C オプション) でmemcachedを起動すると、1レコードに対して8バイトのデータを節約する事ができます。

1レコードにつき8バイトの節約と聞くと大した数字に聞こえないかもしれませんが、たったの1000万レコードでおよそ76MBのメモリ空間を節約する事ができます。この機能によるインパクトはウェブサービスの様に粒度の細かなキャッシュを多数つくるユースケースでは言うまでもなく大きいです。弊社でも検証してみたところ、嬉しい結果を得る事ができました。

余談ですが、この節約機能はもともと開発のロードマップには載っておらず、Wikipediaの中のパフォーマンス屋さんの要望で実現されました。

簡単な検証

私のブログで使った例と同じで恐縮ですが、まず10万個のKey/Valueをキャッシュする簡単なスクリプトを用意します。

#!/usr/bin/perl
 
use strict;
use warnings;
 
use Cache::Memcached;
use constant {
    NITEMS => 100000,
    KLEN   => 8,
    VLEN   => 16,
};
 
sub random_key {
    my @chars = ('a'..'z', 'A'..'Z', '0'..'9');
    my $buf = "";
 
    foreach (1..KLEN) {
        $buf .= $chars[rand @chars];
    }
    return $buf;
}
 
my $memc = Cache::Memcached->new ({
    servers => ['localhost:11211'],
    debug   => 0,
});
 
my $val = 'a' x VLEN;
 
for (1..NITEMS) {
    $memc->set(random_key, $val);
}

そしてこのスクリプトを新機能を適用していない以下のオプションで起動したmemcachedのプロセスに対して行います。

$ memcached -m 1024

そして以下がstatsコマンド(一部)の結果です。

...
STAT bytes 9000000
STAT curr_items 100000
STAT total_items 100000
STAT evictions 0
END

さて、次に新しいメモリ節約オプションを有効にしたサーバに同じスクリプトを走らせます。

memcached -m 1024 -C

そして結果です。

...
STAT bytes 8200000
STAT curr_items 100000
STAT total_items 100000
STAT evictions 0
END

ご覧のとおり、781KBのメモリを節約したことがわかります。これだけのメモリをさらにキャッシュにつかえるので、インフラのコストパフォーマンスが向上し、ウェブサービス屋さんにとって嬉しい機能であることがわかっていただけるかと思います。

まとめ

memcached-1.4.0-rc1と新しいオプションを紹介しました。メモリ節約オプションを有効にすると一つのキャッシュレコードに対して、8バイトのメモリ領域を節約することができ、塵も積もれば山となる論理で、結果的にインパクトのあるメモリ空間の節約になります。

このRCには開発者たちだけではなく、様々な人たちや組織のフィードバックがこめられているので、ぜひ試していただけたらなと思います。

先日、Drizzleのスレッド管理を担うコアの一部分がモジュール化され、勉強がてらMySQLのスレッド管理の設計を調べてみました。その時のメモ(だから文が少し固いかも)と、Drizzleでの戦略を今回のエントリーで公開します。

最後のDrizzleでは?セクションまではプログラミングの教科書に載っている様な典型的なセオリを述べているだけなので、MySQLのインターナルに詳しい方は最後まで飛ばした方が良いかもしれません。

ちなみにソースはMySQL 5.1とMySQL 6.0のドキュメントです

現在の仕組みと制限

現在のMySQLでは新たなクライエント接続に対して専用スレッドを割り当てるといった古典的なモデルを採用しています。割当を実際に行うのはコネクションマネージャというNetwork Interfaceを監視するスレッドであり、OSによってlistenするインターフェイスが異なります。

マネージャは無駄に新たなスレッドを生む事を避けるために、接続に割り当てるスレッドがキャッシュに存在するかを調べます。役目を終えたスレッドはスレッドキャッシュに空きがあった場合にキャッシュされますが、ない場合は破棄されます。

Thread Cache Model

スレッドキャッシング

スレッドキャッシュのサイズはthread_cache_sizeというシステム変数によって定められ、デフォルト値は0です。したがって、デフォルト設定のMySQLは新たな接続に対し新たなスレッドを生成し、接続が完了したらスレッドを破棄するという処理を繰り返します。thread_cache_size変数は起動時と運用中のどちらでも変更する事が可能です。

問題点

このモデルの問題点は当然ながら、スレッドの数がクライエント接続数に比例して増えるという事です。連続するスレッド生成と破棄のコストを考慮にいれると、効率のよい仕組みとは呼べません。さらにスレッド数(接続数)の増減に比例して消費するサーバ資源が増えてしまうので、個々のスレッドが持つスタック領域を抑えるという実装になっています。

MySQL 6.0.4からの仕組み

従来のモデルで問題視された多くの同時接続を円滑に処理するためにスレッドプーリングが採用されます(実装はされている)。コネクションマネージャは特定の接続にスレッドを割り当てるのではなく、完全にクライエントからのリクエストを受信した後に処理待ちのタスクキューに追加します。

サーバはサービススレッドのプールを管理し、処理待ちのリクエストをプール内の使用可能なスレッドに割り当てます。リクエスト処理の終えたスレッドは再び使用可能な状態となり、他リクエストの処理を行います。

ワーカスレッドたちはサーバの起動時に生成され、サーバプロセスが終了するまで生き続け、特定のコネクションに縛られません。また、プールのサイズは固定であるため、消費する資源がコネクション数に比例して増える事がありません。とまあ、典型的なプーリングモデルですね。

スレッドプーリングの弱点

多数のコンプレックス(多めの演算力とI/Oを伴う)クェリーが頻繁に発行される環境では、スレッド数の上限設定が仇になる場合があります。全スレッドが複数のクライエントからのリクエストを処理する場合、当然ながら新たなリクエストに対応する事ができず、サーバが固まってしまいます。この問題はスレッドプールの限界値を上げる事で対処する事が推奨されています。限界値はthread_pool_sizeというシステム変数により定めれており、これを起動時に指定する事で変更が可能です(デフォルト値は20)。

運用法や設定

スレッドプーリングはmemcachedで実績があるlibeventを使って実装されており、適用するにはconfigure時に--with-libeventを指定する必要があります。スレッドモデルの選択はサーバの起動時にthread_handlingシステム変数によって判別されます。デフォルトのスレッドマネージメントモデルは接続に対して専属のスレッドが割り当てられる “--thread_handling=one-thread-per-connection”モードです。スレッドプーリングを適用するには、--thread_handling=pool-of-threadsオプションでサーバを起動します。

Drizzleでは?

全てのソリューションやアルゴリズムにはユースケースによって必ず何らかの弱点が存在します。Drizzle Projectではサーバの軽量化と同時に、幅広い問題を柔軟に解決するためにMicroKernelアーキテクチャを採用しています。これはスレッドスケジュリングも例外ではなく、MySQLのスレッドマネージメントコードは完全にコアから抜き取られ、分離されています。

正確に述べると、MySQL内でいう、one-thread-per-connectionとpool-of-threadsの両モードのコードが完全にモジュール化されています。これは何が嬉しいかといいますと、まずmutexをコアから外部(モジュール)に押し出す事によってlock contentionの対応をサーバから分離できる事です(小人さんがロックレスなモジュールを書いてくれるかも!)。あとは、ソースコードがスマートに整理できるというところでしょうか。例えばスレッドプーリングってどうやって実装されているの?と気になった場合はまずプラグインのインターフェイスを見て、

#ifndef DRIZZLED_PLUGIN_SCHEDULING_H
#define DRIZZLED_PLUGIN_SCHEDULING_H
 
typedef struct scheduling_st
{
  bool is_used;
  uint32_t max_threads;
  bool (*init_new_connection_thread)(void);
  void (*add_connection)(Session *session);
  void (*post_kill_notification)(Session *session);
  bool (*end_thread)(Session *session, bool cache_thread);
} scheduling_st;
 
#endif /* DRIZZLED_PLUGIN_SCHEDULING_H */

モジュールの実装(drizzle/pluginディレクトリ以下)を覗くだけで、簡単に読む事ができます。例えばスレッドプーリングの実装だと以下のファイルをお好みのエディタで開くとよいでしょう:

$ vi drizzle/plugin/pool_of_threads/pool_of_threads.cc

とまあ、パッケージ内を比較的簡単に泳ぐ事ができ、システム改善を従来の設計よりも簡単にハックする事が可能です。

まとめ

MySQLのスレッドマネージメントの現在と今後、そして制限や弱点を調査し、メモを公開しました。また、MySQLが抱える、このドメインの問題に体するDrizzleのアプローチも共有し、lock contentionの対応をコアから押し出す利点を紹介しました。

今回のエントリーだけだとMicroKernelの詳しい設計やモジュールの事が全然わからん!と突っ込まれそうですが、そこまで書くと膨大なエントリーになりそうなので、また別の機会に紹介します。同時に今後もDrizzle Projectで面白かったり、嬉しいネタが出てきたらちょくちょく共有していきたいと思います。

こんにちは、某Perl界隈のIRCチャンネルでPythonがマイブーム的なKY誤爆をしてしまったtmaesakaです。

先日、以前から興味のあったGoogle App EngineMemcache APIについて少し調べ、こちらに英文で報告したのですが、今日は日本語で要約したまとめを紹介します。

まず軽く前置きですがGoogle App Engine (GAE)とは、Googleが提供しているウェブアプリケーションをGoogleのインフラ上でスケーリングや冗長化など、ある程度のノウハウや資金を要求される面倒な事を気にせずに運営できるプラットフォームです。つまり、典型的なPaaSの例であり、サービスの運営コストをelastic(伸縮)にします。昨今バズワード化しつつあるクラウドコンピューティングの一種でもあります。

GAEのインフラはGoogleより提供されているAPIセットを用いて利用します。その中にはon memory cacheがMemcache APIという形で提供されています。インターフェイスはmemcachedと同様Key/Valueベースのもので、アプリケーションのトータルパフォーマンス向上に役立つAPIです。

このAPIはmemcachedを連想させられるネーミングですが、実際にGAEのドキュメントを読むと、こう記述されています:

The Memcache API has similar features to and is compatible with memcached by Danga Interactive.

つまりmemcached互換で、なおかつ似ていると書かれていているだけです。普通はこの辺で納得するのでしょうが、私はこういった文を見ると調べたくなる性格なので、ちょっと深入りしてみました。

実際にMemcache APIを使うのは簡単で、’memcache’ モジュールをGAEパッケージからインポートします:

from google.appengine.api import memcache

あとはアプリケーションから必要に応じて、各種APIメソッドをコールするだけです。

プロトコル違反なKeyでセットしてみる

memcachedのASCIIプロトコルでは、Keyの長さは250バイトまでという制限があります。それ以上の長さのKeyを送信するとmemcachedはERRORを返します。では、GAEはどうでしょう?

遊び気分で300バイトのKeyで適当な値をSetしようとする以下のコードを走らせてみたところ:

from google.appengine.api import memcache
 
memcache.flush_all()
test_key = 'x' * 300
 
if not memcache.set(test_key, 'some_val'):
    print 'Failed to set'
    quit()
 
print "Looks like we're good = " + memcache.get(test_key)

以下のエラーがローカルのApp Serverから返ってきました:

Keys may not be more than 250 bytes in length, received 300 bytes

あらあら、上記のエラーだけを見ると明らかにmemcachedっぽい動作ですが、memcachedに合わせているだけかもしれません。Keyの長さ制限はドキュメントに記述されていないので、このエラーに遭遇したら驚くデベロッパーもいるかもしれませんね(レアなケースですが)。

さて、次はもっとマジメに独特な情報で比較しましょう。

メモリ消費量で比較してみる

memcachedではサーバインスタンスがどれだけのデータ(総バイト数)をキャッシュしているかをstatsコマンドで容易に取得する事が可能です。同様に得られる情報は制限されるものの、Memcache APIでも同じ事が可能です:

from google.appengine.api import memcache
 
stats = memcache.get_stats()
if stats: print stats['bytes']

ここでトリビアですが、memcachedから取得できるキャッシュサイズは純粋に全てのkeyとvalueを合計した値ではなく、各レコードのオーバヘッド(item構造体のサイズ)を加算した値です。

この値を実際にGoogle上にデプロイしてあるアプリが返す結果と比較してみました:

1 x 128 byte value with a 5 byte key
Memcache API: 133 bytes
memcached-1.2.6: 184 bytes

64 x 128 byte values with 5 byte keys
Memcache API: 8512 bytes
memcached-1.2.6: 11776 bytes

128 x 128 byte values with 5 byte keys
Memcache API: 17024 bytes
memcached-1.2.6: 23552 bytes

なんとGAEは一切オーバヘッドを報告しません。これでMemcache APIのバックエンドに様々な可能性が広がりましたね。例えばネットワーク越しに分散されているGoogle Sparse Hashかもしれないし、アプリケーションのキャッシュマネージメントの事情でstats情報を独立した仕組みで保持しているのかもしれません。次のセクションで分散やマネージメントに関する推理を紹介します。

運用を考えてみる

特定のアプリケーションに関するキャッシュ情報の取得」は言葉にすると簡単に聞こえるものの、実際は簡単ではありません。まず考えなくてはならない事は、アプリケーションがどの様にキャッシュスペースを与えられているのかという点です。

例えばアプリケーションに対して必要に応じた数の専用インスタンスを用意するか、「専用」という概念を捨て、Keyに対しApplication Identifierをappend/prependして、他アプリケーションとキャッシュプールを共有するといったモデルです。どちらのモデルを採用するにしても、キャッシュのstatsという概念はインスタンス毎に存在するものなので、「このアプリケーションがxxx」という情報の管理と保持には独立した仕組みが必要と考えられます。つまりアプリケーション毎にindexが必要だという事です、例えばInverted Indexあたりのデータストラクチャ(appid -> stats_postings)で保持するなど。

上記の様な工夫を行わなければ、statsリクエストに対して毎回、ルックアップと演算がオンザフライで発生する事になり、低効率なシステムとなります。したがって下の層でmemcachedを使っていたとしても、統計管理が独立していれば、オーバヘッド抜きの純粋な値が保持される可能性もあります。GAEが保持と管理が楽な情報だけをstatsインターフェイスで提供している説明にもなります。

  • hits
  • misses
  • byte_hits
  • items
  • bytes
  • oldest_item_age

さいごに

Google App Engineで遊んでいて感動した事は、ドキュメンテーション(英語のやつ)がとてつもなく解り易い事です。あまり賢くなくて挫折屋な私でも、すぐに自立して簡単なコードが書けるレベルまでいけたほどです。

プログラミング言語面では現状、Pythonしかサポートされていませんが、もし今後いろいろな言語、特にRubyあたり(私は書けませんが)が対応されたら実に凄いプロダクトになるんじゃないかな〜、と思いました。Perl対応も完了したらかなり盛り上がるかも。

ここしばらく、水面下でBrian Akerを代表とするMySQL/SUNのエンジニアたちや、業界のオープンソースハッカーたちとMySQLをスリムダウンさせたマイクロカーネルRDBMSを開発していたのですが、本日アナウンスされたので、日本語でご紹介させていただきたいと思います。

Drizzleとは?

Drizzleとは必要のないものは一切存在しない、最低限でパフォーマンス重視な「MySQLよりシンプルで、軽く、安定して、高速な」 MySQLのforkです。マイクロカーネルアーキテクチャを採用したので、必要のないものは後付けできる構成です。こういった目標もあり、現在、Drizzleの開発チームはMySQLをドラスティックにリファクタリングしています。

コミュニティベースのプロジェクト

Drizzleで大事な事は、Drizzleはコミュニティベースのプロジェクトであるという事です。Montyのブログエントリーでも語られていますが、Drizzleでは、MySQLに長年存在していた致命的なバグが迅速に直されたり、プロダクトの進化を待たずとも、パッチやアイデアを誰でも貢献できます。したがって、Drizzleの開発はBazaarなどのオープンソースソフトウェアを使って行われており、LaunchpadやFreenodeなどの公開されている場所で行われています。

MySQLの替わりではない

一つ明確にしておかなければならないポイントは、DrizzleはMySQLの替わりになるプロダクトではありません。Drizzleのターゲットはとても限られた、RDBMSのカスタマイズを必要とする中~大規模なウェブアプリケーションです。

例えば、中~大規模で使われるMySQLは、memcachedと同様、セキュアな環境で運用されると仮定されます。したがって、安全であるという割り切りで、ユーザ名とパスワードをシンプルに, “hoge”と”hoge”に設定したとします。この場合、運用者としてはユーザ名とパスワードに意味はありませんが、MySQLの内部では、適当なクレデンシャルでも、ACL関連のロックが適用され、パフォーマンスを影響する要素となります。また、Query Cacheに関しても、個人レベルのデータベースでは効果的ですが、アプリケーションの規模が少し大きくなると意味を成さないコンポーネントとなります。

中~大規模なアプリケーションの開発者たちは、これらの機能をカスタマイズしたいと思ったり、実際に自社用にカスタマイズしています。こういうった人たちの仕事を楽にするためにDrizzleがあるのであって、一般的な場面で使うRDBMSとしては、MySQLが正しい選択だと思います。

マイクロカーネル アーキテクチャ

ウェブ業界で働く方で、最近のMySQLには使うことのない機能が多々あると感じる方は少なくないと思われます。Drizzleでは取り急ぎ、Stored Procedures, Triggers, Prepared Statements, Views, Query Cache, Event Scheduler, ACL, UNIX Socketをサーバから取り外しました。替わりに、私達はこれらのコンポーネントをプラガブルにするマイクロカーネルなアーキテクチャを目指します。

マイクロカーネルなら、Query Cacheをmemcachedのプールで代用してデータベースサーバたちに共通のキャッシュを共有させる事も可能になります。

オープンソースのライブラリを積極的に使う

MySQLは現状、歴史的経緯によって自社製のライブラリを使って実装されています(my_*系)。ですが、自分たちだけで開発するよりは、これらのライブラリをglib、libxml2、libpcreなどのオープンソースコミュニティによって開発・メンテナンスされているものに置き換える事によって、不都合やパフォーマンス改善を迅速、かつ高いクオリティを維持する事が可能です。したがって、私たちは実績のあるオープンソースのライブラリを積極的に使おうという結論に至りました。

ダウンロードしたい!

Drizzleは絶賛開発中で、リリースの日程も未定ですので、リンク経由でダウンロードできるパッケージはありません。ですが、先ほど説明したようにDrizzleは公開されたリポジトリを使って開発しているので、bzrの使い方を少し覚えたらソースを入手できます。以下がLaunchpadのプロジェクトページへのリンクです:

ちなみにbzrでは、trunkからbranchを切るのは、”bzr branch 親” だけで出来ます。

まとめ

今回はMySQLの中の人たちや、ウェブ業界のエンジニアたちが望む、ウェブに優れたRDBMの開発プロジェクトをご紹介させて頂きました。Drizzleが完成すると、デベロッパーはApacheの様に様々なプラグインを書いて、ある程度、「自分仕様」なRDBMSを構築することが可能になります。

後はDrizzleのFAQを読んで頂けたらな、と思います。

memcachedの生みの親であるBradから了解を得た上で先日、memcached Users Group :: Japanというコミュニティを立ち上げました。

http://memcached.jp

コミュニティに関して

目的は国内におけるmemcachedの普及と技術界隈の人たちが集まり、スケールに関する様々な情報が集まる場を形成する事です。ウェブパフォーマンスの向上に関わっている人たちに限らず、初心者の人たちも大歓迎です。

それと英語メーリングリストでは聞きにくい質問を気楽にしていただけたらな、と思います。

コミュニティの運営は運用面やその他もろもろを考えた結果、Google Groupsを使って行なう事にしました。ですから、memcached.jp自体はウェブページを一枚しか配信していません。

グループへの参加は以下のURLで行なえます:

http://groups.google.com/group/memcached-ja

イベントしようぜ!

7月か8月にグループメンバーで30人規模の勉強会を東京近辺で行ないたいと思います。memcachedを導入している方々がどの様にアーキテクチャに組み込んでいるか(例えばwrite-through and/or read-through cacheとして)などの発表の場になればなと思います。

色々なバックグラウンドの人たちが同じ部屋に集まるわけですから、現状のアーキテクチャで困っている事を発表すると大概の悩みは解消されるかもしれません。

発表していただける方はお手数ですが、以下に内容を送っていただけますでしょうか:

admin@memcached.jp

正確な日程は調整中ですが、決まり次第グループ内で告知いたします。

では皆さま、宜しくお願いいたします。