Skip to content

Кэшировать собранную спецификацию: сейчас документ строится заново на каждый запрос #133

Description

@nixel2007

Что есть сейчас

ГенераторOpenApi.СформироватьСпецификацию собирает документ целиком на каждый GET /v3/api-docs:

Функция СформироватьСпецификацию(Запрос = Неопределено) Экспорт
	Сборка = Новый СборкаСпецификации();
	...
	Спецификация.Вставить("paths", СобратьПути(Сборка, ЕстьЗащищенныеМаршруты));

Обход всех маршрутов, транслитерация имён, построение схем по рефлексии типов, сериализация. Кэша нет ни на каком уровне.

Почему это можно кэшировать

От запроса зависит ровно один разделservers, он выводится из заголовков X-Forwarded-*. Всё остальное — info, paths, tags, components — на одних и тех же маршрутах даёт побайтово одинаковый результат: это уже проверено тестом на двенадцать одновременных запросов.

Значит документ собирается один раз, а servers подставляется поверх на каждый запрос.

Инвалидация

Маршруты после старта приложения не меняются: все три места регистрации — СборщикМаршрутов, РегистраторМаршрутовOpenApi, РегистраторКаталогаФайлов — отрабатывают рогатками при ЗапуститьПриложение, до того как поднимется слушатель.

Если считать регистрацию в рантайме возможной, кэш сбрасывать в Маршрутизатор.ДобавитьМаршрут.

Чем хранить кэш

АтомарнаяСсылка из sfaqer/atomic — 0.1.1, классы АтомарноеЧисло, АтомарноеБулево, АтомарнаяСсылка.

Обычная переменная желудя тут не годится: желудь один на приложение, а запросы за спецификацией приходят одновременно. На первом запросе двое собрали бы документ и записали каждый свой — ничего не сломается, но работа делается дважды. Заводить ради этого семафор тяжелее самой задачи.

Атомарная ссылка закрывает и то, и другое: чтение свободно от блокировок, а первую сборку укладывает СравнитьИУстановить — кто успел, тот и записал, остальные берут записанное.

// АтомарнаяСсылка - собранный документ без раздела servers.
//                   Неопределено - ещё не собран.
Перем КэшДокумента;

// В СформироватьСпецификацию
Документ = КэшДокумента.Получить();
Если Документ = Неопределено Тогда
    КэшДокумента.СравнитьИУстановить(Неопределено, СобратьДокумент());
    Документ = КэшДокумента.Получить();
КонецЕсли;

Раздел servers подставляется поверх копии на каждый запрос: сам кэш остаётся неизменяемым, иначе параллельные запросы затирали бы друг другу адрес.

Сброс, если решим считать регистрацию в рантайме возможной, — Установить(Неопределено) в Маршрутизатор.ДобавитьМаршрут, тоже без семафора.

Зависимость новая: atomic в packagedef пока нет.

Что это заодно снимает

Вопрос о стоимости обходов в генераторе. СодержитПараметр и ИмеетТелоЗапроса проходят Действие.Параметры циклом с ВРег — около восьми раз на точку маршрута. Сейчас это повторяется на каждый запрос за спецификацией, при кэшировании отработает один раз за жизнь приложения, и оптимизировать там будет нечего.

Откуда взялось

Предлагал CodeRabbit нитпиком в #124. Я отклонил с обоснованием «документ зависит от запроса» — формально верно, по существу нет: зависит только servers.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions