DjangoのORMを使用して、約750個のファイル(それぞれ約250MB)から取得した10億件のレコードをdbにアップロードする予定です。現在、各ファイルの処理には約20分かかりますが、この処理を高速化する方法があるかどうか疑問に思っていました。
次の措置を講じました。
物事をスピードアップするために他に何ができますか?ここに私の考えのいくつかがあります:
これらのアイテムまたは他のアイデアに関するポインタは歓迎です:)
Django 1.4は、QuerySetオブジェクトでbulk_create()
メソッドを提供します。以下を参照してください。
これは、Django ORMに固有のものではありませんが、最近、2000以上のファイルから8カラムのデータで6,000万行以上をsqlite3データベースに一括挿入しなければなりませんでした。挿入時間を48時間以上から約1時間に短縮しました。
dBのキャッシュサイズ設定を増やして、より多くのRAM(デフォルトは常に非常に小さく、3GBを使用しました); 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)
Iterable_dataには、リストなどを指定することも、ファイルリーダーを開くこともできます。
DB-APIにドロップ およびcursor.executemany()
を使用します。詳細については、 PEP 249 を参照してください。
Django 1.10/Postgresql 9.4/Pandas 0.19.0でいくつかのテストを実行し、次のタイミングを得ました:
DataFrame.to_sql()
で3000行を挿入し、IDを取得しない:774msdf.to_sql()
はオプションで 複数の挿入を単一のステートメントに組み合わせる -テストしていません.bulk_create(Model(**df.to_records()))
で3000行を挿入し、IDを取得しない:574msto_csv
_を使用して3000行をStringIO
バッファーおよびCOPY
(cur.copy_from()
)に挿入し、IDを取得しない:118msto_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%割り当てられるわけではありませんが、 。
http://djangosnippets.org/snippets/446/ には一括挿入スニペットもあります。
これにより、1つの挿入コマンドに複数の値のペア(INSERT INTO x(val1、val2)VALUES(1,2)、(3,4)--etcなど)が与えられます。これにより、パフォーマンスが大幅に向上するはずです。
また、文書化されているように見えますが、これは常にプラスです。
また、すばやく簡単に何かをしたい場合は、これを試すことができます: http://djangosnippets.org/snippets/2362/ 。プロジェクトで使用したシンプルなマネージャーです。
もう1つのスニペットはそれほど単純ではなく、リレーションシップの一括挿入に焦点を当てていました。これは単なる一括挿入であり、同じINSERTクエリを使用するだけです。
開発Django got bulk_create: https://docs.djangoproject.com/en/dev/ref/models/querysets/#Django.db.models.query.QuerySet.bulk_create