1

我知道标题很复杂,所以我将尝试给出一个简化的示例来说明我正在尝试做的事情。请容忍我一会儿。

我正在研究期权价值计算和投资组合风险分析服务器,我遇到了一个问题,我不太确定在加载我的持久对象图然后访问一些我动态添加的缓存属性时如何解决数据库负载。

假设我有以下映射类:

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 函数时,我会得到一个 AttributeError ,说明 Portfolio 对象没有 values_store 属性。

起初这真的让我很困惑,但是当我稍微调试一下这个问题时,我似乎发现引用同一个 Portfolio 的子 AssetClass 对象实际上可能引用了内存中的不同对象。在使用 python 的 id() 函数在每次访问期间检查 Portfolio.id 与内存 id 后,我开始相信这一点。这样做,我经常会发现,虽然我可能只访问过一个 Portfolio.id,但我引用了几个不同的内存地址,具体取决于哪个孩子正在访问。

我知道这很啰嗦,我很感激那些一直坚持我的人。我的问题是,有没有办法让我将整个对象图加载到内存中一次且只有一次?从资源的角度来看,这个 ValuesStore 对象可能很昂贵,我希望尽可能少地实例化它。

作为参考,我正在使用 Python 2.7.3、SQLAlchemy 0.8、gevent 1.0rc2,并且在 Windows 7 和 OS X 10.8 上都遇到了同样的问题。

再次感谢。

** 编辑 **

我花了很多时间浏览我的代码,寻找会导致 Portfolio 对象与当前会话分离并附加到新会话的语句。我找不到任何类似的东西。我尝试监听各种 Instance 和 Session 事件,每次,我都可以看到正在加载的“新”投资组合,但它是由原始 Session 加载的。

投资组合对象是否有可能被垃圾收集?我尝试通过生成一个新的 Gevent 线程来测试这个假设,以保持对 Portfolio 的强引用,如下所示:

def keep_portfolio_alive(p):
    portfolio = p
    while True:
        gevent.sleep(0)

这似乎可以解决问题。我再也没有看到 Session 再次加载投资组合。

** 编辑 2 **

所以我不确定,但我相信我找到了我的问题。在定义映射类之间的关系时,我实际上使用了 attribute_mapped_collections 而不是普通列表。初始化类时,我会像这样遍历子对象:

for asset_class in portfolio.asset_classes.itervalues():
    # do something

由于其中一些类是多重嵌套的,因此在层次结构中,有一个类已使用此复制迭代器引用,然后它使用另一个复制迭代器引用其自己的子类。我最好的猜测是 Portfolio 类最终在它和它的父对象之间或者它和它的任何子对象之间没有强引用。然后 GC 在某个时候将其标记为收集。

希望这对将来的某人有所帮助。

4

0 回答 0