タイトルが複雑であることはわかっているので、私がやろうとしていることの簡単な例を挙げようと思います。しばらくお付き合いください。
オプション値の計算とポートフォリオ リスク分析サーバーで作業を行っていますが、永続化されたオブジェクト グラフを読み込んで、動的に追加したキャッシュされたプロパティにアクセスするときに、対処方法がよくわからない問題に遭遇しました。データベースの負荷。
次のマップされたクラスがあるとします。
Base = declarative_base()
class Portfolio(Base):
__tablename__ = "portfolios"
id = Column(Integer, primary_key=True)
asset_classes = relationship("AssetClass", backref="portfolios")
class AssetClass(Base):
__tablename__ = "asset_classes"
id = Column(Integer, primary_key=True)
portolio_id = Column(Integer, ForeignKey("portfolios.id"))
assets = relationship("Asset", backref="asset_classes")
class Asset(Base):
id = Column(Integer, primary_key=True)
asset_class_id = Column(Integer, ForeignKey("asset_classes.id"))
次に、ポートフォリオをロードし、次のように初期化を行います。
session = Session()
# pretend there's only one Portfolio
portfolio = session.query(Portfolio).one()
init_portfolio(portfolio)
while(keep_server_alive):
# session stays open for server life
gevent.sleep(0)
session.close()
def init_portfolio(p):
# note that this is a dynamically added property
portfolio.values_store = ValuesStore()
for asset_class in p.asset_classes:
init_asset_class(asset_class)
def init_asset_class(ac):
for asset in ac.assets:
init_asset(asset)
すべてが初期化されたら、長時間実行される gevent スレッドを使用して、次のように価格の更新を処理し、特定の微分値を計算します。
def on_underlying_update(asset, underlying_price):
# the next line non-deterministically fails with an AttributeError
values_store = asset.asset_class.portfolio.values_store
values_store.get_asset_price(asset, underlying_price)
これらの計算は非常にコストがかかる可能性があるため、ValuesStore オブジェクトには結果を格納するためのキャッシュ戦略があります。on_underlying_update 関数をランダムにパスすると、Portfolio オブジェクトに values_store プロパティがないことを示す AttributeError が返されます。
最初はこれに戸惑いましたが、問題を少しデバッグしていると、同じ Portfolio を参照している子 AssetClass オブジェクトが実際にはメモリ内の別のオブジェクトを参照している可能性があることがわかりました。Python の id() 関数を使用して、各アクセス中に Portfolio.id をメモリ ID に対してチェックした後、私はこれを信じるようになりました。そうすることで、アクセスする Portfolio.id は 1 つしかないのに、どの子がアクセスしているかに応じて、複数の異なるメモリ アドレスを参照していることに気付くことがよくあります。
私はこれが長々と続いたことを知っています。私の質問は、オブジェクト グラフ全体を一度だけメモリにロードする方法はありますか? この ValuesStore オブジェクトは、リソースの観点からはコストがかかる可能性があるため、インスタンス化する回数はできるだけ少なくしたいと考えています。
参考までに、私は Python 2.7.3、SQLAlchemy 0.8、gevent 1.0rc2 を使用していますが、Windows 7 と OS X 10.8 の両方で同じ問題が発生しています。
再度、感謝します。
** 編集 **
Portfolio オブジェクトを現在のセッションから切り離し、新しいセッションにアタッチするステートメントを探してコードを調べるのに多くの時間を費やしました。そのようなものは見つかりませんでした。あらゆる種類のインスタンス イベントとセッション イベントをリッスンしようとしましたが、毎回、「新しい」ポートフォリオが読み込まれているのを確認できましたが、それは元のセッションによって読み込まれていました。
Portfolio オブジェクトがガベージ コレクションされている可能性はありますか? 次のように、ポートフォリオへの強い参照を保持するために新しい Gevent スレッドを生成することにより、この仮説をテストしてみました。
def keep_portfolio_alive(p):
portfolio = p
while True:
gevent.sleep(0)
これで問題は解決したようです。セッションがポートフォリオを 2 回ロードすることはありません。
** 編集 2 **
よくわかりませんが、問題を見つけたと思います。マップされたクラス間の関係を定義するとき、実際には通常のリストの代わりに attribute_mapped_collections を使用しました。クラスを初期化するとき、次のように子オブジェクトを反復処理します。
for asset_class in portfolio.asset_classes.itervalues():
# do something
これらのクラスの一部は多重ネストされているため、階層に沿って、このコピーされた反復子を使用して参照されたクラスがあり、別のコピーされた反復子を使用して独自の子クラスを参照していました。私の推測では、Portfolio クラスは最終的に、それとその親オブジェクトの間、またはそれとその子オブジェクトのいずれかとの間に強い参照がなくなったということです。その後、GC はある時点でコレクション用にマークを付けました。
これが将来誰かに役立つことを願っています。