ZODB зафиксировал залипание и заставил мое приложение заморозить - PullRequest
0 голосов
/ 10 ноября 2018

Сегодня я обнаружил ошибку в моем приложении на Python, использующем ZODB. Пытаясь выяснить, почему мое приложение зависает, я решил, что причиной является ZODB.

При настройке ведения журнала для отладки кажется, что при фиксации ZODB найдет 2 соединения и затем начнет зависать.

INFO:ZEO.ClientStorage:('127.0.0.1', 8092) Connected to storage: ('localhost', 8092)
DEBUG:txn.140661100980032:new transaction
DEBUG:txn.140661100980032:commit
DEBUG:ZODB.Connection:Committing savepoints of size 1858621925
DEBUG:discord.gateway:Keeping websocket alive with sequence 59.
DEBUG:txn.140661100980032:commit <Connection at 7fee2d080fd0>
DEBUG:txn.140661100980032:commit <Connection at 7fee359e5cc0>

Поскольку я новичок в ZODB, есть идеи, как решить / как копать глубже?

Похоже, это связано с одновременными коммитами.

Я полагал, что открытие нового соединения инициирует выделенный менеджер транзакций, но это не так. При инициализации нового соединения без указания диспетчера транзакций используется локальное (совместно используемое с другими соединениями в потоке).

Мой код:

async def get_connection():
    return ZEO.connection(8092)

async def _message_db_init_aux(self, channel, after=None, before=None):
    connexion = await get_connection()
    root = connexion.root()

    messages = await some_function_which_return_a_list()

    async for message in messages:
        # If author.id doesn't exist on the data, let's initiate it as a Tree
        if message.author.id not in root.data: # root.data is a BTrees.OOBTree.BTree()
            root.data[message.author.id] = BTrees.OOBTree.BTree()

        # Message is a defined classed inherited from persistant.Persistant
        root.data[message.author.id][message.id] = Message(message.id, message.author.id, message.created_at)

    transaction.commit()
    connexion.close()

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

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

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

Более того, используя ZEO.connection() для каждой транзакции, вы заставляете ZEO создать полностью новый объект клиента со свежим кешем и пулом соединений.Используя вместо этого ZEO.DB() и кэшируя результат, создается один клиент, из которого можно объединять и повторно использовать соединения, и с локальным кэшем для ускорения транзакций.

Я бы изменил код на:

def get_db():
    """Access the ZEO database client.

    The database client is cached to take advantage of caching and connection pooling

    """
    db = getattr(get_db, 'db', None)
    if db is None:
        get_db.db = db = ZEO.DB(8092)
    return db

async def _message_db_init_aux(self, channel, after=None, before=None):
    with self.get_db().transaction() as conn:
        root = conn.root()

        messages = await some_function_which_return_a_list()

        async for message in messages:
            # If author.id doesn't exist on the data, let's initiate it as a Tree
            if message.author.id not in root.data: # root.data is a BTrees.OOBTree.BTree()
                root.data[message.author.id] = BTrees.OOBTree.BTree()

            # Message is a defined classed inherited from persistant.Persistant
            root.data[message.author.id][message.id] = Message(
                message.id, message.author.id, message.created_at
            )

Метод .transaction() для объекта базы данных создает новое соединение под капотом, в момент ввода контекста (with вызывает вызов __enter__) и когда with блокировка завершается, транзакция фиксируется и соединение снова устанавливается в пул.

Обратите внимание, что я использовал синхронный метод def get_db();подписи вызовов в коде клиента ZEO полностью синхронны.Их безопасно вызывать из асинхронного кода, потому что под капотом , реализация использует повсюду asyncio, используя обратные вызовы и задачи в одном и том же цикле, а фактический ввод-вывод откладывается на отдельные задачи.

0 голосов
/ 11 ноября 2018

Если не указано иное, используется локальный менеджер транзакций. Если вы открываете несколько соединений в одном и том же потоке, вам необходимо указать диспетчер транзакций, который вы хотите использовать. По умолчанию

 transaction.commit()

- локальный менеджер транзакций.

 connection.transaction.manager.commit()

будет использовать менеджер транзакций, выделенный для транзакции (а не локальный).

Для получения дополнительной информации, проверьте http://www.zodb.org/en/latest/guide/transactions-and-threading.html

...