のclass XFactory
オブジェクトを作成するファクトリがありますclass X
。のインスタンスX
は非常に大きいため、ファクトリの主な目的は、クライアントコードに対してできるだけ透過的にインスタンスをキャッシュすることです。のオブジェクトclass X
は不変であるため、次のコードは妥当なようです。
# module xfactory.py
import x
class XFactory:
_registry = {}
def get_x(self, arg1, arg2, use_cache = True):
if use_cache:
hash_id = hash((arg1, arg2))
if hash_id in _registry:
return _registry[hash_id]
obj = x.X(arg1, arg2)
_registry[hash_id] = obj
return obj
# module x.py
class X:
# ...
それは良いパターンですか?(実際のファクトリーパターンではないことはわかっています。)変更すべき点はありますか?
現在、X
オブジェクトをディスクにキャッシュしたい場合があります。pickle
そのために使用_registry
し、オブジェクトへの参照ではなく、ピクルスオブジェクトのファイル名に値として格納します。もちろん、_registry
それ自体を永続的に保存する必要があります(おそらく、独自のピクルファイル、テキストファイル、データベース、または単にピクルファイルにを含むファイル名を与えることによってhash_id
)。
現在、キャッシュされたオブジェクトの有効性は、に渡されるパラメーターだけでなくget_x()
、これらのオブジェクトを作成したコードのバージョンにも依存しています。
厳密に言えば、誰かx.py
がその依存関係を変更または変更し、プログラムの実行中にそれを再ロードすると、メモリキャッシュされたオブジェクトでさえ無効になる可能性があります。これまでのところ、私のアプリケーションではありそうにないため、この危険性は無視しました。ただし、オブジェクトが永続ストレージにキャッシュされている場合は、無視できません。
私に何ができる?hash_id
引数arg1
とを含むタプルのハッシュ、およびarg2
ファイル名と最終更新日、x.py
および(再帰的に)依存するすべてのモジュールとデータファイルを計算することで、より堅牢にすることができると思います。二度と役に立たなくなるキャッシュファイルを削除する_registry
ために、各レコードの変更された日付のハッシュ化されていない表現に追加します。
しかし、理論的には誰かがモジュールを動的にロードする可能性があるため、このソリューションでさえ100%安全ではありません。ソースコードを静的に分析することからは、私はそれについて知りません。私がすべてに出て、プロジェクト内のすべてのファイルが依存関係であると想定すると、一部のモジュールが外部のWebサイトなどからデータを取得した場合でも、メカニズムは機能しなくなります。
さらに、x.py
とその依存関係の変更の頻度が非常に高いため、キャッシュの無効化が頻繁に発生します。
したがって、私はある程度の安全性を放棄し、明らかな不一致がある場合にのみキャッシュを無効化することも考えました。これはclass X
、キャッシュを無効にする変更が発生したと開発者が信じる場合は常に変更する必要があるクラスレベルのキャッシュ検証識別子を持つことを意味します。(複数の開発者と、別失効識別子がそれぞれに必要とされる。)この識別子は、一緒にハッシュされるarg1
とarg2
とに格納されたハッシュキーの一部となります_registry
。
開発者は検証識別子の更新を忘れるか、既存のキャッシュが無効化されたことに気付かないclass X
可能性があるため、別の検証メカニズムを追加するほうがよいように思われますX
。たとえばX
、がテーブルの場合、すべての列の名前を追加します。ハッシュ計算には、特性も含まれます。
このコードを書くことはできますが、重要な何かが欠けていると思います。また、このすべてをすでに実行できるフレームワークまたはパッケージがあるかどうかも疑問に思っています。インメモリキャッシングとディスクベースキャッシングを組み合わせるのが理想的です。
編集:
私のニーズはプールパターンで十分に対応できるように思えるかもしれません。しかし、さらに調査すると、そうではありません。私は違いを挙げようと思いました:
オブジェクトは複数のクライアントで使用できますか?
- プール:いいえ、各オブジェクトをチェックアウトして、不要になったときにチェックインする必要があります。正確なメカニズムは複雑かもしれません。
- XFactory:はい。オブジェクトは不変であり、無限に多くのクライアントが一度に使用できます。同じオブジェクトの2番目のコピーを作成する必要はありません。
プールサイズを制御する必要がありますか?
- プール:多くの場合、はい。もしそうなら、そうするための戦略はかなり複雑かもしれません。
- XFactory:いいえ。オブジェクトはオンデマンドでクライアントに配信する必要があり、既存のオブジェクトが適切でない場合は、新しいオブジェクトを作成する必要があります。
すべてのオブジェクトは自由に置き換え可能ですか?
- プール:はい、オブジェクトは通常自由に置き換え可能です(そうでない場合は、クライアントが必要とするオブジェクトを確認するのは簡単です)。
- XFactory:絶対にありません。特定のオブジェクトが特定のクライアント要求に対応できるかどうかを確認することは非常に困難です。それは、(a)同じ引数と(b)同じバージョンのソースコードで作成された既存のオブジェクトが使用可能かどうかによって異なります。パート(b)はXFactoryで検証できないため、クライアントに支援を求めます。クライアントは2つの方法でこの責任を果たします。最初に、クライアントは指定された複数の内部バージョンカウンター(開発者ごとに1つ)のいずれかを増分できます。これは実行時に発生することはありません。ソースコードの変更により既存のオブジェクトが使用できなくなると考えた場合にのみ、これらのカウンターを変更できます。次に、クライアントは必要なオブジェクトに関する不変条件を返し、XFactoryはオブジェクトをクライアントに提供する前にこれらの不変条件が違反されていないことを確認します。これらのチェックのいずれかが失敗した場合、
パフォーマンスへの影響は注意深い分析が必要ですか?
- プール:はい。場合によっては、オブジェクト管理のオーバーヘッドがオブジェクトの作成/破棄のオーバーヘッドよりも大きいと、プールが実際にパフォーマンスを低下させることがあります。
- XFactory:いいえ。問題のオブジェクトの計算コストは非常に高いことが知られており、それらをメモリまたはディスクからロードすることは、オブジェクトを最初から再計算するよりも間違いなく優れています。
オブジェクトはいつ破壊されますか?
- プール:プールがシャットダウンされたとき。リソースを(部分的に)解放するように指示された場合、または特定のオブジェクトがしばらく使用されていない場合、オブジェクトを破壊する可能性もあります。
- XFactory:不変の違反またはカウンターの不一致のいずれかによって証明されるように、最新ではないソースコードのバージョンでオブジェクトが作成された場合。このようなオブジェクトを適切なタイミングで見つけて破棄するプロセスは非常に複雑です。さらに、すべてのオブジェクトの時間ベースの無効化を実装して、無効なオブジェクトを使用することで蓄積されたリスクを減らすことができます。XFactoryはそれがオブジェクトの唯一の所有者であることを決して確認しないので、このような無効化は、クライアントオブジェクトの追加の「バージョンカウンター」によって最適に達成されます。
マルチスレッド環境にはどのような特別な考慮事項がありますか?
- プール:オブジェクトのチェックアウト/チェックインでの衝突を回避する必要があります(2つのクライアントに対してオブジェクトをチェックアウトしたくない)
- XFactory:オブジェクト作成時の衝突を回避する必要があります(2つの同一の要求に基づいて2つのオブジェクトを作成したくない)
クライアントがオブジェクトを解放しない場合、何をする必要がありますか?
- プール:しばらく待ってから、オブジェクトを他のユーザーが使用できるようにする場合があります。
- XFactory:適用されません。クライアントは、オブジェクトがいつ処理されたかをXFactoryに通知しません。
オブジェクトを変更する必要がありますか?
- プール:再利用する前にデフォルトの状態にリセットする必要がある場合があります。
- XFactory:いいえ、オブジェクトは不変です。
オブジェクトの永続化に関連する特別な考慮事項はありますか?
- プール:通常はありません。プールはオブジェクト作成のコストを節約するためのものなので、すべてのオブジェクトがメモリに保持されます(ディスクからの読み取りは目的に反します)。
- XFactory:はい、XFactoryは複雑な計算を実行するコストを節約することを目的としているため、事前計算されたオブジェクトをディスクに保存することは理にかなっています。その結果、XFactoryは永続ストレージの一般的な問題に対処する必要があります。たとえば、初期化時に、永続ストレージに接続し、そこから現在利用可能なオブジェクトに関するメタデータを取得し、要求された場合はそれらをメモリにロードする準備をする必要があります。オブジェクトは、「存在しない」、「ディスク上に存在する」、「メモリ内に存在する」の3つの状態のいずれかになります。XFactoryの実行中、状態は一方向(このシーケンスでは右側)にのみ変化します。
要約すると、プールの複雑さはアイテム1、2、4、6、およびおそらく5、7、8にあります。XFactoryの複雑度はアイテム3、6、9にあります。唯一のオーバーラップはアイテム6であり、コアではありませんプールまたはXFactoryのいずれかの機能ですが、マルチスレッド環境で機能する必要があるすべてのパターンに共通する設計上の制約です。