четверг, 25 февраля 2016 г.

Запуск только одной задачи на celery в один момент времени

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

https://gist.github.com/WorldException/9ce045f61627e6fa8d59

четверг, 18 февраля 2016 г.

mysql и много insert-ов с помощью sqlalchemy, ускоряемся.

Как то потребовалось мне сделать генерацию пары десятков прайсов, да хранить их всех в одной таблице. А прайсы были большие по паре сотен тысяч записей в каждом. Да и обновлять их надо минимум раз в час.
Попытки вставки в цикле обычным insert привело к тому что 10 тыс. записей вставлялось примерно минут 15.
В общем правильный путь вставки за несколько секунд полного прайса оказался следующим.

sqlalchemy:

  1. class Price(base):
  2.     __tablename__ = 'prices'
  3.     ....
  4. s = session()
  5. buffer = []
  6. for item in items:
  7.    ...
  8.    buffer.append({'name':.., 'price':...})
  9. s.bulk_insert_mappings(Price, buffer)
  10. s.commit()

Так подсказывают делать все примеры и это правильно. Должен по идее выполниться pymysql.cursor.executemany(). Но как оказалось в последней версии pymysql есть ошибка, executemany не распознает конструкцию INSERT ... VALUES (%(name)s, %(price)s) и делает в цикле запрос на каждую запись, т.е. получаем те же тормоза.
После небольшого патча pymysql все встает на свои места.
Готовую версию можно взять так
  1. #!/bin/bash
  2. git clone https://github.com/WorldException/PyMySQL.git
  3. pip uninstall pymysql
  4. cd PyMySQL
  5. python setup.py install


Патч выглядит так
pymysql/cursor.py

-RE_INSERT_VALUES = re.compile(r"""(INSERT\s.+\sVALUES\s+)(\(\s*%s\s*(?:,\s*%s\s*)*\))(\s*(?:ON DUPLICATE.*)?)\Z""",
+RE_INSERT_VALUES = re.compile(r"""(INSERT\s.+\sVALUES\s+)(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))(\s*(?:ON DUPLICATE.*)?)\Z""",
ВНИМАНИЕ!
Мой патч приняли в основную ветку PyMySQL начиная с версии 0.7.2, так что вам достаточно обновиться!

Еще важный момент настройки MySQL который позволил мне избежать постоянных deadlock при параллельной вставке и ускорить вставку на порядок. Это разбивка таблицы на партиции по ключу, в моем случае это было имя прайса. Выглядит это примерно так: 

PARTITION BY KEY (unikey) PARTITIONS 100

А так же 

DELAY_KEY_WRITE=1