Отражены ли метаданные SQLAlchemy в задачах сельдерея? - PullRequest
0 голосов
/ 29 сентября 2018

Для лучшей тестируемости и по другим причинам хорошо иметь конфигурацию сеансов базы данных SQLAlchemy неглобальной, как очень хорошо описано в следующем вопросе:

как настроить сеанс sqlalchemy в задачах сельдерея безглобальная переменная (также обсуждаемая в 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, если это уместно).

Я думал об использовании изменения версии alembic в качестве сигнала для повторного отражения, но не совсем уверен, как заставить его работать в сельдерее.Например, если более чем один работник сразу почувствует изменение, глобальный meta может обрабатываться без поточной защиты.

Если это имеет значение, использование сельдерея в данном случае автономно, нетмодули / приложения веб-фреймворка / все, что присутствует в приложении сельдерея.Эта проблема также упрощается, поскольку используется только ядро ​​SQLAlchemy, а не средство отображения объектов.

1 Ответ

0 голосов
/ 21 мая 2019

Это только частичное решение, и оно для SQLAlchemy ORM (но я думаю, что нечто подобное легко реализовать для Core).

Основные моменты:

  • двигатель находится в модулеуровень, но config (URL доступа, параметры) из os.environ
  • сеанс находится в своей собственной фабричной функции
  • на уровне модуля: BaseModel = automap_base(), и затем классы таблиц используют этот BaseModel в качестве суперклассаи обычно только один аргумент - __tablename__, но туда можно добавить произвольные отношения, атрибуты (очень похоже на обычное использование ORM)
  • на уровне модуля: BaseModel.prepare(ENGINE, reflect=True)

Тесты(используя pytest) внедрить переменную среды (например, DB_URL) в conftest.py на уровне модуля.

Один важный момент: database_session всегда инициируется (то есть вызывается заводская функция) в целевой функции,и распространяется на все функции явно.Этот способ позволяет естественным образом контролировать единицы работы, обычно по одной транзакции на задачу.Это также упрощает тестирование, потому что все функции, использующие базу данных, могут быть предоставлены с поддельной или реальной (тестовой) сессией базы данных.

«Задача» - это вышеупомянутая функция, которая вызывается в функции, котораяоформлен заданием - таким образом функция задания может быть протестирована без механизма задания.

Это только частичное решение, поскольку повторного отражения нет.Если рабочие задачи могут быть остановлены на мгновение (и база данных в любом случае испытывает простои из-за изменений схемы), поскольку это обычно фоновые задачи, поэтому это не представляет проблемы.Рабочие также могут быть перезапущены с помощью некоторого внешнего наблюдателя, который может отслеживать изменения базы данных.Это может быть удобно с помощью супервизора или другого способа управления работниками сельдерея, работающими на переднем плане.

В общем, после того, как я решил проблему, как описано выше, я больше ценю философию «явный лучше, чем неявный».,Все эти волшебные «приложения», «запросы», будь то в сельдерее или Flask, могут принести крошечные сокращения в сигнатурах функций, но я бы предпочел передать какой-то контекст по цепочке вызовов для улучшения тестируемости и лучшего понимания контекста иуправление.

...