· 2 мин чтения
Токен-бакет на целых числах: что проверяют property-тесты
Как rate-gate считает токен-бакет без float, зачем милли-токены и какие три свойства доказывают property-тесты proptest.
Ограничитель частоты выглядит простым, пока не доходит до округлений. Если хранить токены в float, ошибка накапливается, а два экземпляра алгоритма на одних данных могут разойтись. В rate-gate (Rust, axum) токены лежат в милли-токенах: целое число, где 1000 равно одному токену.
Что считается
Бакет хранит число милли-токенов и момент последнего пополнения. При проверке пополнение равно elapsed * refill, ограничивается ёмкостью, затем списывается cost (он может быть больше 1). Если токенов не хватает, сервис отвечает allowed: false и считает retry_after_ms, то есть сколько ждать до нужного числа токенов, и reset_ms, когда бакет наполнится.
Время передаётся в чистые функции параметром, поэтому в тестах его можно «прокручивать» без сна.
Три свойства
Property-тесты (proptest) проверяют не примеры, а законы:
- за любой период бакет выдаёт не больше
capacity + refill * elapsed; - залп без хода времени не превышает ёмкость;
- оценка скользящего окна никогда не превышает лимит.
В README нет истории о найденной ошибке, и я её не выдумываю: смысл свойств в том, что они ловят класс ошибок округления и переполнения, а не одну конкретную.
Что это не гарантирует
- Состояние в памяти одного экземпляра: реплики его не делят.
- Скользящее окно приближённое: взвешенная сумма текущего и предыдущего окна, оценка округляется вверх, то есть консервативно.
- Снапшот раз в 30 секунд не транзакционный: при аварии лимиты станут чуть мягче.