-- 0009_rate_limits — счётчики брутфорс-лимитов в БД вместо памяти. -- -- Раньше лимитер был in-memory (internal/ratelimit): перезапуск API -- обнулял все счётчики, а при нескольких инстансах каждый считал своё. -- Таблица решает оба: строка переживает рестарт и общая для всех -- инстансов, подключённых к базе. -- -- Семантика — то же фиксированное окно, что и раньше: строка с ключом -- живёт window_start..window_start+window, внутри — счётчик попыток; -- инкремент и сброс окна делаются одним upsert'ом (см. ratelimit.DBLimiter). -- -- Ключ — «имя лимита|IP» (login|1.2.3.4): четыре эндпоинта с разными -- потолками живут в одной таблице, IP не обязан быть глобальным ключом. -- -- Чистка протухших строк — фоновым тикером в cmd/api (RunCleanup): -- все окна — минутные, поэтому строки старше 10 минут бесполезны. -- Без чистки таблица росла бы со скоростью уникальных IP. -- -- Применённый файл не редактировать (checksum в schema_migrations). CREATE TABLE rate_limits ( -- «имя лимита|IP»; primary key — и есть точка апсерта key text PRIMARY KEY, -- начало текущего окна; старше окна — счётчик начинается заново window_start timestamptz NOT NULL DEFAULT now(), -- попыток в текущем окне count integer NOT NULL DEFAULT 0, CHECK (count >= 0) ); -- Чистка по времени: DELETE по window_start идёт по индексу. CREATE INDEX rate_limits_window_start_idx ON rate_limits (window_start);