データベースに記録してある大量のデータを一括で処理する際に発生しがちなパターンとして以下のものがあります。
- 対象データが大量(10万件とか)のため、アプリケーション側でループを書き、1回のループ内では対象データを
LIMIT 1000 OFFSET ...
のSQLで取得する - アプリケーション側での処理で、処理対象のデータの抽出SQL の
WHERE
句の条件を、条件に当てはまらないようにUPDATE
する
このような処理を書いてしまうと、ループで全対象データを全て処理できずに漏れが発生します。
そのような現象を、 Pagination Drift (パジネーションドリフト) と呼ぶようです。
この Django プロジェクトは、パジネーションドリフトの再現と対策を書いたものです。
class Product(models.Model):
name = models.CharField(max_length=100)
active = models.BooleanField(default=True)
パジネーションドリフトの不具合が含まれています。
$ pipenv install
$ cd pagination_drift_specimen
$ ./manage.py migrate
$ ./manage.py s01_refresh_products
10000 の Product を作ります。 その内半分は、商品名に「不適切」という文言が含まれます。
$ ./manage.py s02_active_inactive_counts
active=True, Count: 10000
全商品を対象に、 active 別に件数を表示します。
$ ./manage.py s11_deactivate_inappropriate_products
...
... 商品 10000 この商品は不適切です。 を無効化しました。
$ ./manage.py s02_active_inactive_counts
active=False, Count: 3000
active=True, Count: 7000
本来、5000件ある対象商品を無効化するスクリプトですが、パジネーションドリフトが発生するため3000件しか処理できていません。
$ ./manage.py s01_refresh_products
$ ./manage.py s02_active_inactive_counts
active=True, Count: 10000
$ ./manage.py s21_preload_all_primary_keys
$ ./manage.py s02_active_inactive_counts
active=False, Count: 5000
active=True, Count: 5000