Open source · MIT

Toll

API-шлюз на Slim: ключи с квотами, ведро токенов, кэш ответов и размыкатель — и воронка, на которой видно, где остановился каждый запрос.

v1.0.0 Видно, куда делись запросы
Slim 4PHP 8.5PostgreSQL 18PSR-15Tailwind 4
Toll

Счётчик запросов ничего не объясняет

Панели шлюзов показывают, сколько запросов пришло. Это число не отвечает ни на один вопрос: был ли всплеск настоящим трафиком или один партнёр в цикле ретраил свою же 429; окупается ли кэш; почему upstream молчит — потому что к нему не ходят или потому что размыкатель держит дверь.

Toll построен вокруг одного вопроса: куда делись запросы? Каждая ступень конвейера сообщает, скольких она остановила и скольких пропустила, и числа сходятся сверху донизу: пришло — минус без ключа — минус квота — минус отдал кэш — минус размыкатель — дошло до чужого сервиса.

Что внутри

Шесть middleware в жёстком порядке

Маршрут, ключ, квота, кэш, размыкатель, прокси. Порядок объявлен в одном файле и он же и есть продукт: каждая ступень либо отвечает сама, либо пропускает дальше, и в ответе клиенту стоит заголовок с тем, кто именно его обслужил.

Ведро токенов под блокировкой

Пополнение и списание идут одним UPDATE ... RETURNING под блокировкой строки, а часы берутся из базы, а не из одного из серверов приложения. Залп в шестьдесят параллельных запросов бьётся ровно по ёмкости ведра, без двойных трат.

Кэш с ETag, а не с ключом

Ключ кэша считается по методу, пути и отсортированным параметрам и никогда не включает ключ API: один ответ обслуживает всех, кто спрашивает одно и то же. У кого уже есть эта версия, получает 304 и ноль байт тела.

Размыкатель с одной пробой

Когда чужой сервис падает, шлюз отвечает 503 сам и мгновенно, вместо того чтобы держать соединения до таймаута. В полуоткрытое состояние проходит ровно один пробный запрос — переход делается атомарным UPDATE, а не «кто первый успел».

Перцентили без сырого журнала

Задержки складываются в гистограмму из девяти корзин, свёрнутую по минутам одним запросом вместе с записью в журнал. Экраны читают свёртку, поэтому p95 за сутки считается одинаково быстро и на тысяче запросов, и на миллионе.

Честные отказы

Кривой ключ отсекается проверкой формы до похода в базу, тело сверх лимита не покидает шлюз, а каждый исход — включая отказы — попадает и в журнал, и в воронку. Клиент получает Retry-After и X-RateLimit-*, а не молчание.

Скриншоты

Воронка: где остановился каждый запрос
Воронка: где остановился каждый запрос
Маршруты: кэш, частота, всплеск и размер тела
Маршруты: кэш, частота, всплеск и размер тела
Ключи: квота в минуту и в сутки, остаток в ведре
Ключи: квота в минуту и в сутки, остаток в ведре
Журнал: исход каждого запроса и время самого upstream
Журнал: исход каждого запроса и время самого upstream
Фильтр по исходу: только то, что отбила квота
Фильтр по исходу: только то, что отбила квота
Тёмная тема
Тёмная тема

Установка

git clone https://github.com/dripips/toll.git cd toll docker compose up --build

Есть проект или идея?

Напишите мне, обсудим задачу.