私は非常に大きなテーブルを持っています。現在、MySQL データベースにあります。私はジャンゴを使用しています。
特定のデータを事前に計算するために、テーブルの各要素を反復処理する必要があります (おそらく、私の方が優れていれば別の方法で実行できますが、それは重要ではありません)。
メモリを一定に使用して、反復をできるだけ高速に保ちたいと思います。
*Large* Django QuerySet でのメモリ使用の制限と、大規模な Django QuerySet を反復処理すると大量のメモリが消費されるのはなぜですか? 、djangoのすべてのオブジェクトに対する単純な反復は、データベースからすべてのオブジェクトを取得するため、マシンを強制終了します。
解決に向けて
まず第一に、メモリ消費を減らすには、DEBUG が False であることを確認する必要があります (または、カーソルにモンキー パッチを適用します: settings.DEBUG? を保持しながら SQL ロギングをオフにします) connections
。
でもそれにしても、
for model in Model.objects.all()
はダメです。
わずかに改善されたフォームでさえありません:
for model in Model.objects.all().iterator()
を使用iterator()
すると、キャッシュの結果を内部に保存しないため、メモリを節約できます (必ずしも PostgreSQL である必要はありません!)。ただし、データベースからオブジェクト全体を取得するようです。
素朴な解決策
最初の質問の解決策は、カウンターに基づいて結果を a でスライスすることchunk_size
です。書き方はいくつかありますが、基本的にはすべてOFFSET + LIMIT
SQL でのクエリになります。
何かのようなもの:
qs = Model.objects.all()
counter = 0
count = qs.count()
while counter < count:
for model in qs[counter:counter+count].iterator()
yield model
counter += chunk_size
これはメモリ効率に優れていますが (一定chunk_size
のメモリ使用量は に比例します)、速度の点では非常に貧弱です: OFFSET が大きくなると、MySQL と PostgreSQL の両方 (およびおそらくほとんどの DB) が窒息し始め、速度が低下します。
より良い解決策
Thierry Schellenbach によるこの投稿で、より良い解決策を利用できます。PK でフィルタリングします。これは、オフセットよりもはるかに高速です (速度はおそらく DB に依存します)。
pk = 0
last_pk = qs.order_by('-pk')[0].pk
queryset = qs.order_by('pk')
while pk < last_pk:
for row in qs.filter(pk__gt=pk)[:chunksize]:
pk = row.pk
yield row
gc.collect()
これは満足のいくものになり始めています。現在、メモリ = O(C)、速度 ~= O(N)
「より良い」ソリューションの問題
より優れたソリューションは、PK が QuerySet で使用可能な場合にのみ機能します。残念ながら、常にそうであるとは限りません。特に、QuerySet に個別の (group_by) および/または値 (ValueQuerySet) の組み合わせが含まれている場合はそうです。
そのような状況では、「より良い解決策」は使用できません。
もっとうまくやれるでしょうか?
PK のない QuerySets に関する問題を回避して、より速く進めることができるかどうか疑問に思っています。他の回答で見つけたものを使用している可能性がありますが、純粋な SQL でのみ: using cursors .
私は生の SQL、特に Django が苦手なので、本当の質問は次のとおりです。
大きなテーブル用のより良い Django QuerySet Iterator を構築するにはどうすればよいですか
私が読んだことからの私の見解は、サーバー側のカーソルを使用する必要があるということです (明らかに (参照を参照) 標準の Django カーソルを使用しても同じ結果は得られません。これは、デフォルトで python-MySQL コネクタと psycopg コネクタの両方が結果をキャッシュするためです)。
これは本当に高速な (および/またはより効率的な) ソリューションでしょうか?
djangoで生のSQLを使用してこれを行うことはできますか? それとも、データベース コネクタに応じて特定の Python コードを記述する必要がありますか?
PostgreSQLおよびMySQLのサーバー側カーソル
とりあえず手に入るのはここまで…
ジャンゴchunked_iterator()
もちろん、このメソッドがqueryset.iterator()
ではなくとして機能し、 iterate(queryset)
django コアまたは少なくともプラグ可能なアプリの一部になるのが最善です。
更新いくつかの追加情報を含むdjangoチケットを見つけてくれたコメントの「T」に感謝します。chunked
コネクタの動作の違いにより、おそらく最善の解決策は、透過的に拡張するのではなく、特定のメソッドを作成することiterator
です (私にとっては良いアプローチのように思えます)。実装スタブは存在しますが、この1 年間は何の作業も行われておらず、作成者はまだそれに飛びつく準備ができていないようです。
追加の参照:
- MYSQL の LIMIT オフセットが高いとクエリが遅くなるのはなぜですか?
- LIMIT 句で大きなオフセットを使用して MySQL クエリを高速化するにはどうすればよいですか?
- http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
- postgresql: オフセット + 制限が非常に遅くなる
- PostgreSQL での OFFSET パフォーマンスの向上
- http://www.depesz.com/2011/05/20/pagination-with-fixed-order/
- MySQL の Python Server Side Cursor で行ごとの MySQL ResultSet を取得する方法
編集:
Django 1.6 は永続的なデータベース接続を追加しています
これにより、状況によっては、カーソルの使用が容易になります。それでも、そのようなソリューションを実装する方法は、私の現在のスキル(および学習する時間)の範囲外です..
また、「より良い解決策」はすべての状況で確実に機能するわけではなく、一般的なアプローチとして使用することはできず、ケースバイケースで適応されるスタブのみです...