キャッシュにより、CPU レベルの操作からデータベース インターフェイスに至るまで、処理が大幅に高速化されます。 キャッシュの無効化、つまりキャッシュされたデータをいつ削除するかを決定することは、複雑な課題です。この投稿では、単純ではあるが潜伏性のあるキャッシュの問題に対処します。
この問題は 18 か月間潜んでいましたが、ユーザーが推奨される使用パターンから逸脱した場合にのみ表面化しました。 この問題は、私の組織内のカスタム機械学習 (ML) フレームワーク (scikit-learn 上に構築) に起因していました。 このフレームワークは複数のデータ ソースに頻繁にアクセスするため、パフォーマンスとコストの最適化(BigQuery の下りコストの削減)のためにキャッシュ レイヤーが必要になります。
当初は lru_cache
が使用されていましたが、開発中に頻繁にアクセスされる静的データには永続的なキャッシュが必要でした。 SQLite を使用する Python ライブラリである DiskCache
は、そのシンプルさと 32 プロセス環境および Pandas DataFrames (最大 500MB) との互換性のために選択されました。 メモリ内アクセスのために lru_cache
レイヤーが最上位に追加されました。
この問題は、より多くのユーザーがフレームワークを実験するにつれて明らかになりました。 ランダムに不正確な結果が報告され、一貫して再現することが困難でした。 根本原因: キャッシュされた Pandas DataFrame のインプレース変更。
私たちのコーディング標準では、処理後に新しい DataFrame を作成することが規定されています。 ただし、一部のユーザーは習慣で inplace=True
を使用し、キャッシュされたオブジェクトを直接変更していました。 これにより、直接の結果が変更されただけでなく、キャッシュされたデータも破損し、後続のリクエストに影響を及ぼしました。
説明のために、辞書を使用したこの単純化された例を考えてみましょう。
<code class="language-python">from functools import lru_cache import time import typing as t from copy import deepcopy @lru_cache def expensive_func(keys: str, vals: t.Any) -> dict: time.sleep(3) return dict(zip(keys, vals)) def main(): e1 = expensive_func(('a', 'b', 'c'), (1, 2, 3)) print(e1) e2 = expensive_func(('a', 'b', 'c'), (1, 2, 3)) print(e2) e2['d'] = "amazing" print(e2) e3 = expensive_func(('a', 'b', 'c'), (1, 2, 3)) print(e3) if __name__ == "__main__": main()</code>
lru_cache
はコピーではなく参照を提供します。 e2
を変更すると、キャッシュされたデータが変更されます。
解決策:
この解決策には、キャッシュされたオブジェクトのディープ コピーを返すことが含まれます。
<code class="language-python">from functools import lru_cache, wraps from copy import deepcopy def custom_cache(func): cached_func = lru_cache(func) @wraps(func) def _wrapper(*args, **kwargs): return deepcopy(cached_func(*args, **kwargs)) return _wrapper</code>
これにより、わずかなオーバーヘッド (データの重複) が追加されますが、データの破損は防止されます。
重要なポイント:
lru_cache
の参照動作をより深く理解します。以上がPythonキャッシュ変動値の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。