Skip to content

Тип записи сущности регистрируется один раз на тип сущности - #165

Open
Segate-ekb wants to merge 2 commits into
nixel2007:masterfrom
Segate-ekb:feature/active-record-type-once
Open

Segate-ekb wants to merge 2 commits into
nixel2007:masterfrom
Segate-ekb:feature/active-record-type-once

Conversation

@Segate-ekb

@Segate-ekb Segate-ekb commented Oct 21, 2026 •

Copy link
Copy Markdown

Проблема

АктивнаяЗапись.Создать строила декоратор для каждой записи: ПостроительДекоратора(...).Построить() генерирует исходник и компилирует его через ЗагрузитьСценарийИзСтроки на каждую строку, прочитанную из базы или созданную через СоздатьЭлемент. Каждая запись несла собственный скомпилированный сценарий — это и время, и память: на hub.oscript.io пул сущностей со скомпилированными декораторами держал сотни мегабайт, которые не отпускались.

Что изменилось

  • Тип записи регистрируется в системе типов один раз на тип сущности под именем ЗаписьСущности_<ТипСущности> (ПостроительДекоратора.ЗарегистрироватьВСистемеТипов). Регистрация идёт под блокировкой СинхронизированнаяКарта.ВычислитьЕслиОтсутствует, поэтому в многопоточном приложении тип регистрируется ровно один раз на процесс.
  • Запись создаётся через Новый ИмяТипа; хранилище и модель данных привязываются к экземпляру через Рефлектор, а не к типу, поэтому записи разных менеджеров одного типа сущности по-прежнему сохраняются каждая в свой источник.
  • АктивнаяЗапись.ТипСущности определяет декоратор через ОбработкаДекоратора.ЭтоДекоратор, а не по ТипЗнч = Сценарий.
  • Новый набор tests/АктивнаяЗапись.os (на КоннекторInMemory): у записей одного типа сущности один тип; Сохранить/Прочитать/Удалить через запись; записи разных менеджеров пишут в свои источники; Сохранить/Удалить записи через менеджер.
  • docs/АктивнаяЗапись.md: описание типа записи, имя метода чтения приведено к фактическому (Прочитать).

Совместимость

  • ТипЗнч(Запись) теперь возвращает Тип("ЗаписьСущности_<ТипСущности>"), а не Тип("Сценарий"). Исходный тип сущности, как и раньше, даёт АктивнаяЗапись.ТипСущности(Запись) или ОбработкаДекоратора.ИсходныйТип(Запись).
  • Публичный интерфейс записи (Сохранить, Прочитать, Удалить), пул сущностей и тождество записей в пуле не менялись.

Замеры: было / стало

Библиотека BenchmarkOneScript v0.9.1, OneScript v2.2.1, macOS, Apple M5 Pro. КоннекторInMemory, сущность с четырьмя полями, 1000 строк; 5 итераций, 1 прогревочная, 1 вызов за итерацию, &МониторингПамяти.

Кейс (1000 строк) Было: Mean Было: Allocated Стало: Mean Стало: Allocated
ПервоеЧтениеВПул — Закрыть() хранилища, затем Получить(Тип) 531.62 ms 712.37 MB 121.02 ms 116.29 MB
ПовторноеЧтениеИзПула — Получить(Тип) по заполненному пулу 27.13 ms 42.71 MB 31.63 ms 42.71 MB
СозданиеЗаписей — 1000 × СоздатьЭлемент(Тип) 377.65 ms 661.65 MB 32.87 ms 65.77 MB

Полные отчёты библиотеки:

Было (master, dd5d20e)
| Method                | Строк | Baseline |      Mean |      StdErr |      StdDev | Ratio | RatioSD |   Op/s | Allocated |
|-----------------------|------:|----------|----------:|------------:|------------:|------:|--------:|-------:|----------:|
| ПервоеЧтениеВПул      | 1,000 | Yes      | 531.62 ms | 18,004.7 us | 40,259.7 us |  1.00 |    0.00 |  1.881 | 712.37 MB |
| ПовторноеЧтениеИзПула | 1,000 | No       |  27.13 ms |    167.7 us |    375.0 us |  0.05 |    0.00 | 36.864 |  42.71 MB |
| СозданиеЗаписей       | 1,000 | No       | 377.65 ms |  1,859.2 us |  4,157.4 us |  0.71 |    0.05 |  2.648 | 661.65 MB |
Стало (эта ветка)
| Method                | Строк | Baseline |      Mean |      StdErr |      StdDev | Ratio | RatioSD |   Op/s | Allocated |
|-----------------------|------:|----------|----------:|------------:|------------:|------:|--------:|-------:|----------:|
| ПервоеЧтениеВПул      | 1,000 | Yes      | 121.02 ms | 13,292.6 us | 29,723.2 us |  1.00 |    0.00 |  8.263 | 116.29 MB |
| ПовторноеЧтениеИзПула | 1,000 | No       |  31.63 ms |  1,027.2 us |  2,297.0 us |  0.27 |    0.06 | 31.616 |  42.71 MB |
| СозданиеЗаписей       | 1,000 | No       |  32.87 ms |    360.4 us |    805.8 us |  0.28 |    0.06 | 30.423 |  65.77 MB |

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

Исходник бенчмарка
// Бенчмарк entity: чтение строк в пул и создание записей.
#Использовать "/Users/e.ivanov/git_reps/public/entity-type-once/src"

&Параметры(1000)
Перем Строк Экспорт;

Перем Источник;
Перем Менеджер;
Перем Хранилище;

&МониторингПамяти
&КоличествоИтераций(5)
&КоличествоПрогревочныхИтераций(1)
&КоличествоВызововЗаИтерацию(1)
&ЭкспортMarkdown
&КаталогАртефактов("/private/tmp/claude-501/-Users-e-ivanov-git-reps-public-oscript-openhub/265ccc9c-7c1b-4310-b826-60510d31d3b8/scratchpad/spike-entity/bench/артефакты")
Процедура ПриСозданииОбъекта()
КонецПроцедуры

&ПередКаждымКейсом
Процедура ПодготовитьБазу(Контекст) Экспорт

	Если Менеджер <> Неопределено Тогда
		Возврат;
	КонецЕсли;

	ПодключитьСценарий("/private/tmp/claude-501/-Users-e-ivanov-git-reps-public-oscript-openhub/265ccc9c-7c1b-4310-b826-60510d31d3b8/scratchpad/spike-entity/bench/ПакетЗамера.os", "ПакетЗамера");

	Источник = Новый ИсточникДанных("Замер", Тип("КоннекторInMemory"), "замер");
	Источник.УстановитьАвтоЗакрытие(Ложь);
	Менеджер = Новый МенеджерСущностей(Источник);
	Менеджер.ДобавитьКлассВМодель(Тип("ПакетЗамера"));
	Менеджер.Инициализировать();
	Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("ПакетЗамера"));

	Для Н = 1 По Строк Цикл
		Пакет = Хранилище.СоздатьЭлемент();
		Пакет.Имя = "пакет-" + Н;
		Пакет.Описание = "Описание пакета номер " + Н + " для замера веса строки в пуле сущностей";
		Пакет.Скачиваний = Н * 7;
		Пакет.Сохранить();
	КонецЦикла;
	Хранилище.Закрыть();

КонецПроцедуры

// Первое чтение строк после очистки пула: сборка объекта каждой строки плюс чтение из базы.
&Бенчмарк
&Эталон
Процедура ПервоеЧтениеВПул() Экспорт
	Хранилище.Закрыть();
	Хранилище.Получить();
КонецПроцедуры

// Повторное чтение тех же строк: объекты берутся из пула, колонки перечитываются.
&Бенчмарк
Процедура ПовторноеЧтениеИзПула() Экспорт
	Хранилище.Получить();
КонецПроцедуры

// Чистое создание записей без базы.
&Бенчмарк
Процедура СозданиеЗаписей() Экспорт
	Для Н = 1 По Строк Цикл
		Хранилище.СоздатьЭлемент();
	КонецЦикла;
КонецПроцедуры

Сущность замера: Идентификатор (&Идентификатор, &ГенерируемоеЗначение, Целое), Имя, Описание, Скачиваний (Целое), таблица ПакетыЗамера. Запуск: oscript oscript_modules/benchmark/src/BenchmarkOneScript/cmd/main.os run --memory ЧтениеСущностей.os.

Проверка

Локально зелёные tests/АктивнаяЗапись.os, tests/КоннекторInMemory.os, tests/КоннекторJSON.os, tests/ОбъектМодели.os. Наборы на SQLite и PostgreSQL локально не запускались (arm64 macOS без SQLite.Interop), их проверяет CI.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Новые возможности

    • Добавлена поддержка активных записей сущностей с единым типом для каждого типа сущности.
    • Активные записи можно создавать, читать, сохранять, изменять и удалять.
    • Записи сохраняют связь с создавшим их менеджером, хранилищем и моделью данных.
    • Добавлена возможность определить исходный тип сущности по активной записи.
  • Документация

    • Обновлено описание жизненного цикла активных записей и операции чтения.
  • Тесты

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

@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8a4e093f-70da-494a-8edc-e7475f358156
📥 Commits

Reviewing files that changed from the base of the PR and between 92a3847 and 2866e5c.

📒 Files selected for processing: 1
  • src/internal/Модули/АктивнаяЗапись.os
💤 Files with no reviewable changes: 1
  • src/internal/Модули/АктивнаяЗапись.os

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

Изменения регистрируют общий тип активной записи для каждого типа сущности. Экземпляры получают хранилище и модель от менеджера. Тесты проверяют регистрацию, операции с записями и изоляцию между менеджерами. Документация уточняет тип записи и имя метода чтения.

Changes

Активные записи

Layer / File(s) Summary
Определение и регистрация типа записи
src/internal/Модули/АктивнаяЗапись.os, docs/АктивнаяЗапись.md
ТипСущности обрабатывает декораторы и возвращает исходный тип. Для типа сущности регистрируется общий тип записи с полями и методами Прочитать, Сохранить, Удалить. Имя типа сохраняется в синхронизированной карте. Документация описывает это поведение.
Создание записей и проверка операций
src/internal/Модули/АктивнаяЗапись.os, tests/АктивнаяЗапись.os, docs/АктивнаяЗапись.md
Создать создаёт экземпляр зарегистрированного типа и устанавливает хранилище и модель через рефлектор. Тесты проверяют общий тип записей, сохранение, чтение, изменение, удаление и привязку к менеджеру. Раздел Получить переименован в Прочитать.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Suggested reviewers: nixel2007, nivanchenko

Merge Risk: ⚪ Minimal · up to 2866e

The active-record changes appear mergeable after normal checks; no concrete issue requiring a pre-merge fix was established.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed Заголовок точно описывает основное изменение: тип записи сущности регистрируется один раз для каждого типа сущности.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Кролик тип записи связал,
Хранилище к нему привязал.
Запись читает и хранит,
Потом себя удалит.
Тесты порядок подтвердят,
И менеджеры не смешат.

Comment @coderabbitai help to get the list of available commands.

Comment thread src/internal/Модули/АктивнаяЗапись.os Outdated

Возврат Декоратор;
// Проверка и регистрация под одной блокировкой: тип регистрируется ровно один раз на процесс.
Возврат ТипыЗаписей.ВычислитьЕслиОтсутствует(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Читать под блокировкой на горячем пути очень дорого. Надо менять на CoW-карта здесь подойдёт лучше.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants