キャッシュを同時に再構築する問題

「Web、Caching、Memcached」に関する一連の投稿が続きます。 ここから始めます: 1、2、および3

これらの投稿では、memcached、そのアーキテクチャ、可能なアプリケーション、キャッシュキーの選択、クラスタリング、アトミック操作、およびmemcachedでのカウンターの実装について説明しました。



今日は、キャッシュの同時再構築の問題を検討します。これは、リセットまたは失われたばかりのキャッシュへの同時アクセスが多数ある場合に発生し、データベースのオーバーロードにつながる可能性があります。



次の投稿はキャッシュのタグ付けについてです。



キャッシュを同時に再構築する



この問題は、主に負荷の高いプロジェクトで一般的です。 次の状況を考慮してください:多くのページまたは特に人気のあるページ(たとえば、メインページ)で使用されるデータベースからの選択があります。 このサンプルは、いくつかの「有効期限」でキャッシュされています。 キャッシュは一定時間後にリセットされます。 同時に、選択自体は比較的複雑で、その計算によりバックエンド(DB)が著しくロードされます。 ある時点で、memcachedのキーが削除されます。 そのライフは期限切れになります(ライフタイムはキャッシュによって設定されます)、この時点でいくつかのフロントエンド(サンプリングが頻繁に使用されるため、いくつか)はこのキーによってmemcachedに変わり、その不在を検出し、 DB つまり、いくつかの同一のクエリが同時にデータベースに到達し、それぞれがデータベースを著しくロードします。しきい値を超えると、リクエストは妥当な時間内に完了せず、さらに多くのフロントエンドがキャッシュに戻り、不在を検出し、さらに多くのクエリをデータベースに送信します、データベースでは対処できません。 その結果、データベースサーバーは重大な負荷を受け、「レイダウン」しました。 この状況はドッグパイル効果と呼ばれます(例: korchasa.blogspot.com/2008/04/dog-pile.htmlも参照)。 何をすべきか、そのような状況を避ける方法は?



キャッシュの再構築に関する問題は、2つの要因がある場合にのみ問題になります。単位時間あたりのキャッシュアクセスが多いことと、要求が複雑なことです。 さらに、ある要因が別の要因を補うことができます。比較的人気がありませんが、非常に複雑で長いサンプル(実際には存在しないはずです)では、同様の状況に陥ります。 それではどうしますか?



次のスキームを提案できます:memcachedのキャッシュでキーの有効期間を制限しなくなりました-他のキーに置き換えられるまで存在します。 しかし、キャッシュデータと一緒に、その寿命のリアルタイムを記録します。たとえば、



{  : 2008-11-03 11:53,  : { ... } }
      
      





これで、memcachedからキーを受信するときに、「expire before」フィールドを使用してキャッシュの有効期限が切れているかどうかを確認できます。 ライフタイムが期限切れになった場合、キャッシュを再構築する必要がありますが、ロックを使用してそれを行います(次のセクションでロックについて説明します)、ブロックできない場合は、再度待機することができます(ロックが既に存在する場合、誰かがキャッシュを再構築しています)、古いキャッシュ値を返します。 ブロックに成功すると、自分でキャッシュを構築しますが、他のフロントエンドはロックを確認するため、同じキャッシュを再構築しません。 有効期限を指定せずにmemcachedに保存する主な利点は、キャッシュが既に誰かによって再構築されている場合に古いキャッシュ値を取得できることです。 正確に行うべきこと-他の誰かがキャッシュを構築してmemcachedから新しい値を取得するまで待機するか、古い値を返す-値が受け入れられる期間と待機状態で費やすことができる時間に依存します。 ほとんどの場合、ロックを解除するためのチェックで2〜3秒待機することができ、キャッシュが構築されていない場合(サンプルが2〜3秒以上かかることが判明した場合)、古い値を返し、フロントエンドを他のタスクに解放します。



そのようなアルゴリズムの例



  1. キャッシュキャッシュにアクセスできますが、その寿命は切れています。
  2. ユーザーcache_lockキーを使用してブロックしようとしています。
    • ロックの取得に失敗しました:
      • ロックが解除されるのを待っています。
      • 待たなかった:古いキャッシュデータを返す。
      • 彼らは待っていました:キー値を再度選択し、新しいデータ(別のプロセスによって構築されたキャッシュ)を返します。
    • 私はロックを取得することができました:
      • 自分でキャッシュを作成します。


このようなスキームにより、実際には要求を一度だけ完了するだけで十分な場合に、同じ「重い」要求でバッ​​クエンドを「埋め戻す」状況を排除または最小限に抑えることができます。 最後の質問は残っていますが、正しいロックを確保する方法は? 明らかに、同時再構築の問題は異なるフロントエンドで発生するため、ロックはすべてのフロントエンドで公開されている場所、つまりmemcachedにある必要があります。









memcachedのロック



memcachedを使用してロックを実装するための2つのオプション(ミューテックス、バイナリセマフォ)を検討します。 最初のものは正しくありません。並列プロセスを正しく除外することはできませんが、それは明らかです。 2番目は完全に正しいですが、それほど明白ではありません。



'lock'



キーでロックしたいとします。get操作を使用してキー値を取得しようとしています。 キーが見つからない場合、ロックはありません。 set



操作を使用して、このキーset



値をたとえば1に設定し、ライフタイムを最大ロックライフタイムを超える短い時間間隔、たとえば10秒に設定します。 これで、フロントエンドがクラッシュしてロックを解除しない場合、10秒後に自動的に破棄されます。 したがって、 set



を使用しset



ロックをset



、必要なすべてのアクションを実行した後、 del



コマンドで対応するキーを削除するだけでロックを削除します。 最初のget



操作でキー値を取得した場合、これは別のプロセスによってロックがすでに設定されていることを意味し、ロック操作は失敗しました。



説明されている方法には、競合状態があるという欠点があります。 2つのプロセスが同時にget



、どちらもキーがないという答えを取得でき、両方がset



、両方がロックを正常に設定したと想定します。 キャッシュの同時再構築などの状況では、これは許容される場合があります。 ここでの目標は、他のすべてのプロセスを除外することではなく、データベースに対する同時クエリの数を大幅に減らすことです。これにより、この単純で誤ったオプションが提供されます。



2番目のオプションは正しく、1番目のオプションよりも簡単です。 ロックを取得するには、1つのコマンドを実行するだけです: add



、キー名とライフタイムを指定します(最初のバージョンと同じくらい短い)。 add



コマンドは、memcachedにキーがまだない場合にのみ成功します。つまり、ロックをキャプチャすることができたプロセスは、このプロセスだけです。 次に、必要なアクションを実行し、 del



コマンドでロックを解除する必要があります。 add



が「そのようなキーはすでに存在します」というエラーadd



返す場合、ロックは他のプロセスによって以前にキャプチャされています。



All Articles