Повторный анализ кодовой базы (docs/reviews/2026-09-26-codebase-review-2.md) и доработки:
025 Ёмкость префикса — размер его подсети (а не сумма листьев); «Обзор» считает ёмкость
по корневым активным IPv4-префиксам и адреса внутри них.
026 Политика блокировки входа: 5 неудач на логин+IP, 20 на IP, 50 на логин со всех IP,
кроме известных IP (known_logins, миграция 0009) — владельца нельзя заблокировать анонимно.
027 Сериализация попыток входа по IP (advisory-lock после блокировки логина).
028 UI «Префиксы»: загрузка всех страниц (до 20 000), счётчики по total, предупреждение об усечении.
029 Advisory-lock по VRF для операций, меняющих дерево префиксов и раскладку адресов.
030 Исправление замечаний ревью 025-029: _lock_prefix (VRF блокируется до чтения префикса,
409 при одновременном переносе), константы политики входа перенесены в app/services.py.
Тесты: 14 passed (проверка ёмкости родителя приведена к семантике 025); сквозные сценарии
и гонки — docs/reviews/2026-09-26-changes-025-029-review.md, 2026-09-27-changes-030-review.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.1 KiB
5.1 KiB
Итог: целостность дерева префиксов при параллельных изменениях (изменение 029)
Что сделано
app/api/v1/prefixes.py: константаVRF_LOCK_NS = 7033и хелпер_lock_vrf(db, *vrf_ids)—pg_advisory_xact_lock(VRF_LOCK_NS, vrf_id)для каждого VRF, отсортированных по возрастанию id (исключает взаимную блокировку при операциях над несколькими VRF).- Вызов добавлен в начале каждой операции, меняющей дерево или раскладку адресов, до любых чтений дерева:
create_prefix— поbody.vrf_id, первой строкой обработчика;allocate_subnet— сначала обычныйSELECT vrf_idпрефикса-родителя (без блокировки строки), затем_lock_vrf, затем уже существующийSELECT … FOR UPDATE;update_prefixпри смене VRF — по исходному и целевому VRF, до вызова_move_to_vrf;delete_prefix— по VRF префикса, до переподвешивания детей;create_address— по VRF префикса, до проверки «самый узкий префикс»;allocate_next— обычныйSELECT vrf_id, затем_lock_vrf, затем существующийSELECT … FOR UPDATEпо префиксу.
- Чтения (
GET) не блокируются; операции над разными VRF друг друга не блокируют (независимые ключи advisory-lock). README.md, раздел «Модель данных»: одно предложение о сериализации изменений дерева одного VRF.
Отклонения от плана
Нет.
Как проверено
venv/bin/python -c 'import app.main'— без ошибок.- Стенд пересобран,
healthy. - Сценарий из плана (20 повторов, временная организация
rv3-tree-test, скрипт в scratchpad): в каждой итерации — свой/24-родитель, и параллельно (два потока) — явное создание.0/29и автовыделение/30из родителя (POST …/subnets/next {length:30}). Оба запроса каждый раз завершились без ошибок (0 ошибок из 40 запросов). Итоговое состояние дерева (отдельныйGETпосле завершения обоих запросов пары — не тело ответа гонки, оно отражает лишь момент собственного commit) проверено на всех 20 итерациях: во всех случаях, когда выделенный/30оказался внутри.0/29, егоparent_idравен id.0/29, а не.0/24. 0 из 20 неверныхparent_id. PASS.- Первый прогон теста дал 5 ложных срабатываний из-за ошибки самого теста (сверка велась по телу HTTP-ответа каждого из двух конкурентных запросов, которое отражает
состояние дерева на момент commit именно этого запроса, а не финальное состояние после обоих); после исправления теста (повторный
GETуже обоих префиксов после завершения гонки) все 20 итераций прошли успешно, а прямая проверка в БД (SELECT … FROM prefixes) подтвердила, что для всех 5 «проблемных» по первому прогону итераций конечное состояние в базе уже было верным (гонка не оставляла ошибочных данных, ошибался только клиентский тест).
- Первый прогон теста дал 5 ложных срабатываний из-за ошибки самого теста (сверка велась по телу HTTP-ответа каждого из двух конкурентных запросов, которое отражает
состояние дерева на момент commit именно этого запроса, а не финальное состояние после обоих); после исправления теста (повторный
- Временная организация и все её префиксы удалены по завершении.
Не проверено
- Гонка при
update_prefix(смена VRF, две блокировки) и при одновременномcreate_address/allocate_nextв одном VRF — проверялась только комбинацияcreate_prefix+allocate_subnet, явно описанная в плане; остальные точки вызова_lock_vrfпроверены только чтением кода. - Поведение под большей степенью параллелизма (более двух одновременных запросов) и при конкурентных операциях над разными VRF (что они не блокируют друг друга) — не измерялось.