ΠΘ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 倍程度でしょう!したがって、同じ量のメモリがあっても、オブジェクトベースのキャッシュは、データベースのページキャッシュよりも_はるかに多くの_オブジェクトをキャッシュできることが多く、データベースのキャッシュに依存するよりもキャッシュを使用することの第三の利点を提供します。

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

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