Mon 26 December 2011
Исправление работы django-kombu с InnoDB
Наверное многие из вас используют django-celery, или хотя бы слышали про этот инструмент для асинхронного выполнения задач. В самом простейшем случае, когда нет желания взводить дополнительные redis или AMQP сервера, очередь задач организуется через уже имеющуюся базу данных. В моем случае это был MySQL.
В случае, когда очередь организуется через базу, Celery использует специальный бэкенд django-kombu, который устроен довольно просто — при добавлении сообщения, оно сериализуется и кладется в табличку БД, при операции pop, запись из базы изымается. Такой вот AMQP для бедных.

Итак, я использовал MySQL и celery работал отлично до тех пор, пока не было принято решение сконвертировать все таблички в InnoDB дабы использовать транзакции. После такого изменения, celery перестал "подхватывать" новые задачи и выполнял их только после рестарта.
В интернетах предлагалось три решения этой проблемы:
- откатить все таблички django-kombu обратно на движок MyISAM;
- установить в конфиге MySQL
ISOLATION LEVEL READ-COMMITTED; - раскомментировать некую строчку в сорцах django-kombu, после чего он станет переоткрывать коннекты к базе при каждой операции.
Объясню, почему все три никуда не годятся.
- Вариант, предлагающий держать часть табличек в MyISAM, лишает нас транзакционности.
- Установка иного
ISOLATION LEVEL, глобально или в рамках сессии, может привести к некоторым "спецэффектам" при выполнении задач, рассчитанных на более строгую изоляцию. - Переустановка соединения с базой каждый раз — просто грязный хак, замедляющий работу очереди в несколько раз.
Пришлось лезть в кишки django-kombu и править все самому. Оказалось, что по время опроса очереди,
библиотека держит коннект с базой открытым и все селекты выполняются в рамках одной длиииной трензакции.
Это означает, что все сообщения, которые будут добавлены в очередь в процессе, kombu не увидит
из за того, что в MySQL по умолчанию установлен ISOLATION LEVEL REPEATABLE READ.
В итоге, я обернул несколько методов в декоратор commit_on_success, и всё заработало как надо. При этом небольшой тест показал производительность в 3 раза больше чем у версии с "грязным" хаком.
Попутно поправил еще один баг, связанный с тем, что celery завершала свою работу в случае, когда MySQL становился недоступен.
Создал Pull Request, но пока его не смерджили, исправления в моей ветке на GitHub.
Comments !