Ускорить объемную вставку с помощью Django ORM?

Я планирую загрузить миллиард записей, взятых из ~ 750 файлов (каждый ~ 250 МБ) в базу данных, используя django ORM. В настоящее время для обработки каждого файла требуется ~ 20 минут, и мне было интересно, есть ли способ ускорить этот процесс.

Я принял следующие меры:

Что еще я могу сделать, чтобы ускорить процесс? Вот некоторые из моих мыслей:

Любые указатели относительно этих предметов или любые другие идеи будут приветствоваться :)

Ответ 2

Это не относится к Django ORM, но в последнее время мне приходилось массировать вставки > 60 миллионов строк из 8 столбцов данных из более чем 2000 файлов в базу данных sqlite3. И я узнал, что следующие три вещи сократили время вставки с 48 часов до ~ 1 часа:

  • увеличить размер кеш-памяти вашей БД, чтобы использовать больше ОЗУ (по умолчанию они всегда очень маленький, я использовал 3 ГБ); в sqlite это делается PRAGMA cache_size = n_of_pages;

  • записывать в RAM вместо диска (это вызывает незначительные проблема, если система выходит из строя, но что-то, что я считаю пренебрежимым если у вас уже есть исходные данные на диске); в sqlite это делается PRAGMA journal_mode = MEMORY

  • последний и, возможно, самый важный: не создавайте индекс, пока вставка. Это также означает, что нельзя объявлять UNIQUE или другие ограничения, которые могут привести к созданию индекса DB. Создайте индекс только после того, как вы закончите вставку.

Как упоминалось ранее, вы также должны использовать cursor.executemany() (или просто ярлык conn.executemany()). Чтобы использовать его, выполните:

cursor.executemany('INSERT INTO mytable (field1, field2, field3) VALUES (?, ?, ?)', iterable_data)

Итерируемые_данные могут быть списком или чем-то похожим, или даже читателем открытого файла.

Ответ 4

Я провел несколько тестов на Django 1.10/Postgresql 9.4/Pandas 0.19.0 и получил следующие тайминги:

  • Вставьте 3000 строк по отдельности и получите идентификаторы из заполненных объектов, используя Django ORM: 3200ms
  • Вставьте 3000 строк с помощью Pandas DataFrame.to_sql() и не получите идентификаторы: 774ms
  • Вставьте 3000 строк с помощью менеджера Django .bulk_create(Model(**df.to_records())) и не получите идентификаторы: 574ms
  • Вставьте 3000 строк с to_csv в буфер StringIO и COPY (cur.copy_from()) и не получите идентификаторы: 118ms
  • Вставьте 3000 строк с помощью to_csv и COPY и получите идентификаторы с помощью простого SELECT WHERE ID > [max ID before insert] (возможно, не является поточно-ориентированным, если COPY удерживает блокировку таблицы, предотвращая одновременные вставки?): 201 мс
def bulk_to_sql(df, columns, model_cls):
    """ Inserting 3000 takes 774ms avg """
    engine = ExcelImportProcessor._get_sqlalchemy_engine()
    df[columns].to_sql(model_cls._meta.db_table, con=engine, if_exists='append', index=False)


def bulk_via_csv(df, columns, model_cls):
    """ Inserting 3000 takes 118ms avg """
    engine = ExcelImportProcessor._get_sqlalchemy_engine()
    connection = engine.raw_connection()
    cursor = connection.cursor()
    output = StringIO()
    df[columns].to_csv(output, sep='\t', header=False, index=False)
    output.seek(0)
    contents = output.getvalue()
    cur = connection.cursor()
    cur.copy_from(output, model_cls._meta.db_table, null="", columns=columns)
    connection.commit()
    cur.close()

Все показатели производительности были получены для таблицы, уже содержащей 3000 строк, работающих на OS X (i7 SSD 16 ГБ), в среднем из десяти запусков с использованием timeit.

Я возвращаю свои вставленные первичные ключи, назначая идентификатор пакета импорта и сортируя по первичному ключу, хотя я не на 100% уверен, что первичные ключи всегда будут назначаться в том порядке, в котором строки сериализуются для команды COPY - буду признателен за мнения в любом случае,

Ответ 5

Также есть фрагмент объемной вставки http://djangosnippets.org/snippets/446/.

Это дает одну команду вставки нескольких пар значений (INSERT INTO x (val1, val2) VALUES (1,2), (3,4) --etc и т.д.). Это должно значительно повысить производительность.

Он также сильно документирован, что всегда является плюсом.

Ответ 6

Кроме того, если вам нужно что-то быстрое и простое, вы можете попробовать следующее: http://djangosnippets.org/snippets/2362/. Это простой менеджер, который я использовал в проекте.

Другой фрагмент не был таким простым и был действительно сосредоточен на объемных вставках для отношений. Это просто простая вставка и просто использует тот же запрос INSERT.