Python レート制限ライブラリlimitsの使い方

Posted by: noda

概要

Pythonで使えるレート制限ライブラリlimitsの使い方を紹介します。

limitsは汎用のレート制限ライブラリです。単位時間あたりの実行回数を制限することができます。例えば、APIの実行頻度を1分あたり500回までに抑えたい、という場合に使うことができます。人間が読みやすい形式(500/minute等)で設定を記述できるのも好印象です。

公式の情報は以下を参考にしてください。現時点(2022年2月)でのlimitsの最新バージョンは2.3.3となっています。

インストール

pipからインストールします。

pip install limits

基本的な使い方

まずは実際に使ってみます。1秒あたり1回しか実行できない関数を作成し、動作を確認します。

import limits
import time

# limitsの初期設定
storage = limits.storage.MemoryStorage()
limiter = limits.strategies.MovingWindowRateLimiter(storage)
one_per_second = limits.parse("1/second")

def limited(index):
    # limitsの利用
    if not limiter.hit(one_per_second, "namespace", "value"):
        print(f"[{index}] skip")
        return
    print(f"[{index}] worked")

limited(1)
time.sleep(0.5)
limited(2)
time.sleep(0.5)
limited(3)

実行結果はこちら。

[1] worked
[2] skip
[3] worked

limited関数を3回呼び出していますが、実行されたのは1回目と3回目のみとなります。2回目はレート制限によりスキップされました。

解説

まずはlimitsの初期設定を行います。

# limitsの初期設定
storage = limits.storage.MemoryStorage()
limiter = limits.strategies.MovingWindowRateLimiter(storage)
one_per_second = limits.parse("1/second")

limitsでは制御情報を保存するストレージとレート制限判定の戦略を選択することができます。上記の例ではメモリストレージと移動ウィンドウ戦略を選択して設定しています。

ストレージは以下のいずれかを選択できます。

  • メモリ
  • Redis
  • Memcached

ちょっとした実験ならばメモリストレージで簡単に利用できます。マルチプロセス構成やマルチインスタンス構成のプログラムならRedis等を利用することで拡張することができます。また、Redis等を利用するとlimitsの制御情報を外部から確認することも可能です。

127.0.0.1:6379> keys LIMITER/*
1) "LIMITER/namespace/value/1/1/second"

制御戦略は大きく分けて以下の2つを選択できます。

  • 固定ウィンドウ戦略
  • 移動ウィンドウ戦略

レート制限と聞いて一般的に思い浮かべる動作は移動ウィンドウ戦略だと思います。固定ウィンドウ戦略は決まった時刻に回復するリソースを扱うような場合に適しています。詳細は公式ドキュメントに譲りますが、通常はバースト耐性も高く動作を理解しやすい移動ウィンドウ戦略を利用すれば大丈夫だと思います。

limitsのパーサを利用することで可読性の高い設定を利用できます。10/minuteや1000/6hourなどのテキストによって設定することが可能です。

続いてlimitsによるレート制限を実行します。

    # limitsの利用
    if not limiter.hit(one_per_second, "namespace", "value"):
        print(f"[{index}] skip")
        return

hit()関数で実行可能かどうかを判定します。最初の引数は先ほどパースした制限条件です。それ以降の引数は制限対象を示すタグのようなもので、自由な文字列を複数指定できます。判定はこのタグごとに実行されます。タグが異なれば何度も実行することが可能になります。namespaceとしてプログラム名を、valueとしてユーザIDなどを設定すると使いやすいと思います。戻り値はbooleanです。真偽を判定して処理を分けることが可能になります。