ΠΘVΘΠΙΠΞ

ΠΘVΘΠΙΠΞ

Building a new internet together.

キャッシング: 目的と利点の理解

最近、ウェブアプリケーションのスケーリングに取り組む機会がありました。これはかなり伝統的なアーキテクチャを持っています:CDN が高速なネイティブコードのウェブサーバー(例えば、apache2 や nginx)の前にあり、その前に遅い解釈型言語(例えば、Python や Ruby)で書かれたアプリコードがあり、データベース(例えば、Postgres や MySQL)と通信し、さらにインメモリの KV ストア(例えば、redis や memcached)をキャッシュとして使用しています。想像できるように、このアプリケーションをスケーリングするには「もっとキャッシングを追加する」ことが多く関わってきます。

以前にも話したように、私はパフォーマンスの問題をハードウェアの利用に関して考えるのが好きです — 特定の作業単位を達成するためにどのハードウェアリソースを消費しているのか?したがって、これは私の頭の中で浮かんでいたモデルを書く良い機会のように思えます:例えば、memcachedにキャッシュをする際、実際には基盤となる物理リソースの使用に関して何をしているのでしょうか?

キャッシュポイントルックアップ#

私がこの分類について考え始めた理由の一つは、次の質問に答えるためでした:なぜプライマリキーによるデータベースのポイントルックアップをキャッシュするのか?データベースはすでに最近アクセスされたデータのための独自のインメモリキャッシュを持っているので、なぜ彼らの前に_追加の_キャッシュを置くのでしょうか?それにもかかわらず、非常に非科学的な Twitter の投票は、プライマリキーのルックアップであっても、memcache や類似の技術をデータベースの前に置くことがかなり一般的な慣行であることを確認しています。以下の分類を進める中で、キャッシングの一般的な視点と目的について触れ、プライマリキーによるルックアップのキャッシングが私たちに何を示すのかについても触れます。

キャッシュの機能#

私は、伝統的なウェブアプリケーションアーキテクチャにおいてキャッシュサーバーが果たす可能性のある 3 つの異なる「基本的」目的を特定しました。これらは「純粋な」視点であり、キャッシュの展開はこれらすべての組み合わせを含みます。しかし、私はそれらを少し別々に考えるのが役立つと感じており、キャッシングをあるエンドポイントに追加しようとする際にどれを期待しているのかを把握するのが重要です。

メモリと CPU のトレードオフ#

これはおそらく、アプリケーションキャッシングの目的についての最も古典的な概念です。高価な操作があり、結果が必要なたびにそれを行うのではなく、頻度を減らし、結果をキャッシュに保存します。最終的な結果は、システムがより少ない CPU を使用しますが、キャッシュされた結果を保存するためにより多くのメモリを使用します。このパターンの古典的な例は、全ページキャッシングのようなもので、レンダリングされたページ全体をどこかにキャッシュします(アプリフレームワーク内やフロントエンドのウェブサーバーや CDN 内など)。複雑なデータベースクエリの出力をキャッシュして、単一のポイントルックアップで置き換えることができるのも別の例です。

このトレードオフは、キャッシングしている計算が高価で、出力が比較的小さい(必要なメモリを減らすため)場合に最も意味があります。ウェブアプリケーションはしばしば Python や Ruby のような比較的遅い言語で書かれているため、このトレードオフが価値がある可能性が高くなります。しかし、一般的には、CPU に制約があり、メモリに制約がない場合には、適切なトレードオフとなるかもしれません。

このトレードオフを実現するために、別のキャッシュサーバーは必要ありません。中間計算の結果をデータベース自体に保存すれば、同様のトレードオフを実現できます。この戦略のバージョンは一般的に「マテリアライゼーション」と呼ばれます。

キャッシングのこの見方は、プライマリキーのルックアップをキャッシュする理由を理解する上で最も情報が少ないかもしれません。キャッシュはデータベースとほぼ同じデータを保存するため、何の計算を節約しているのかは明らかではありません。それでも、インメモリキャッシュはポイントルックアップを提供するために一般的なデータベースよりも少ない CPU を消費する可能性が高いことに注意します。ほとんどの場合、データベースはクエリを解析し、テーブルメタデータを検査し、正しいインデックスを見つけ、B-Tree を歩く必要があります(関連するページがすべてメモリ内にあっても)、一方で memcached のようなツールははるかに単純なパーサータスクを持ち、基本的には単一のハッシュテーブルルックアップを行います。したがって、ポイントルックアップのキャッシングに関する一つの見方は、キャッシュサーバーのメモリとデータベースサーバーの CPU 使用量をトレードオフしているということです。

もちろん、キャッシュノードは通常データベースとは_異なる_ハードウェアであることも関連しています。これがインメモリキャッシングサービスの第二の特徴につながります:

さらなるメモリまたは CPU の追加#

従来のデータベースアーキテクチャはかなりモノリシックです:MySQL や PostgreSQL を実行している 1 台のマシンがすべての読み取りおよび書き込みトラフィックを処理します。これにより、サーバーに追加できる CPU と RAM の量に制限が生じます(実用的な理由から、私たちはしばしばお金で買える最大のインスタンスを使用できないか、使用したくありません)。シャーディングされたアーキテクチャでも、シャードを追加することはしばしば時間がかかるか重い操作です。したがって、実際にはデータベース内でお金を CPU やメモリに変える能力に制限があります。

しかし、もし私たちがデータベースから作業を移動し、キャッシュに入れることができれば、システム内で_より多くの CPU 使用量やメモリにアクセスできる_ことになります。この方法でリソースを追加することは、一般的には「単一のリクエストを処理するコスト」として測定される効率を向上させるわけではないことに注意が必要です。実際、一般的には、システム内の異なるノード間でデータを移動するオーバーヘッドが追加されるため、効率が低下する傾向があります。前述のように、CPU とメモリをトレードオフしている場合、リクエストごとの総コストを実際に削減できるかもしれません。リソースを追加するだけであれば、スループットのためにお金をトレードオフできるようになりますが、通常、個々のリクエストを安くすることはありません。

キャッシュは、運用上、データベースよりもスケールしやすい傾向があります。単一のキャッシュキーのルックアップによるシャーディングは概念的に非常に簡単であり、複数のキャッシュノードを追加できます。このようにして、キャッシュアーキテクチャは、データベースサーバーが提供できる以上にキャッシングのための利用可能なメモリとリクエスト処理のための CPU を何倍にも増やす可能性があります。

ここで、キャッシュノードとデータベースの読み取りレプリカを比較することもできます。これはデータベースクラスターをスケールするための別の一般的な戦略です。読み取りレプリカもシステムに追加の CPU と RAM を追加しますが、各個別のクエリのパフォーマンス特性を大きく変えることはありません。ただし、各レプリカは通常、データセット全体を複製する必要があるため、書き込みトラフィックは_すべての_レプリカで一定量の CPU と I/O 帯域幅を消費し、スケールアウトの効果を減少させます。また、各レプリカがキャッシュに保持するキー空間の部分をシャーディングすることは難しいか不可能であり、追加の RAM をシャーディングされたキャッシングアーキテクチャと同じように効率的に使用することが難しくなります。

この視点は、ポイントルックアップのキャッシングの価値を非常によく説明しています!キャッシュとデータベースが個々のルックアップを提供する上で同じ効率であったとしても、キャッシュノードを追加することで、データベースクラスターをスケールするよりもはるかに簡単にメモリと CPU を水平にスケールでき、メモリ内に保持されるレコードの総数と利用可能な総 QPS を増加させることができます。

より効率的なメモリ使用#

この最後のポイントは、ある意味で最もドメイン特有ですが、私にとって最も興味深いものの一つでもあります。なぜなら、最も驚くべきことだったからです。

ほとんどのデータベースは、「ページ」のレベルでキャッシングを実装しています。ページは一般的に 4k〜16k の範囲です。ページはディスク上のストレージとアクセスの基本単位であり、データベースキャッシュは I/O レイヤーの上にあり、ページ全体をキャッシュします。

このアーキテクチャの意味は、たとえ単一のレコードだけを望んでいても、少なくとも完全なページのデータをキャッシュしなければならないということです。頻繁にアクセスされるレコードの「作業セット」がデータベース全体のコレクションよりもはるかに小さい場合、これはひどいメモリ効率につながる可能性があります!100 バイトの行を持つテーブルを想像すると、最悪の場合、_単一の行をキャッシュするために_フルの 8k のデータをメモリに引き込むことになり、作業セットをキャッシュするために必要な RAM が 80 倍に膨れ上がります!一般的に、私たちのレコードはその制限に達するほどスパースである可能性は低いですが、オーバーヘッドは依然としてかなり重要です。

対照的に、Redis や Memcache のようなインメモリストアは、個々のオブジェクトを正しくキャッシュします。100 バイトのレコードをキャッシュする際には、ハッシュテーブルやアロケータのオーバーヘッドなどのためにいくらかのオーバーヘッドが避けられませんが、おそらく最大でも 2 倍程度でしょう!したがって、同じ量のメモリが与えられた場合、オブジェクトベースのキャッシュはデータベースのページキャッシュよりも_はるかに多くの_オブジェクトをキャッシュできることが多く、データベースのキャッシュに依存するよりもキャッシュを使用する利点の 3 つ目を提供します。

この結果は、私が最初にそれを理解したときに非常に驚きました!この文書で概説されたキャッシュの最初の 2 つの使用法は私にとって非常に直感的でしたが、専用のキャッシュがデータベースのキャッシュに対してこのような劇的な効率向上を持つとは思ってもみませんでした。この理由は、少なくともいくつかの運用のレジームにおいて、専用のキャッシュサーバーでプライマリキーのルックアップをキャッシュするための非常に説得力のあるケースだと思います:あなたはデータベース自体ができるよりも、MB の RAM あたり数倍のレコードをキャッシュできるかもしれません!

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。