1. 八百万のOSS
  2. #8 GETするだけなのに書き込み..
#8 GETするだけなのに書き込みが走る?memcachedのLRUとCPUの話
2026-06-24 35:13

#8 GETするだけなのに書き込みが走る?memcachedのLRUとCPUの話

キャッシュサーバmemcachedをGoで再実装したcatatsuy。


「マップに入れて返すだけでしょ」と思いきや、メモリを使い切る前にデータを捨てるeviction、最近使われていないデータを管理するLRU、そして素直なLRU実装では「GETがhitしてもリスト更新が走る」という落とし穴がありました。


read-heavyなキャッシュなのに、GETが書き込みに近い処理になる。この自作版ではLRUがボトルネックになり、コード上は1コアしか使わない実装になっていました。自作してみると、mapで返すだけでは終わらない、LRUとロックの難しさが見えてきます。


本家memcachedはHOT / WARM / COLD / TEMPのような世代分けを持ち、GETのたびに毎回LRU順位を真面目に更新するのではなく、active bit、async bump、LRU maintainerなどでリスト更新を分散・近似しています。LRUを真面目にやりすぎるとロックが増えるから、あえて真面目にやらない。その工夫があるからこそ、本家memcachedは複数CPUのある環境でもスケールしやすい。


hentekoと一緒に、プロトコルの単純さと内部実装の難しさのギャップを覗いていきます。


- utsuro: https://github.com/catatsuy/utsuro

- memcached: https://github.com/memcached/memcached

- memcached - a distributed memory object caching system https://memcached.org/blog/modern-lru/

- Redis: https://redis.io/

- OpenAI Codex: https://github.com/openai/codex

- Go: https://go.dev/


─────────────

YouTube: https://youtu.be/KzZSzbZF5ik

Web: https://yaoyorozu-oss.henteko07.com/

X: https://x.com/yaoyorozu_oss

感想

まだ感想はありません。最初の1件を書きましょう!

00:00
最近、AIを使って既存のツールとかを再実装してみるとか、そういった部分が結構あるなと思っているんですけど、実際に自分もいくつかシャリの再発明みたいなことをしていたりするんですけど、片鶴さんもあったりしますかね。
自分もそれ最近思ってて、先日、memcachedの再実装してみたんですよね。うつろって名前なんですけど。
なので今回、うつろの実装を紹介しつつ、最後にmemcached本家の話もできればなと思っています。
お、いいですね。まずはうつろの実装の紹介っていうところからですかね。うつろどういうものなんですかね。
memcachedの再実装なんで、ちょっとmemcachedの基本的なことも話していくんですけど、うつろはmemcachedテキストプロトコルっていうのをベースにしたeメモリのKVSって感じなんですけど、
memcachedのいいところって、そのプロトコルが非常に単純っていうところなんですよね。
memcachedテキストプロトコルというプロトコルが存在してるんですね。
存在していて、すっごい単純なんですよ。この後ちょっとまた話すと思うんですけど、
TCPで、テルネットとか使ってTCPでも手書きしてしゃべれちゃうぐらいのすごい手軽な仕様なんですね。
それで受け取ったデータをKVSとして保存して、それをメモリ上に保存しておく。
なので再起動したら消えちゃうっていうのがmemcachedなんですけど、うつろも同じで再起動したら消えちゃうみたいな感じですね。
メモリ上にキーバリューを構築してるって感じですよね。
そうですね。なので、キャッシュサーバーにすごく便利っていうのがmemcachedなので、割と簡単に実装できるんじゃないと思って作っちゃいました。
やってみたみたいな感じですね。
メモリ上にキャッシュを載せるっていうので、使い方的にはあれですよね。
高速にキーバリュースターとして使いたい場合に使うみたいなイメージですよね、基本的には。
そうですね。本当にキャッシュですね。消えてもいいデータしか入れちゃダメっていう。
消えると困るようなものは載せられないっていう。
そうですね。そういうのはデータベースとかに入れてくださいっていう感じで。
memcachedは高速なので、データベースとかに比べて高速なので、キャッシュとかを使うには便利。
だけど、消えたら困るデータは絶対入れちゃダメっていうんですね。
そこがちょっとRedisとかと違うようなところですよね。
そうですね。Redisは映像化もできるんですが、memcachedは基本的に映像化できないですね。
で、それを再実装してみてどうでした?
すごい雑な振りなんですけど。
意外と実装できたんですよ。
意外とあっさりできたんで、ちょっと軽くこれから実装を紹介していきたいと思います。
03:05
まず、キャッシュデータケースとプロトコルがどんだけ単純かっていうところで、
まずセット、セットとゲットがあるわけですけど、
まずセットしたい場合は、セットって打ってスペースでキー名、
その後に、この後また話すんですけど、フラグスってやつがあって、
その後にいつエクスパイヤーするのかっていう時間を入れて、
その後に何バイトなのかっていう情報を入れて、で開業しますと。
その後に実際に入れたいデータを入れてまた開業するっていうだけなんですよ。
なるほど。文字列で見るとプロトコルなのであれなんですけど、
使い方のリファレンスなのかなって感じですね。
単純に。値を保存するという命令がそこに書かれてるんだろうなっていうような。
そうですね。すごく単純で本当に最低限っていうところですね。
ゲットはゲットしてキー名で開業するだけ。
で、バリューの値が返ってくるっていう感じになります。
開業が絶対入るんですね。
開業が必要ですね。
ちょっとそこが特殊というかなんでだろうっていう。
いや特殊ではないんですよ。HTTPだって開業必要じゃないですか。
そうなんですね。そこが分かってなかったです。
HTTPやHPヘッダーとか全部開業必要なんで。割と普通ですよ。
なるほど。
で、フラグスの説明をちょっと追加するんですけど、
フラグスは基本的にイントのデータです。
なんで整数が基本的に入ります。
で、何に使うかっていうと、
これはNemcacheリーガーは自由に使ってくださいみたいな感じなんですよ。
自由。
キーに決めてくださいって感じで、結構ライブラリーによっては、
0だったら、これ数字は適当なんだけど、0だったらイントが入ってるとか、
1だったら文字列が入っているとか、2だったらGGP圧縮されてるデータが入っているとか、
そんな感じで、ライブラリーが自由に使っている領域なんですよ。
バリューの型を示すこともできたり、他の使い方もできたりってことなんですね。
そうです。Nemcacheリーガーのクライアントライブラリーが自由に使っていい領域なんですよね。
フラグスっていうのは。
ここはNemcacheリーガー扱う上で若干罠になったりする部分ではあるんですけど、
今回はNemcacheリーガー再実装しただけなんで、そんなに関係はないんですけど、
そういう実装、誘い仕様がNemcacheリーガーにはありますね。
確かに再実装でNemcacheリーガーを再実装したうつろであれば、
フラグスは自由にやっていいよっていうことだから、主導詞すればいいってことですよ。
うつろ側は特に保存しているだけで、クライアント側がどう扱ってるかっていうのはうつろは感知していないので、
今回はあまり関係ないですね。
確かに。
という感じですね。本当にそれだけなんですよ。
なので、うつろって今回、棒で作ったんですけど、本当にスペースで区切られているだけだから、
06:05
位置を取ってきて、スペースで区切って、コマンドがセットなの、ゲットなのっていうのを見て、
実際の値見てみたいな、本当にそれしかやってないですよね。
パースも簡単ってことですね。
パースも簡単。再実装しても全然大したことない。
ちょっと気になるのが、フラグスの後のエクスパイヤーの時間ですね。
ここら辺の実装ってどうなってるのかなって、すごい単純に思うんですよね。
ここが若干めんどくさくて、ゼロなら期限なしなんですけど、
一定の時間以下だったら相対秒、例えば60とかって入れたら、今から60秒間使えるってなるんですよ。
なるほど。
けど、長かったらユニクスタイムスタンプで入れるんですよね。
例えば1ヶ月後とかだったら、1ヶ月後のユニクスタイムスタンプを入れないといけない。
なるほど。その秒数ではないってことですね。
秒数ではない。
難しくないですか?
そこが若干めんどくさくて、今回CodeXが全部やってくれたんで、自分は実装しないんだけど、
何か言い区分とかで、この場合はこう、この場合はこうってちょっとめんどくさいことやってました。
これはプロトコル的に明示的に何秒以上だったらユニクスタイムスタンプ扱いみたいな。
のがあるはずだけど、何秒か忘れました。うつろんソースコード見れば書いてあると思います。
すごいナイブな実装になってますね、そこ。
若干なんでここだけよくわかんないことになってるかわかんないんだけど、なぜかそうなってるんで。
再実装する場合は実装しないといけない。
どっちかに合わせてほしいですよね。
どっちかに合わせてほしいなって、この実装を見たときに思ったんですけど、何だこの言い区分と思ったんで。
面白ポイントですね、ここ。
若干面白ポイントなんですけど、CodeXがやってくれたんで、そうなんやっていう感じでしたね。
あとはその後のバイト数ですかね。
バリュー受け取ってるわけじゃないですか、その後に。
わざわざバイト数持ってくる人ってあんのかなというのが若干疑問なんだけど。
これ多分ね、MemcachedがC言語で実装されてるんですよ。
そうなると何バイトって確保しないといけないのかってC言語で実装って書いてるとめちゃくちゃ大事じゃないですか。
メモリを事前に確保したいってことですね。
そうです。で、実際HTTPとかもサイズ返すじゃないですか、コンテンツレンジです。
なのでそれと同じといえば同じですね。
なるほど。じゃあ事前に確保しておいてそこにぶち込むよっていうのを約束してるって感じですね。
そうですね。Goとかだったら別にどうでもいいっちゃどうでもいいんだけど、Cで実装してたらこれないと本当にしんどいので。
確かに。
すごく大事なところではありますね、ここは。
ちなみに再実装ではちゃんとバリューに指定されたものとこのバイト数の再検証みたいなことしてるんですか。
どうだったかな。それはCodeXくんがやってくれたので覚えてない。
09:00
すごい気になるところで、バリデーションして違反してたらどうなるんだろうっていうのが若干気になるところではありますけど、Memcachedのほうでもどうなってるのか若干気になります。
Memcachedは確保してるはずですね、Cで実装されてるから。使わないとかなりめんどくさいやつです。
あれか、もうすでに確保していてそこに超える容量でバリューが来てしまったら当然エラーですもんね。
エラーになりますね、それは。
だから大丈夫って話。そもそも入らないからエラーになるからバリューエーションする必要性もあんまないよねとかそういう話でもかもしれない。
Memcached本家がどういう実装してるかわからないけど、入れる必要はないですね。このバイト数が実際の値と違ったら保存する必要はない。
なるほど。で、次はGETのプロトコルですかね。GETはシンプル。
GETはもうGETするだけで返ってくる。で、フラグ図とかも全部返ってくるので、それを見てクライアント側で何かやるっていう感じですね。
で、マルチGETにも対応して。
マルチGETもできますね。GETキー複数は出せて。その場合は返ってきたり返ってこなかったりするんですよ。要はそのキーが存在しない場合は何も返ってこない。
単純にヌールというか何も返ってこない。
何も返ってこないですね、マルチGETの場合は。GETの場合は何だっけな、なんかノーデータ的なやつ返さないといけない。ノットファウンドだったかな。みたいなやつ返さないといけないっていうルールがあって、その辺も実装されてますね、うつろは。
なるほど。マルチGETの場合はノットファウンドが返らない。
ノットファウンド返らないですね。
単一だったら返るけどっていう。
そうですね。
この辺も別に普通のHTTP APIとかでもこんな感じじゃないですかっていう感じではあります。
そうですね、そうでしょうというような感じで。若干気になるのが内部実装、うつろの場合でいいんですけど、値の保存方法ですね。
もちろんファイルに書き出してるとかないと思うんですよ。
永続化してないんで、単純に配列で持ってたりするんですか。
これはこれから話さないといけないところで、Goのマップを使って保存をシンプルにしてるんですけど、Goのマップだけだとダメで、なんでかっていうと、エビクションっていう実装が必要で。
エビクション?
エビキャッシュDだと、エビキャッシュD以外のKBSとかにもエビキションっていう種類あるんですけど、メモリーが例えば1メガありますと、1メガ超えそうですってなって、超えましただとOMキラーが発動しちゃうじゃないですか。
なので超えちゃダメなんですよ、エビキャッシュD側は。
なので超えそうになったら、もう消さないといけないんですよ、データを。
で、そのデータがエクスパイヤード来てたら消せばいいんだけど、もう本当にカツカツだった場合、エクスパイヤード来てなくても、まだエクスパイヤードしちゃダメなデータでも消さないといけないんですよね。
12:09
これがエビクションズっていう、エクスパイヤーしてないけどデータ消されたっていうやつをエビクションズとかって言うんだけど、そういう実装しないといけないので、
そのマップに入れただけだと、もう満杯になったら死んじゃうんですよね。
なんか難しそうなんですけど、今聞いてた実装が。何を根拠にしていくんですかね。
うつろは結構シンプルな実装になっていて、まずマップも使いますと。
マップの中に入ってるのはポインターで、そのマップの値がポインターになっていて、そのポインターもポインターでまた別の管理していて、リンクドリストの実装をしています。
ここが若干めんどくさいんですけど、まずセットしたらどんどんリンクドリストに先頭にセットしていく。
リンクドリストなんで、プレブとネクストにお互いポインターがあるので、例えば挿入されたらルートのネクストが新しくセットされた値になると。
一番最初のデータですね。
最初のデータで、もともとルートがネクストのデータを新しく挿入されたネクストにする。
もともと先頭だったプレブを新しくセットされたやつにする。
という風にすればリンクドリストが綺麗にどんどん先頭に挿入されていくじゃないですか。
連結されていきますね。
一番後ろにあるやつが一番最後に見たやつだから、一番最後のリンクドリストの一番後ろから消していけばいい。
後ろから消していくんですね。
先頭にどんどん新しいやつ挿入されていくから、一番後ろにあるやつがいらないやつだからそこから消していく。
そういうことか。だからファーストイン、ファーストアウトということですよね。
先頭、一番古いやつから消していくってことですよね。
そんな感じでやっていて、ただこれゲットも変えないといけなくて、
要はめっちゃゲットしてる、めっちゃ参照されてるやつをいきなり消したら問題あるじゃないですか。
なるほど、そこまで考慮する。
消すやつってもうセットしてずっとゲットしてないやつなんですよね。消したやつって。
重要度が低そうだね。
重要度が低いやつ、これLRUとかって言うんですけど、最近最も使ってないやつを削除するっていう処理をする。
リリストリセントリユーズとか、最も最近使われてないみたいな意味だけど。
LRUの処理をしたいので、セットで一番先頭に入れますと。
ゲットが来たときも同じ処理をする。
15:01
先頭になっていくんですね。
先頭に持っていって、その前後のやつをまたリンクのリストなんでくっつけ合う。
やるとどんどん先頭に行く。
一番後ろのやつが最も使われてなくて、一番最後に最初に入れたものっていうことですね。
そうですね、最初に入れてから誰もゲットしてないやつになるので、後ろから見ていって、そいつをどんどん消していけばいいんですよ、Effectionsの処理は。
確かに、そういうデータの整理になっていれば、消すほうが楽ですね。
そうなんです。それにリンクドリストって、AIに言えばばーってすぐできちゃうから、実装自体は割とシンプルですよ。
手で書こうとするとめんどくさそうだなって。
手で書くと若干めんどくさいけど、別に一個一個の関数やってることは、このポインター切ってこのポインター入れるみたいな、そんな感じでやってるだけなんで。
シンプルですよね、やってることが。
やってることがシンプルで、なのでそのLRUのリストと、あとKBSのマップ作っておいて、アクセスするときはKBSのマッププラスLRUの更新。
リンクドリストの更新で、Effectionsの処理はそのLRUのリンクドリストの一番後ろのやつをどんどん消していく。
なんかここまで聞いてて、単純にマップの中からキーを拾ってきてバリュー返すだけでしょうと思ってたんですけど、意外とやってること多いですね。
そう、Memcachedはやっぱりやらないといけないんですよ。そうじゃないとどんどんメモリーが膨らんで勝手に死んじゃうので。
いい感じにメモリーの容量を確保するためにそういう工夫をしているってことですよね。
サーバーのメモリーをうまく活用するためにって感じですね。
もう一個、例えばゲットしたときにもうすでにエクスパイヤードしてましたってなったら、その要素消せるんですけど、ずっとゲットが来ない可能性もあるじゃないですか。
ずっと残り続けちゃいますよね。
ずっと残り続けちゃうから、こういうリンクドリストの実装っていうのは必要なんですよ、絶対に。
そのときどうするんですか。単純にゲットが来なかったときに。
リンクドリストで一番後ろに来るから、エフェクションズの処理が走ったときにどんどん消えていく。
そこに入れとくってことなんですね。
一番後ろにいるから。
他のゲットが走ったときに削除が走るようにするってことなんですね。
エフェクションズの処理をどういうときに実行するかっていう問題ではあるんだけど。
確かに、エフェクションズの処理。
そのときに実行する必要はないんだよね。
ゲットのときは、その要素がエクスパイヤーとしていたら、その要素を削除してノットファウンドを返すっていう処理になる。
そのエフェクションズの処理をいつやってるんですかね。
ゲットとかセットとかがないときに行ってもいいってことですよね。
そうですね。この処理はエフェクションズの処理がいつ実行されてるかはちょっと覚えてないんですが、いつでもいいんですよね。
18:01
例えば、Goだったら簡単に書けるんで、何秒ごととかでもいいし、
あと、使ってるメモリーの容量が一定を超えたら実行するでもいいし。
なるほど。ガーベージコレクション的な感じで。
そう、これ完全にやってることがガーベージコレクションと一緒なんですよ。
それ実装してて思ったんですよね。あれこれ、結局問題になるのGCじゃんっていう。
そんな気がしますね。
このところ自体は簡単だし、あとGoのマップ使えば簡単にできるじゃんと思ったけど、
結局GCとの戦いになるんだっていうことをうつろを作ってすごい痛感したんですよね。
ちなみに、Memcachedとかにインクルメントとかレクルメントとかあるかなと思うんですけど、うつろもサポートしてるんですかね。
そうなんですよ。うつろも実装してるんですけど、ここ実はわざとMemcached互換じゃなくなっていて、
ここは自分が昔ハマったことあるからっていうところなんだけど、まずRedisの話を先にしちゃうんだけど、
Redisはキーが存在していないキーに対してインクルメントを実行すると、そのキーに対して1が入るんですよ。
存在しないものに対してインクルメント。
存在しない場合は0だと、0が入ってるって仮定して1が返ってくるようになってるんですね、Redisは。
そうですね、なんか素直な感じですね。
Memcachedは残念ながらこれができなくて、存在しないキーに対してインクルメントすると、存在してませんよっていうエラーになっちゃうんですよね。
なるほど、先にキーを確保しとけよっていうことなんですね。
やりたければ最初に0をセットしてからインクルメントしないとMemcachedはインクルメントできない。
なんか素直じゃないですね。
これ何が困るかっていうと、アトミックに処理できないんですよね。
例えば同時にリクエストが来て、インクルメントしないといけないリクエストが2つ同時に実行されたときに、1回0セットしてからインクルメントってやると、タイミングによっては本当は2にしないといけないのに1になっちゃう可能性がある。
ありますね。
だからこれって良くないんですよ。アトミックに処理できないから、レディスみたいな存在しないキーに対してインクルメントしても1以下返ってくるって仕様じゃないとアトミックな処理ができないんですよ。
自分はこれでハマったことがあるので、自分で自作するんだったらMemcachedの実装に合わせずに、レディスみたいに存在しないキーに対してインクルメントできるようにしたいなと思って、わざとMemcached互換じゃなくしてます。
これなんでMemcachedでそんなセットを先にゼロで入れないといけないんですかね。
分かんないですけど、正直自分はこれ結構困ったので、なんでこうなんだろうとは思ってるんですけど。
直してほしいですよね。
直してほしいなと思ってるんですけど、これ結構人によるというか、結構キャッシュ系のライブラリとか仕組みっていくつかあるんですけど、これ結構バラバラなんですよね。
21:07
このインクルメントの仕組みというか。
存在しないキーに対するインクルメントをどう処理するかって結構実装によってバラバラですね。
なるほど。Memcachedだったら絶対1回はセットしないといけないっていう。
存在しないキーに対してインクルメントできないので、セットしないといけない。
ここだけはちょっとレディス側に寄りたいですね。
そう思ったんで、うつろはわざと非互換にしてます。
別にこれで困ることあるかっていうとない。既存のMemcached使ってる実装はやってないはずだから、別にこれでバグるってことは基本ないはず。
なるほど。
っていう感じでやっていて、これでMemcached互換として動くじゃんってなったんですよ。
ちなみにDecrementのほうはどうなんですかね。
Decrementも一緒です。
なかったらNot Foundっていう。
Not Foundと一緒ですね。
うつろではどうしてるんですか。
うつろは存在しないキーに対してインクルメントもDecrementもできます。
Decrementするとゼロになるって感じなんですか。
マイナス1ですね。
マイナスになるんですね。ちゃんと。なるほど。あんまマイナス扱うことないですよね。
あんまないけど、別にあっても困らないんで。
なるほど。ちょっとインクルメントと巻きほどを揃えたっていう感じですね。
そうですね。
っていう感じですね。なので、これで動いたじゃんと思って。
自分が管理してるMemcached使ってるレポあるんで、実際に実装切り替えて動かしたんですけど、
動いたんですよ、普通に。
動く。
普通に動くじゃんってなったんですよ。
いいじゃないですか。
いいじゃんってなったんですけど、じゃあこれを実際のMemcachedに置き換えられるかっていうと、
自分は置き換えたくないって思ったんですよね。
なんで。
っていうのがさっきの、さっき話した話で、リンクドリストの話なんですけど、
これポインター外してるじゃないですか。
これポインターを外して打つみたいな処理をするないといけないんで、
これって平行に動かせないんですよ。
なるほど。
ゼコ5を使ってるのに、プログラミングで動かせなくて。
ロックしちゃうんですね。
ロックしちゃうし、しかもこれゲットしたときもLRUの関係で、
リンクドリストを先頭に持っていかないといけないから、
ゲットでも書き込みが走るんですよ。
ロックしちゃいますね、ゲットでも。
ゲットでも書き込みのロック、完全なロックしないといけない。
そうすると、ゲットセットと両方ともで完全にロックしないといけないから、
これ1コア以上使えないんですよね、この実装って。
あとちょっと今思ったんですけど、マルチゲットの挙動って、
もしかして先頭から1個ずつ処理していくみたいな感じになっちゃう?
グチョグチョな処理ですよ。
24:00
ですよね。
ロックして全部処理して、処理が終わったらアンロックする。
そこでも平行で全ての値を取りにいくみたいなことは絶対できないってことですね。
そうですね、その間にやることはできないですね。
なので、実装できたできたと思って実装読んでて、
あれこれ1コアしか使えないじゃんってことに気づいたんですよね。
パフォーマンスが出ないというか、リソース。
パフォーマンスが使えない。
自分はMemcachedの大規模の運用ってやったことあるんですけど、
MemcachedってCPU結構積んだマシンで動かしても、
ちゃんと複数のCPU使えるんですよね。
賢いね。
だから絶対にうつろみたいな実装にはなってないはずなんですよ。
本家のMemcachedって。
それで、本家のMemcachedって実装どうなってんの?ってなったんですよね。
うつろ作って。
すごくね?ってなりますね。
できたできた動いたじゃんって言って余裕じゃんって思って、
実装見たらあれこれじゃ全然パフォーマンス出ねえじゃんってことに気づいちゃったっていう。
このアーキテクチャだと不可能だってことですよね。
そうCPU1コア以上使うのが不可能だってことに気づいてしまったんですよ。
なるほどちょっとなんか、
机上ではいい感じだったけど、
実際にやってみると、
エコで動かせねえっていうような。
当たり前の問題にぶち当たったってことですね。
実際に自分で実装してみて、すごく当たり前なんです。
実装しちゃうと絶対にこれ1コア以上動かないって自明なんだけど、
やっぱりそんなことあんま考えたことなかったから、
あれこれMemcachedと同じけどなんないじゃんっていうことに気づいちゃったんですよ。
確かに言われて気づきました。
で、Memcachedの方の実装は読んだんですかね?
ちょっとMemcachedのブログとかあったんで、
Memcachedがどうやってんのかっていうのを軽く調べてみました。
どうでした?
これはJVMのGCみたいだなって正直思ったんですけど、
世代で分けるんですよ。
世代で分ける。
まず最初にセットしたら、基本的にはHotに入るんですよね。
で、Hotに入って、
で、GETされたら、
一回GETされたって情報は残すんだけど、
さっきのLinkedListみたいなことはやんない。
それがやっちゃうと一回ロックしないといけないから。
書き込み処理はしないってことですよね。
そうですね。
一回GET来たらインクリメントするんだけど、
それだけしかやらない。
で、なんでインクリメントするかっていうと、
それが、もしGETがセットされた後にGETが来てるんだったら
WARMっていうのに行くんですよね。
で、もし一回も見られてなかったらCOLDっていうのに行くと。
グループ分けをするってことですね、データの。
そうですね。よく参照されるやつとあまり参照されないやつっていうのをグルーピングする。
27:02
分かりやすい。
基本的にはこのCOLDの方がエビクションの処理が走ったときに削除される候補になる。
はい。
ただ、COLDもよく参照されてるねってなったらWARMに上げていくみたいな処理が非同期で動いてたりとかする。
けど、リチギに全部やらない?
うつろは全部一個一個GETが来たらリチギに先頭に持ってっていうのを愚直にやってたんですけど、
そんな真面目にやらない?
それ真面目にやると絶対ロックになっちゃうから。
そこはそんなに真面目にやらないっていうところ。
で、WARMに入ったやつもあんまり使えてないなってなったらCOLDに移すっていう処理がある。
これもそんなに真面目にやらない。
真面目にやりすぎるとやっぱり心理的ストロックが絶対いるから真面目にやらないっていう。
で、あとTEMPってやつもあって、メキシャ処理ってすごいTTLが短いやつとかも入れられるんですよ。
例えば10秒とか。
10秒とか60秒とかすごい短いやつも入るから、そういうのに対して真面目に処理するの。
だってそれ処理しちゃうとパフォーマンス落ちちゃうから、逆に真面目にやりたくないからTEMPってやつに入れて、
すぐにエクスパイヤーするからどんどん削除していくっていう。
なるほど。
ちゃんとグループ分けをざっくりするっていう。
ざっくりグループ分けして、けど真面目にやりすぎない。
若干気になってるんですけど、さっきから気になってるんですけど、真面目にやりすぎない度合いどうなんだろうなっていうの。
その件の度合いは結構コードをちゃんと読まないとわからないところではあるんですけど、
そこを理直にやりすぎないっていうのをうまくやることで、うまいことCPU複数使えるようにしてるらしいです。
そのデータの整理を理路整然としないってことですよね。
そう、ちゃんとやらないっていう。
全てが整理整頓されてる状態にはせずに、ある程度のグループ分けして、しかもエクスパイヤーが短いものに関してはTEMPっていうグルーピングにすることで、
TEMPに入ったやつは適当に消していいよみたいな感じになる。
どうせ時間が来るから管理しないよねっていうふりにしてることなんですね。
それ聞いてからだとさっきのセットのところでエクスパイヤーがUNIXタイムスタンプなのか相対秒なのかあったじゃないですか。
そこの命令の仕方みたいなところも若干わかってくるなっていう気がするんですけど。
相対秒だったらもうすぐ死ぬでしょっていう。
UNIXタイムだったらしばらく死なないでしょっていう判定をしてるんだと思うんですよ。
そうですよね。そんな気がしますよね、ここ絶対に。
30:03
そう。なので結構思ったよりよく考えられていて、ちょっと軽い気持ちで再実装したのを本当にすいませんでしたみたいな感じに途中からなったんですけど。
でも確かに話を聞く前だったらなんでこんなことしてるんだろうなっていうところが確かに理屈ありそうっていうような感じですね。
そうですね。やっぱりこの辺も再実装して初めてちゃんと気づいたところなんですよね。
意識しないですよね。
意識しないし、ちゃんとCPU分散するからMemcachedって、Memcachedパフォーマンス出せていいよねぐらいにしか思ってなかったんで。
すごい作りになってるんだろうなという気が若干。
そうですね。だからLRUも本当にリーストじゃない可能性あるんですよね。
真面目にちゃんと管理してないから、あんまり多分こいつ使ってないだろうみたいな感じでやる。
けどそれで全然いいんですよねMemcachedって。
消えてもいいデータを残すっていう大前提があるからってことですよね。
そうですね。そのCacheでしか使わないから、そんぐらいの雑さでいいし、それでうまく動かないんだったらMemcachedの使い方として間違ってるから、これぐらいでいいんですよ。
アプリケーション側の構成というか使い方を改めてくれっていうことですね。
そうなんですよ。だからMemcachedの設計としては完全に正解で、これでいいんですよMemcachedは。
だってCacheヒットしなかったらもう一回セットしろよって話ですもんね。
そうですね。
アプリケーション側からしたら。
アプリケーション側でちゃんとやってくださいっていう。
かなり割り切ってるんだけども、割り切りが平行的にCPUちゃんと使えるとかそういったところに生きてきてるんですね。
そうなんですよね。
いい話だな。最終いい話だなと思ってきたんですけど。
軽い気持ちで再実装して、あれこれMemcachedと同じ動きにならないってなってMemcached見てMemcachedすげえってなったっていう話です。今回の話は。
いい話だなと思って。別にやらなかったら絶対知らないし気づけないですね。
そうです。自分も本当にやらなかったら絶対気づかなかったですね。
勉強になるなという感じですね。本当に。
こういうことがあるからこそ既存のツールだったとしても一回勉強がてら機械で再実装してみるっていうのは結構有用そうなんですかね。
有用だと思いますね。ツールにももちろんよると思うんですけど、自分はすごいMemcachedやって勉強になったので、軽い気持ちでやってみてもいいんじゃないかなって思いましたね。
いいですね。あとはプロトコルの主要だったりとかも結局調べることになるかなと思うのでね。再実装の過程で。
33:03
そうですね。その辺も勉強になると思います。
単純にあれですよね。次Memcachedを使うときにMemcachedの気持ちが分かりますよね。
そう。すごく気持ちが分かるようになりました。
温かい気持ちでMemcachedを使うことになるので、いいなと思います。
Memcachedへのリスペクトがめちゃくちゃ増しましたね。これをやって。
いや、素晴らしい。これは素晴らしい。いい感じですね。
じゃあちょっといい感じの時間になってきたので最後にMemcachedの再実装だったり移ろうだったりとか、どこでもいいんですけど何か一言あれば。
そうですね。Memcachedやっぱり大規模なサービスだったら今でもガンガン使われてる仕組みだし、
本当にすごいたくさんCPRサーバーで動かしてもちゃんと動いてくれるんで、めっちゃ優秀だなって思っているし、
再実装した結果そんなパフォーマンスが出せなかったので自分の実装は。
本当にこの辺って実際にやってみないと分かんないことだし、みんなもAI使って再実装とかやったらいいと思います。
はい、みなさんもやりましょう。
ということで今回はここまでにしたいなと思います。
ヤウヨロズのOSSでは毎回一つのOSSを取り上げて、それについて片付いたヘンペコで技術的なところも深掘りしながら話をしていく番組になっています。
今後もその名の通りヤウヨロズのOSSを取り上げていきたいなと思っていますので、ぜひ次のプラットフォームでフォローと高評価のほうをお願いします。
またこのOSSについても取り上げてほしいなどありましたら、コメントとかで教えていただけると嬉しいです。よろしくお願いします。
xnazubu.xsignをハッシュタグヤウヨロズのOSS漢字でヤウヨロズカッテグマのほうをつけてください。
感想をお待ちしておりますのでよろしくお願いします。
それでは今回もありがとうございました。
ありがとうございました。
35:13

コメント

スクロール