Pythonアプリケーションを作成するときは、キャッシュが重要です。キャッシュを使用してデータの再計算や低速のデータベースへのアクセスを回避すると、パフォーマンスが大幅に向上します。
Pythonは、単純な辞書からfunctools.lru_cache
などのより完全なデータ構造まで、キャッシュの組み込みの可能性を提供します。 。後者は、キャッシュサイズを制限するために、最近使用されていないアルゴリズムを使用して任意のアイテムをキャッシュできます。
ただし、これらのデータ構造は、定義上、ローカルです。 Pythonプロセスに。アプリケーションの複数のコピーが大規模なプラットフォームで実行される場合、メモリ内のデータ構造を使用すると、キャッシュされたコンテンツを共有できなくなります。これは、大規模な分散アプリケーションでは問題になる可能性があります。
したがって、システムがネットワーク全体に分散されている場合は、ネットワーク全体に分散されたキャッシュも必要です。現在、キャッシュ機能を提供するネットワークサーバーはたくさんあります。DjangoでのキャッシュにRedisを使用する方法についてはすでに説明しました。
このチュートリアルで説明するように、memcachedは分散キャッシュのもう1つの優れたオプションです。基本的なmemcachedの使用法を簡単に紹介した後、「キャッシュと設定」やフォールバックキャッシュを使用してコールドキャッシュのパフォーマンスの問題を回避するなどの高度なパターンについて学習します。
memcachedのインストール
Memcached 多くのプラットフォームで利用可能です:
- Linuxを実行している場合 、
apt-get install memcached
を使用してインストールできます またはyum install memcached
。これにより、ビルド済みのパッケージからmemcachedがインストールされますが、ここで説明するように、ソースからmemcachedをビルドすることもできます。 - macOSの場合 、Homebrewを使用するのが最も簡単なオプションです。
brew install memcached
を実行するだけです Homebrewパッケージマネージャーをインストールした後。 - Windows 、memcachedを自分でコンパイルするか、コンパイル済みのバイナリを見つける必要があります。
インストールしたら、 memcached memcached
を呼び出すだけで起動できます コマンド:
$ memcached
Python-landからmemcachedを操作する前に、memcachedクライアントをインストールする必要があります。 図書館。これを行う方法は、いくつかの基本的なキャッシュアクセス操作とともに次のセクションで説明します。
Pythonを使用したキャッシュ値の保存と取得
memcachedを使用したことがない場合 、それはかなり理解しやすいです。それは基本的に巨大なネットワーク利用可能な辞書を提供します。このディクショナリには、主に従来のPythonディクショナリとは異なるいくつかのプロパティがあります。
- キーと値はバイトである必要があります
- キーと値は有効期限が切れると自動的に削除されます
したがって、 memcachedとやり取りするための2つの基本的な操作 set
およびget
。ご想像のとおり、これらはそれぞれ、キーに値を割り当てるため、またはキーから値を取得するために使用されます。
memcachedとやり取りするための私の好みのPythonライブラリ pymemcache
です —使用することをお勧めします。 pipを使用して簡単にインストールできます:
$ pip install pymemcache
次のコードは、 memcachedに接続する方法を示しています Pythonアプリケーションでネットワーク分散キャッシュとして使用します:
>>> from pymemcache.client import base
# Don't forget to run `memcached' before running this next line:
>>> client = base.Client(('localhost', 11211))
# Once the client is instantiated, you can access the cache:
>>> client.set('some_key', 'some value')
# Retrieve previously set data again:
>>> client.get('some_key')
'some value'
memcached ネットワークプロトコルは非常にシンプルで、実装が非常に高速であるため、正規のデータソースからの取得や再計算に時間がかかるデータを保存すると便利です。
この例では、非常に簡単ですが、ネットワーク全体にキー/値タプルを格納し、アプリケーションの複数の分散された実行中のコピーを介してそれらにアクセスできます。これは単純ですが、強力です。そして、それはアプリケーションを最適化するための素晴らしい第一歩です。
キャッシュされたデータを自動的に期限切れにする
データをmemcachedに保存する場合 、有効期限( memcached の最大秒数)を設定できます キーと値を維持します。その遅延の後、 memcached キーをキャッシュから自動的に削除します。
このキャッシュ時間を何に設定する必要がありますか?この遅延のマジックナンバーはありません。それは、使用しているデータとアプリケーションのタイプに完全に依存します。数秒になることもあれば、数時間かかることもあります。
キャッシュの無効化 現在のデータと同期していないためにキャッシュを削除するタイミングを定義する、も、アプリケーションが処理する必要があるものです。特に、古すぎるデータや古いデータを提示する場合 避けるべきです。
ここでも、魔法のレシピはありません。構築しているアプリケーションのタイプによって異なります。ただし、処理する必要のある範囲外のケースがいくつかあります。これについては、上記の例ではまだ説明していません。
キャッシングサーバーは無限に成長することはできません。メモリは有限のリソースです。したがって、キーは、他のものを格納するためにより多くのスペースが必要になるとすぐに、キャッシングサーバーによってフラッシュされます。
一部のキーは、有効期限(「存続時間」またはTTLとも呼ばれます)に達したために有効期限が切れることもあります。そのような場合、データは失われ、正規のデータソースを再度照会する必要があります。
これは実際よりも複雑に聞こえます。 Pythonでmemcachedを操作する場合は、通常、次のパターンで操作できます。
from pymemcache.client import base
def do_some_query():
# Replace with actual querying code to a database,
# a remote REST API, etc.
return 42
# Don't forget to run `memcached' before running this code
client = base.Client(('localhost', 11211))
result = client.get('some_key')
if result is None:
# The cache is empty, need to get the value
# from the canonical source:
result = do_some_query()
# Cache the result for next time:
client.set('some_key', result)
# Whether we needed to update the cache or not,
# at this point you can work with the data
# stored in the `result` variable:
print(result)
注: 通常のフラッシュアウト操作のため、欠落しているキーの処理は必須です。コールドキャッシュシナリオを処理することも義務付けられています。つまり、 memcached 開始されたばかりです。その場合、キャッシュは完全に空になり、一度に1つのリクエストでキャッシュを完全に再設定する必要があります。
これは、キャッシュされたデータを一時的なものとして表示する必要があることを意味します。また、以前にキャッシュに書き込んだ値がキャッシュに含まれることを期待してはなりません。
コールドキャッシュのウォーミングアップ
memcached など、一部のコールドキャッシュシナリオは防止できません。 クラッシュ。ただし、たとえば、新しい memcachedに移行できるものもあります。 サーバ。
コールドキャッシュシナリオが発生することが予測できる場合は、それを回避することをお勧めします。キャッシュを補充する必要があるということは、キャッシュされたデータの正規のストレージが突然、キャッシュデータを持たないすべてのキャッシュユーザーによって大規模に攻撃されることを意味します(雷の群れの問題としても知られています)。
pymemcache FallbackClient
という名前のクラスを提供します これは、ここに示されているように、このシナリオの実装に役立ちます:
from pymemcache.client import base
from pymemcache import fallback
def do_some_query():
# Replace with actual querying code to a database,
# a remote REST API, etc.
return 42
# Set `ignore_exc=True` so it is possible to shut down
# the old cache before removing its usage from
# the program, if ever necessary.
old_cache = base.Client(('localhost', 11211), ignore_exc=True)
new_cache = base.Client(('localhost', 11212))
client = fallback.FallbackClient((new_cache, old_cache))
result = client.get('some_key')
if result is None:
# The cache is empty, need to get the value
# from the canonical source:
result = do_some_query()
# Cache the result for next time:
client.set('some_key', result)
print(result)
FallbackClient
順序を尊重して、コンストラクターに渡された古いキャッシュを照会します。この場合、常に新しいキャッシュサーバーが最初に照会され、キャッシュミスが発生した場合は、古いキャッシュサーバーが照会され、プライマリデータソースへのリターントリップの可能性が回避されます。
いずれかのキーが設定されている場合、そのキーは新しいキャッシュにのみ設定されます。しばらくすると、古いキャッシュを廃止してFallbackClient
を使用できなくなります。 new_cache
に直接置き換えることができます クライアント。
確認と設定
リモートキャッシュと通信する場合、通常の同時実行の問題が再発します。複数のクライアントが同時に同じキーにアクセスしようとしている可能性があります。 memcached チェックと設定を提供します 操作、 CASに短縮 、これはこの問題の解決に役立ちます。
最も単純な例は、ユーザー数をカウントしたいアプリケーションです。訪問者が接続するたびに、カウンターは1ずつ増加します。 memcachedを使用する 、簡単な実装は次のようになります:
def on_visit(client):
result = client.get('visitors')
if result is None:
result = 1
else:
result += 1
client.set('visitors', result)
ただし、アプリケーションの2つのインスタンスがこのカウンターを同時に更新しようとすると、どうなりますか?
最初の呼び出しclient.get('visitors')
両方に同じ数の訪問者を返します。たとえば、42です。次に、両方が1を加算し、43を計算し、訪問者の数を43に設定します。その数は間違っているため、結果は44、つまり42+になります。 1+1。
この同時実行性の問題を解決するために、 memcachedのCAS操作 便利です。次のスニペットは正しいソリューションを実装しています:
def on_visit(client):
while True:
result, cas = client.gets('visitors')
if result is None:
result = 1
else:
result += 1
if client.cas('visitors', result, cas):
break
gets
get
と同じように、メソッドは値を返します メソッドですが、CAS値も返します。 。
この値の内容は関係ありませんが、次のメソッドcas
に使用されます 電話。このメソッドは、set
と同等です。 gets
以降に値が変更された場合は失敗することを除いて、操作 手術。成功した場合、ループは中断されます。それ以外の場合、操作は最初から再開されます。
アプリケーションの2つのインスタンスが同時にカウンターを更新しようとするシナリオでは、1つだけがカウンターを42から43に移動することに成功します。2番目のインスタンスはFalse
を取得します。 client.cas
によって返される値 を呼び出し、ループを再試行する必要があります。今回は43を値として取得し、44にインクリメントし、そのcas
呼び出しが成功し、問題が解決します。
カウンターをインクリメントすることは、CASが単純化されているため、CASがどのように機能するかを説明する例として興味深いものです。ただし、 memcached incr
も提供します およびdecr
複数のgets
を実行するのではなく、単一のリクエストで整数をインクリメントまたはデクリメントするメソッド / cas
呼び出します。実際のアプリケーションでは、gets
およびcas
より複雑なデータ型または操作に使用されます
ほとんどのリモートキャッシングサーバーとデータストアは、同時実行の問題を防ぐためのそのようなメカニズムを提供します。それらの機能を適切に使用するには、これらのケースを認識することが重要です。
キャッシングを超えて
この記事で説明する簡単なテクニックは、 memcachedを活用するのがいかに簡単かを示しています。 Pythonアプリケーションのパフォーマンスを高速化するため。
2つの基本的な「設定」操作と「取得」操作を使用するだけで、多くの場合、データの取得を高速化したり、結果を何度も再計算することを回避したりできます。 memcachedを使用すると、多数の分散ノード間でキャッシュを共有できます。
チェックアンドセット(CAS)など、このチュートリアルで見たその他のより高度なパターン 操作により、データの破損を回避しながら、複数のPythonスレッドまたはプロセス間で同時にキャッシュに保存されているデータを更新できます。
より高速でスケーラブルなPythonアプリケーションを作成するための高度な手法について詳しく知りたい場合は、ScalingPythonをご覧ください。ネットワーク分散、キューイングシステム、分散ハッシュ、コードプロファイリングなどの多くの高度なトピックをカバーしています。