1 つのアプローチ。次のようなテーブルを作成します。
class Queries(models.Model):
site = models.CharField(max_length=200, db_index=True)
start_time = models.DateTimeField(null = True)
finished = models.BooleanField(default=False)
これは、各クエリがいつ実行されたか、または制限によってクエリがすぐに実行されない場合は今後実行されるかを記録します。start_time はアクションの開始時刻です。アクションが現在ブロックされている場合、これは将来のことです。
1 秒あたりのクエリ数で考えるのではなく、1 クエリあたりの秒数で考えてみましょう。この場合、クエリごとに 1/3 秒です。
アクションを実行する場合は常に、次の操作を行います。
- アクションの行を作成します。q = Queries.objects.create(サイト=サイト名)
- 作成したばかりのオブジェクト (q.id) で、
start_time
このサイトの最大の start_time に 1/3 秒を加えた値にアトミックに設定します。将来の最大が 10 秒である場合、10 1/3 秒でアクションを開始できます。その時間が過去の場合は、now() にクランプします。
- 設定した start_time が未来の場合は、その時刻までスリープします。あまりにも先の場合 (例: 15 秒以上)、行を削除してエラーを出します。
- クエリが終了したら、finished を True に設定して、後で行を削除できるようにします。
アトミックアクションが重要です。競合するため、単純にクエリを集計して保存することはできません。Django がこれをネイティブに実行できるかどうかはわかりませんが、生の SQL では簡単です。
UPDATE site_queries
SET start_time = MAX(now(), COALESCE(now(), (
SELECT MAX(start_time) + 1.0/3 FROM site_queries WHERE site = site_name
)))
WHERE id = object_id
次に、モデルをリロードし、必要に応じてスリープします。また、古い行をパージする必要があります。Queries.objects.filter(site=site, finished=True).exclude(id=id).delete() のようなものはおそらく機能します:作成したばかりのものを除くすべての完成したクエリを削除します。(そうすれば、後のクエリをスケジュールする必要があるため、最新のクエリを削除することはありません。)
最後に、UPDATE がトランザクションで発生しないことを確認してください。これを機能させるには、自動コミットをオンにする必要があります。それ以外の場合、UPDATE はアトミックではありません。2 つのリクエストが同時に UPDATE し、同じ結果を受け取る可能性があります。通常、Django と Python は自動コミットがオフになっているため、オンにしてからオフに戻す必要があります。Postgres では、これは connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) および ISOLATION_LEVEL_READ_COMMITTED です。MySQLでこれを行う方法がわかりません。
(私は、Python の DB-API で自動コミットをオフにするというデフォルトは、重大な設計上の欠陥であると考えています。)
このアプローチの利点は、状態が単純で非常に単純であることです。イベントリスナーやウェイクアップなど、独自の問題があるものは必要ありません。
考えられる問題は、遅延中にユーザーが要求をキャンセルした場合、アクションを実行するかどうかに関係なく、遅延が引き続き適用されることです。アクションを開始しないと、他のリクエストは未使用の「タイムスロット」に移動しません。
自動コミットを機能させることができない場合の回避策は、UNIQUE 制約を (site, start_time) に追加することです。(Django はそれを直接理解しているとは思わないので、自分で制約を追加する必要があります。) 次に、競合が発生し、同じサイトへの 2 つの要求が同時に終了した場合、そのうちの 1 つが制約をスローします。キャッチできる例外であり、再試行できます。生の SQL の代わりに、通常の Django 集計を使用することもできます。ただし、制約例外のキャッチはそれほど堅牢ではありません。