---
doc_id: DLV-CK8S-AUDIT-NPLUS1-POST-WAVE7
type: delivery-audit
title: kodex — аудит N+1 обращений после волны 7
status: completed
owner_role: EM
created_at: 2026-05-05
updated_at: 2026-05-05
related_issues: [626]
related_prs: []
approvals:
  required: ["Owner"]
  status: approved
  request_id: "owner-2026-05-05-post-wave-7-n-plus-one-audit"
  approved_by: "ai-da-stas"
  approved_at: 2026-05-05
---

# Аудит N+1 обращений после волны 7

## TL;DR

Активный Go-код проверен на обращения к БД, внешним API, Kubernetes API, кэшу и методам репозитория внутри циклов обработки коллекций.

Очевидных N+1-путей, требующих немедленной правки перед следующей волной, не найдено. Известный риск графа членства в `access-manager` снижен: фильтр по нескольким статусам выполняется одним запросом, группы и организации кэшируются в рамках одного запроса. Полный пакетный обход слоя графа остаётся архитектурным правилом для следующих графовых сценариев и должен вводиться сразу, когда появится рост размера графа или повторное использование этого чтения другими сервисами.

## Область проверки

- `services/internal/access-manager/**`
- `services/internal/platform-event-log/**`
- `libs/go/eventlog/**`
- `libs/go/grpcserver/**`
- `libs/go/postgres/**`
- `cmd/manifest-render/**`

`deprecated/**` исключён из проверки, потому что это архив старой реализации.

## Метод

- Найдены циклы `for` в активных Go-файлах вне `deprecated/**`.
- Отдельно проверены вызовы `Query`, `QueryRow`, `Exec`, `Get*`, `List*` и методов репозитория рядом с циклами.
- Проверены сценарии графов, списков, outbox-диспетчера и общего журнала событий.
- Решения сверены с `docs/design-guidelines/go/services_design_requirements.md` и `docs/design-guidelines/go/check_list.md`.

## Результаты

| Область | Решение | Статус |
|---|---|---|
| `access-manager` — `CheckAccess` и чтение правил доступа | `resolveSubjects` собирает субъекты, а `ListAccessRules` читает правила одним запросом по списку субъектов. | принято |
| `access-manager` — граф членства | Обход графа остаётся итеративным по слою обхода, но повторное чтение по статусам устранено, а `membershipGraphLookup` кэширует группы и организации в рамках запроса. | принято как малый срез |
| `access-manager` — `ListMembershipGraph` | Операторское чтение графа использует те же ограничения и кэш; pending/blocked рёбра читаются для авторизации без расширения обхода через них. | принято |
| `access-manager` — транзакции репозитория | Цикл внутри `mutateWithOutbox` выполняет заранее собранный ограниченный набор мутаций одной команды в одной транзакции; это не обход входной коллекции с N+1. | принято |
| `access-manager` — outbox-диспетчер | События забираются пачкой через `ClaimOutboxEvents`, затем каждое событие публикуется и отмечается отдельно из-за семантики доставки и повторов. | принято |
| `platform-event-log` и `libs/go/eventlog` | `Claim` отдаёт пачку событий для одного потребителя; `Advance` и `Release` работают с контрольной точкой потребителя и не заменяются массовым обновлением без потери семантики аренды. | принято |
| `libs/go/grpcserver`, `libs/go/postgres`, `cmd/manifest-render` | Не найдено циклов с обращениями к БД, внешним API, Kubernetes API, кэшу или методам репозитория. | принято |

## Решения для следующих сервисов

- Новые чтения графов, деревьев и списков проектировать сразу как пакетный обход слоя.
- Для нескольких статусов, типов и идентификаторов передавать фильтр в репозиторий одним параметром, а не выполнять серию одинаковых запросов.
- Если пакетный контракт ещё не готов, локальный кэш запроса допустим только для малого среза и должен сопровождаться задачей на полноценную пакетную оптимизацию.
- В PR по новым сервисам явно писать, какие циклы с внешним вводом-выводом проверены и почему они не образуют N+1.

## Итог по #626

`#626` закрывается этим аудитом: активный код проверен, очевидные N+1-пути после волны 7 не обнаружены, известный графовый риск описан как допустимый малый срез с правилом для следующих реализаций.
