Как redb хранит сложные объекты: не бесхемная свалка, а RTTI
Про redb (RedBase) есть два зеркальных заблуждения. Первое: раз объект пишется «как документ», внутри лежит сериализованный JSON или блоб. Второе, противоположное: раз всё падает в общую таблицу значений — значит это плоский мешок пар «ключ-значение» без схемы.
Оба мимо. То, что внутри — это RTTI, полноценная система типов, живущая в самой базе. И устроена она заметно сложнее, чем и блоб, и «атрибут-значение». Разбор архитектуры я подробно давал в отдельной статье на Хабре: «redb: реляционное хранилище объектов» — ниже сжатая суть.
Слой типов: база знает настоящий тип каждого поля
redb хранит не только данные, но и их описание типов — три связанных уровня:
types— реальные дескрипторы типов (db_typeи соответствие .NET-типу);_schemes— сами типы (классы), с поддержкой наследования через self-reference;structures— типизированные поля схемы: имя (name), тип (_id_type, FK наtypes), признак коллекции (collection_type) и вложенность (_id_parent).
Значения в values всегда привязаны к конкретной структуре (id_structure, FK на structures). Поэтому строка values — это не безымянная пара «атрибут-значение»: это значение известного, именованного, типизированного, возможно вложенного поля известного класса. База в рантайме знает, что перед ней — decimal Salary в схеме Employee, а не абстрактный «атрибут №42». Это и есть RTTI.
Хранение коллекций: построчно, реляционно
Вложенные массивы, словари и глубокие иерархии redb раскладывает в _values построчно, а не строкой:
Никаких JSON-блобов на диске. Каждый элемент коллекции — отдельная строка с типизированными колонками (
_value_long,valuestring,valuedatetime,valueguid, …), внешними ключами и обычными индексами.Связь и порядок — реляционные. Вложенность собирается self-reference колонкой
arrayparent_id(FK наvalues.id,ON DELETE CASCADE). Порядок массива и ключи словаря — вarrayindex(text:'0','1','2'для массивов, строковый ключ — для словарей).Один элемент — одна строка.
List<OrderItem>внутри класса не превращается ни в JSON-поле, ни в десяток физических таблиц, которые вы заводите руками.
Что это даёт на практике
Честный LINQ на уровне СУБД. Данные лежат в типизированных колонках, а метаданные структур позволяют движку собрать нативный SQL:
Where/OrderBy/GroupBy/ оконные функции идут по реальным индексам базы, а не перебором JSON в памяти бэкенда.Загрузка за один запрос без каскада JOIN-ов. Чтобы поднять объект со всей глубиной вложенности (пусть там 20–30 списков), не нужен каскад
JOIN, как у EF с.Include(). Плоская структура забирается из_valuesодним запросом и собирается в объект в памяти.Точечный Change Tracking (Pro). При сохранении Pro-версия строит деревья
ValueTreeNode(память против БД), сравнивает их (ValueTreeBuilder/ValueTreeDiff) и шлётUPDATEтолько по изменившимся узлам — граф целиком не перезаписывается.
Итог: redb совмещает удобство работы с объектами «как с документами» и фундамент реляционной СУБД — типизацию, индексы, FK и запросы, которые исполняет база, а не бэкенд. Ключ к этому — не блоб и не плоский мешок атрибутов, а persisted-RTTI: types → schemes → structures → values.
Ссылки
Разбор архитектуры на Хабре: Локальная база на клиенте · redb — типизированное хранилище
Архитектура: redbase.app/architecture · Зачем redb: redbase.app/why-redbase