在芹菜任务中反映了SQLAlchemy元数据?

时间:2018-09-29 17:09:48

标签: sqlalchemy celery

出于更好的可测试性和其他原因,最好将SQLAlchemy数据库会话配置为非全局的,如以下问题所述:

how to setup sqlalchemy session in celery tasks with no global variable(并在https://github.com/celery/celery/issues/3561中进行了讨论)

现在,问题是,如何优雅地处理元数据?如果我的理解是正确的,则元数据只能有一次,例如:

engine = create_engine(DB_URL, encoding='utf-8', pool_recycle=3600,
                       pool_size=10)
# db_session = get_session()  # this is old global session
meta = MetaData()
meta.reflect(bind=engine)

由于性能原因,对每个任务的执行进行反思是不好的,元数据或多或少是稳定的并且是线程安全的结构(如果我们只读的话)。

但是,元数据有时会发生变化(celery不是数据库模式的“所有者”),从而导致工作程序出错。

以一种可测试的方式处理meta的优雅方法,又仍然能够对基础数据库的更改做出反应是什么? (如果相关,请使用英语)。

我当时曾想过使用Alembic版本更改作为重新反映的信号,但还不确定如何使它在芹菜中正常工作。例如,如果一个以上的工作人员立即感知到更改,则可以以非线程安全的方式对待全局meta

如果这很重要,那么在这种情况下celery的使用是独立的,celery应用程序中不存在任何Web框架模块/应用程序/任何内容。由于仅使用SQLAlchemy Core,而不使用对象映射器,因此该问题也得到了简化。

1 个答案:

答案 0 :(得分:1)

这只是部分解决方案,适用于SQLAlchemy ORM(但我想类似的事情对于Core来说很容易实现)。

要点:

  • 引擎在模块级别,但是配置(访问URL,参数)来自os.environ
  • 会话位于其自身的工厂功能中
  • 在模块级别:BaseModel = automap_base(),然后表类将BaseModel用作超类,通常仅使用一个参数-__tablename__,但是可以在其中添加任意关系,属性(非常类似于正常的ORM使用)
  • 在模块级别:BaseModel.prepare(ENGINE, reflect=True)

在模块级别的DB_URL中测试(使用pytest)注入环境变量(例如conftest.py)。

重要的时刻:database_session始终在任务函数中启动(即调用了工厂函数),并明确地传播到所有函数中。这种方式允许自然地控制工作单元,通常每个任务一个事务。这也简化了测试,因为所有使用数据库的功能都可以通过伪或真实(测试)数据库会话提供。

“任务功能”是上面的一个函数,该函数在该函数中被调用,并由任务装饰-这样就可以在没有任务机制的情况下测试任务功能。

这只是部分解决方案,因为重做反射不存在。如果任务工作者可以停止一会儿(并且由于架构更改数据库总是会停机),因为它们通常是后台任务,那么这不会造成问题。还可以通过某些外部监视程序来重新启动工作程序,该监视程序可以监视数据库更改。通过使用监督者或其他方法来控制在前台运行的芹菜工人,可以使此操作变得方便。

总而言之,在我解决了上述问题之后,我更加重视“显性胜于隐性”的哲学。所有那些神奇的“ app”(无论是芹菜还是Flask中的“ request”)都可能在功能签名中引入小写的缩写,但我宁愿在调用链中传递某种上下文,以提高可测试性和更好的上下文理解以及管理。