web-dev-qa-db-ja.com

DjangoのORMを使用して一括挿入を高速化しますか?

DjangoのORMを使用して、約750個のファイル(それぞれ約250MB)から取得した10億件のレコードをdbにアップロードする予定です。現在、各ファイルの処理には約20分かかりますが、この処理を高速化する方法があるかどうか疑問に思っていました。

次の措置を講じました。

物事をスピードアップするために他に何ができますか?ここに私の考えのいくつかがあります:

これらのアイテムまたは他のアイデアに関するポインタは歓迎です:)

43
Jonathan
33
Gary

これは、Django ORMに固有のものではありませんが、最近、2000以上のファイルから8カラムのデータで6,000万行以上をsqlite3データベースに一括挿入しなければなりませんでした。挿入時間を48時間以上から約1時間に短縮しました。

  1. dBのキャッシュサイズ設定を増やして、より多くのRAM(デフォルトは常に非常に小さく、3GBを使用しました); sqliteでは、これはPRAGMA cache_size = n_of_pages;

  2. ディスクの代わりにRAMでジャーナリングを実行します(システムに障害が発生した場合、これはわずかな問題を引き起こしますが、ディスクにソースデータがある場合は無視できると思われます); sqliteではこれが行われますPRAGMA journal_mode = MEMORYによる

  3. 最後の、そしておそらく最も重要なもの:挿入中にインデックスを構築しないでください。これは、UNIQUEまたはDBがインデックスを作成する可能性があるその他の制約を宣言しないことも意味します。挿入が完了した後にのみインデックスを作成します。

前述のように、cursor.executemany()(またはショートカットconn.executemany())も使用する必要があります。それを使用するには、次のようにします。

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

Iterable_dataには、リストなどを指定することも、ファイルリーダーを開くこともできます。

16
Yanshuai Cao

DB-APIにドロップ およびcursor.executemany()を使用します。詳細については、 PEP 249 を参照してください。

Django 1.10/Postgresql 9.4/Pandas 0.19.0でいくつかのテストを実行し、次のタイミングを得ました:

  • 3000行を個別に挿入し、Django ORM:3200msを使用して、投入されたオブジェクトからIDを取得します。
  • Pandas DataFrame.to_sql()で3000行を挿入し、IDを取得しない:774ms
  • Django manager .bulk_create(Model(**df.to_records()))で3000行を挿入し、IDを取得しない:574ms
  • _to_csv_を使用して3000行をStringIOバッファーおよびCOPYcur.copy_from())に挿入し、IDを取得しない:118ms
  • _to_csv_およびCOPYを使用して3000行を挿入し、単純な_SELECT WHERE ID > [max ID before insert]_を介してIDを取得します(COPYが同時挿入を防止するテーブルのロックを保持していない限り、おそらくスレッドセーフではありませんか?):201ms
_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()
_

パフォーマンス統計はすべて、OS X(i7 SSD 16GB)で実行されている3,000行を含むテーブルですべて取得され、timeitを使用して平均10回実行されました。

インポートバッチIDを割り当て、プライマリキーで並べ替えることにより、挿入されたプライマリキーを取得しますが、COPYコマンドで行がシリアル化される順序で常にプライマリキーが100%割り当てられるわけではありませんが、 。

7
Chris

http://djangosnippets.org/snippets/446/ には一括挿入スニペットもあります。

これにより、1つの挿入コマンドに複数の値のペア(INSERT INTO x(val1、val2)VALUES(1,2)、(3,4)--etcなど)が与えられます。これにより、パフォーマンスが大幅に向上するはずです。

また、文書化されているように見えますが、これは常にプラスです。

5
Seaux

また、すばやく簡単に何かをしたい場合は、これを試すことができます: http://djangosnippets.org/snippets/2362/ 。プロジェクトで使用したシンプルなマネージャーです。

もう1つのスニペットはそれほど単純ではなく、リレーションシップの一括挿入に焦点を当てていました。これは単なる一括挿入であり、同じINSERTクエリを使用するだけです。

3
Seaux
3
Ilia Novoselov