Build 060.27: implement Persistent Market Data Storage
This commit is contained in:
224
docs/migrations/build_060_27.md
Normal file
224
docs/migrations/build_060_27.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Build 060.27 — Persistent Market Data Storage
|
||||
|
||||
**Engineering Migration Report**
|
||||
|
||||
---
|
||||
|
||||
## Контроль документа
|
||||
|
||||
| Свойство | Значение |
|
||||
|---|---|
|
||||
| Build | 060.27 |
|
||||
| Статус | Completed |
|
||||
| Подсистема | Market Data Acquisition / Persistent Storage |
|
||||
| Компонент | Canonical Market Data Storage |
|
||||
| Дата завершения | 2026-08-01 |
|
||||
| Версия | 1.0 |
|
||||
|
||||
---
|
||||
|
||||
## Связанные документы
|
||||
|
||||
- `build_060_27_architecture.md` — архитектура, решения и подробные
|
||||
результаты подэтапов 060.27.0–060.27.8;
|
||||
- `build_060_26.md` — итог Runtime Integration and Regression;
|
||||
- `dzentra_target_architecture.md` — место Market Data Storage в целевой
|
||||
архитектуре Dzentra;
|
||||
- `master-roadmap.md` — дальнейшая последовательность Build.
|
||||
|
||||
---
|
||||
|
||||
## 1. Назначение Build
|
||||
|
||||
Build 060.27 добавил долговременное PostgreSQL-хранилище Canonical
|
||||
Market Data. Production Trade Stream теперь может сохранять Trades
|
||||
независимо от времени жизни процесса, а Quotes и Candles получили
|
||||
готовые storage-контракты и repositories без подключения существующих
|
||||
consumers.
|
||||
|
||||
Главная гарантия интеграции:
|
||||
|
||||
```text
|
||||
validated Canonical Trade
|
||||
↓
|
||||
durable PostgreSQL write
|
||||
↓
|
||||
operational checkpoint advancement
|
||||
```
|
||||
|
||||
Ошибка включённого persistent storage является фатальной. Runtime не
|
||||
продвигает checkpoint и не продолжает работу так, будто Trade сохранён.
|
||||
|
||||
---
|
||||
|
||||
## 2. Завершённые подэтапы
|
||||
|
||||
| Подэтап | Название | Статус |
|
||||
|---|---|---|
|
||||
| 060.27.0 | Storage Contract and Boundaries | Accepted |
|
||||
| 060.27.1 | PostgreSQL Schema, Migrations and Pool | Accepted |
|
||||
| 060.27.2 | Persistent Trade Repository | Accepted |
|
||||
| 060.27.3 | Quote and Candle Repositories | Accepted |
|
||||
| 060.27.4 | Storage API and Retention | Accepted |
|
||||
| 060.27.5 | Trade Runtime Persistence Integration | Accepted |
|
||||
| 060.27.6 | Bootstrap and Lifecycle Integration | Accepted |
|
||||
| 060.27.7 | Integration and Failure Verification | Accepted |
|
||||
| 060.27.8 | Final Regression and Acceptance | Accepted |
|
||||
|
||||
Каждый подэтап проходил отдельный read-only review. Findings
|
||||
исправлялись и закрывались regression-тестами до принятия.
|
||||
|
||||
---
|
||||
|
||||
## 3. Реализованная архитектура
|
||||
|
||||
### 3.1. Schema, migrations и pool
|
||||
|
||||
Создана PostgreSQL schema `market_data` с partitioned таблицами:
|
||||
|
||||
- `trades` — Canonical Trades;
|
||||
- `quotes` — неизменяемые Canonical Quote snapshots;
|
||||
- `candle_revisions` — наблюдавшиеся ревизии Candles;
|
||||
- `partition_registry` — реестр Dzentra-managed partitions.
|
||||
|
||||
Семь упорядоченных миграций применяются в одной транзакции под
|
||||
transaction-level advisory lock. История хранится в
|
||||
`public.storage_schema_migrations`; неизвестная версия или другое имя
|
||||
миграции являются фатальным schema mismatch.
|
||||
|
||||
`PostgresConnectionPool` не выполняет I/O в конструкторе. Pool явно
|
||||
открывается Application lifecycle перед Runtime и закрывается только
|
||||
после полной остановки Trade Runtime.
|
||||
|
||||
### 3.2. Canonical repositories
|
||||
|
||||
Repository-контракты различают три результата:
|
||||
|
||||
- `INSERTED` — сохранено новое событие;
|
||||
- `DUPLICATE` — идентичное событие уже существует;
|
||||
- `PROVENANCE_UPDATED` — Canonical payload совпал, добавлен новый путь
|
||||
наблюдения.
|
||||
|
||||
Trade identity задаётся `venue + symbol + trade_id + executed_at`.
|
||||
Время защищает историю от коллизии после полного signed 32-bit rollover
|
||||
Trade ID. Разный transport source не создаёт Canonical conflict, а
|
||||
различие цены, количества, времени или стороны создаёт явную ошибку.
|
||||
|
||||
Quote identity использует `venue + symbol + received_at`. Candle
|
||||
identity использует `venue + symbol + interval + open_time +
|
||||
observed_at`, поэтому промежуточные и финальные ревизии не
|
||||
перезаписываются скрытно.
|
||||
|
||||
Одиночные и пакетные операции транзакционны. Конфликт или database
|
||||
error откатывает всю пакетную запись.
|
||||
|
||||
### 3.3. Partitions и retention
|
||||
|
||||
Каждая основная таблица имеет default partition. Управляемый partition
|
||||
manager под общим advisory lock:
|
||||
|
||||
1. проверяет фактическое состояние PostgreSQL и registry;
|
||||
2. создаёт месячную таблицу;
|
||||
3. переносит подходящие default rows;
|
||||
4. подключает partition в той же транзакции.
|
||||
|
||||
Retention по умолчанию выключен. Явный запуск удаляет только
|
||||
зарегистрированные истёкшие partitions и строки раньше точного UTC
|
||||
cutoff. Очистка всех выбранных типов выполняется атомарно.
|
||||
|
||||
### 3.4. Runtime и Bootstrap
|
||||
|
||||
Live и Recovery используют один `TradeObservationSinkProtocol` через
|
||||
общий `TradeStreamConsistencyController`. Единственный владелец записи
|
||||
обеспечивает одинаковую последовательность для обоих путей данных.
|
||||
|
||||
Синхронная PostgreSQL-запись выполняется вне asyncio event loop через
|
||||
принадлежащую Runtime задачу. Cancellation ожидает уже начатую запись и
|
||||
не оставляет фонового worker.
|
||||
|
||||
Storage включается только явным `MARKET_DATA_STORAGE_ENABLED=true` и
|
||||
требует включённый Trade Stream. При выключенном флаге зависимости не
|
||||
строятся, pool не открывается, migrations не запускаются.
|
||||
|
||||
---
|
||||
|
||||
## 4. Real PostgreSQL verification
|
||||
|
||||
Безопасный opt-in harness требует одновременно:
|
||||
|
||||
- `DZENTRA_RUN_POSTGRES_TESTS=1`;
|
||||
- отдельный `DZENTRA_TEST_POSTGRES_DSN`;
|
||||
- явный localhost, loopback address или local socket;
|
||||
- имя базы с префиксом `dzentra_test_`.
|
||||
|
||||
PostgreSQL service, неявный endpoint и смешанный local/remote fallback
|
||||
запрещены. Перед destructive cleanup harness повторно проверяет точное
|
||||
имя фактической базы и специальное имя контрольного соединения.
|
||||
|
||||
Настоящий PostgreSQL подтвердил:
|
||||
|
||||
- атомарность migrations и repositories;
|
||||
- участие обеих concurrent migration/partition caller-сессий;
|
||||
- insert, duplicate, provenance и conflict paths;
|
||||
- перенос default rows и точность retention cutoff;
|
||||
- Runtime persistence через Live и reconnect/recovery;
|
||||
- fatal propagation database failure;
|
||||
- правильное закрытие pool и отсутствие оставшихся соединений.
|
||||
|
||||
---
|
||||
|
||||
## 5. Финальные результаты
|
||||
|
||||
Итоговая приёмка выполнена 2026-08-01:
|
||||
|
||||
```text
|
||||
Targeted Storage/Runtime unit suite: 307 passed
|
||||
PostgreSQL control run: 15 passed
|
||||
PostgreSQL repeated ten times: 150 passed
|
||||
Combined integration suite: 26 passed
|
||||
Fixed stress target: 3 passed, 1 deselected
|
||||
PostgreSQL suite without opt-in: 15 skipped
|
||||
Full offline regression: 2119 passed, 31 deselected
|
||||
git diff --check: clean
|
||||
Untracked files diff check: clean
|
||||
```
|
||||
|
||||
В integration и stress наборах `ResourceWarning` считался ошибкой.
|
||||
Одноразовый PostgreSQL 16 работал на `tmpfs` и после приёмки был
|
||||
остановлен и удалён. Итоговый read-only review не обнаружил открытых
|
||||
findings.
|
||||
|
||||
---
|
||||
|
||||
## 6. Границы Build
|
||||
|
||||
Build 060.27 намеренно не реализует:
|
||||
|
||||
- persistent Runtime checkpoint;
|
||||
- Startup Recovery после перезапуска процесса;
|
||||
- Historical Queries и Data Access Layer;
|
||||
- Replay API и Analytics API;
|
||||
- автоматический partition/retention scheduler;
|
||||
- подключение существующих Quote/Candle consumers;
|
||||
- хранение raw exchange documents.
|
||||
|
||||
Persistent Checkpoint и Startup Recovery относятся к Build 060.28.
|
||||
Historical Access и Replay относятся к Build 060.29. Полная итоговая
|
||||
ревизия документации подсистемы остаётся задачей Build 060.30.
|
||||
|
||||
Постороннее пользовательское изменение `.gitignore` не относится к
|
||||
Build 060.27 и не должно включаться в его staging.
|
||||
|
||||
---
|
||||
|
||||
## 7. Итог
|
||||
|
||||
Build 060.27 завершён и принят.
|
||||
|
||||
Dzentra получила транзакционное долговременное хранилище Canonical
|
||||
Market Data и production persistence для Trades. Operational checkpoint
|
||||
теперь продвигается только после подтверждённой durable-записи, а
|
||||
ошибка Storage завершает Runtime без скрытой потери истории.
|
||||
|
||||
Следующий этап — Build 060.28, Persistent Checkpoint and Startup
|
||||
Recovery.
|
||||
509
docs/migrations/build_060_27_architecture.md
Normal file
509
docs/migrations/build_060_27_architecture.md
Normal file
@@ -0,0 +1,509 @@
|
||||
# Build 060.27 — Persistent Market Data Storage Architecture
|
||||
|
||||
**Статус:** Completed
|
||||
|
||||
**Build:** 060.27
|
||||
|
||||
**Подсистема:** Market Data Acquisition / Persistent Storage
|
||||
|
||||
**Дата начала:** 2026-07-31
|
||||
|
||||
**Дата завершения:** 2026-08-01
|
||||
|
||||
**Версия документа:** 1.5
|
||||
|
||||
---
|
||||
|
||||
## 1. Назначение
|
||||
|
||||
Build 060.27 создаёт долговременное PostgreSQL-хранилище Canonical
|
||||
Market Data. Данные должны сохраняться независимо от времени жизни
|
||||
процесса и стать основой для Persistent Checkpoint, исторических
|
||||
запросов и Replay в следующих Build.
|
||||
|
||||
В Build реализуется production-интеграция только для Trades. Для Quotes
|
||||
и Candles создаются схема, repository-контракты и Storage API, но
|
||||
существующие legacy/runtime consumers пока к ним не подключаются.
|
||||
|
||||
---
|
||||
|
||||
## 2. Статус подэтапов
|
||||
|
||||
| Подэтап | Название | Статус |
|
||||
|---|---|---|
|
||||
| 060.27.0 | Storage Contract and Boundaries | Accepted |
|
||||
| 060.27.1 | PostgreSQL Schema, Migrations and Pool | Accepted |
|
||||
| 060.27.2 | Persistent Trade Repository | Accepted |
|
||||
| 060.27.3 | Quote and Candle Repositories | Accepted |
|
||||
| 060.27.4 | Storage API and Retention | Accepted |
|
||||
| 060.27.5 | Trade Runtime Persistence Integration | Accepted |
|
||||
| 060.27.6 | Bootstrap and Lifecycle Integration | Accepted |
|
||||
| 060.27.7 | Integration and Failure Verification | Accepted |
|
||||
| 060.27.8 | Final Regression and Acceptance | Accepted |
|
||||
|
||||
060.27.0–060.27.1 прошли отдельный read-only review. После исправления
|
||||
cleanup при системном прерывании и удаления двух дублирующих индексов
|
||||
повторный review завершён без findings.
|
||||
|
||||
060.27.2 прошёл отдельный read-only review конкурентных вставок,
|
||||
Canonical conflict, provenance, batch atomicity и error propagation.
|
||||
Review завершён без findings.
|
||||
|
||||
060.27.3 прошёл отдельный read-only review Quote timestamp collision,
|
||||
Candle revision identity, interval case, concurrent provenance и
|
||||
transaction rollback. Review завершён без findings.
|
||||
|
||||
060.27.4 принят после исправления проверки фактического PostgreSQL
|
||||
partition bound и изоляции Retention от concurrent writers. Повторный
|
||||
read-only review concurrent creation, default-row move, DDL safety,
|
||||
точности cutoff и полного rollback завершён без findings.
|
||||
|
||||
060.27.5 принят после отдельного read-only review durable-before-
|
||||
checkpoint ordering, duplicate provenance, fatal error propagation,
|
||||
cancellation-safe live offload и единственного владельца записи.
|
||||
Review завершён без findings; целевые тесты и полная регрессия прошли.
|
||||
|
||||
060.27.6 реализует disabled-by-default Storage feature flag, общий
|
||||
bootstrap graph и управляемый Application lifecycle. Целевые тесты и
|
||||
полная регрессия прошли. Отдельный read-only review feature flag,
|
||||
отсутствия Storage I/O при composition, partial startup, shutdown order,
|
||||
repeated cancellation и task ownership завершён без findings.
|
||||
|
||||
060.27.7 добавляет безопасный opt-in PostgreSQL harness и выполняет
|
||||
реальные schema, repository, partition, retention, Runtime persistence и
|
||||
failure-сценарии на одноразовой локальной базе. Настоящий PostgreSQL
|
||||
выявил ошибку параметризации partition DDL; границы CHECK и ATTACH теперь
|
||||
встраиваются как безопасные `psycopg.sql.Literal`.
|
||||
|
||||
Первичный review выявил обход local-only проверки через PostgreSQL
|
||||
service/неявный endpoint и недостаточное доказательство одновременного
|
||||
участия concurrent callers. Harness теперь запрещает service, неявные и
|
||||
смешанные local/remote endpoints, а непосредственно перед destructive
|
||||
cleanup повторно сверяет точное имя базы и identity контрольного
|
||||
соединения. Migration и partition tests удерживают advisory lock, пока
|
||||
PostgreSQL не подтвердит ожидание обеих caller-сессий; Trade writers
|
||||
синхронизируются общим барьером. Повторный review завершён без findings,
|
||||
подэтап принят.
|
||||
|
||||
060.27.8 выполнил финальную verification-only приёмку без изменения
|
||||
production-кода. Целевой unit-набор, новый одноразовый PostgreSQL,
|
||||
десять повторов PostgreSQL integration, общий integration, fixed stress
|
||||
и полная offline-регрессия прошли. Итоговый read-only review не выявил
|
||||
новых findings; подэтап и Build 060.27 приняты.
|
||||
|
||||
---
|
||||
|
||||
## 3. Принятые архитектурные решения
|
||||
|
||||
1. В production runtime этого Build подключается только хранение Trades.
|
||||
2. Для Quotes и Candles создаётся storage foundation без изменения
|
||||
существующего legacy/runtime wiring.
|
||||
3. Operational checkpoint можно продвигать только после успешного
|
||||
сохранения соответствующего Trade.
|
||||
4. Ошибка включённого persistent storage является фатальной: Runtime не
|
||||
должен скрывать потерю истории и продолжать работу как будто всё
|
||||
успешно.
|
||||
5. Raw exchange documents в Build 060.27 не сохраняются. Хранилище
|
||||
принимает только прошедшие validation и mapping Canonical Models.
|
||||
6. Canonical history по умолчанию не удаляется. Retention включается
|
||||
только явной настройкой и отдельной операцией.
|
||||
7. Идентичность Trade задаётся кортежем `venue`, `symbol`, `trade_id`,
|
||||
`executed_at`. Включение времени защищает от коллизии после полного
|
||||
цикла signed 32-bit Trade ID.
|
||||
8. Market Data размещаются в PostgreSQL schema `market_data`, изменения
|
||||
схемы выполняются упорядоченными SQL-миграциями, соединения выдаёт
|
||||
один явно управляемый pool.
|
||||
|
||||
---
|
||||
|
||||
## 4. Архитектурная граница
|
||||
|
||||
```text
|
||||
Canonical Trade / Quote / Candle
|
||||
│
|
||||
▼
|
||||
market_data.storage contracts
|
||||
│
|
||||
▼
|
||||
PostgreSQL repositories (060.27.2–060.27.3)
|
||||
│
|
||||
▼
|
||||
storage.PostgresConnectionPool
|
||||
│
|
||||
▼
|
||||
PostgreSQL schema market_data
|
||||
```
|
||||
|
||||
Storage зависит от Canonical Models. Canonical Models, Consistency,
|
||||
Recovery и Transport не зависят от PostgreSQL или `psycopg`.
|
||||
|
||||
Trade Runtime использует общий acquisition-side контракт
|
||||
`TradeObservationSinkProtocol`. Его Storage adapter знает о
|
||||
`TradeStorageProtocol`, venue и UTC clock, но Consistency и Runtime не
|
||||
знают о PostgreSQL repository или pool:
|
||||
|
||||
```text
|
||||
Live / Recovery
|
||||
│
|
||||
▼
|
||||
shared TradeStreamConsistencyController
|
||||
│ validated observation
|
||||
▼
|
||||
TradeObservationSinkProtocol
|
||||
│
|
||||
▼
|
||||
TradeStorageObservationSink → TradeStorageProtocol
|
||||
```
|
||||
|
||||
Контракты возвращают явный результат записи:
|
||||
|
||||
- `INSERTED` — новое событие сохранено;
|
||||
- `DUPLICATE` — полностью совпадающее событие уже существовало;
|
||||
- `PROVENANCE_UPDATED` — Canonical payload совпал, но обновлена
|
||||
информация о наблюдении события.
|
||||
|
||||
Несовпадающий payload под тем же ключом не является дубликатом и должен
|
||||
приводить к `MarketDataStorageConflictError`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Модель хранения
|
||||
|
||||
### 5.1. Trades
|
||||
|
||||
Таблица `market_data.trades` хранит Canonical Trade и технические поля
|
||||
provenance:
|
||||
|
||||
```text
|
||||
identity = venue + symbol + trade_id + executed_at
|
||||
provenance = source + observation_sources
|
||||
+ first_observed_at + last_observed_at
|
||||
```
|
||||
|
||||
`source` сохраняет первый зафиксированный путь получения Trade.
|
||||
`observation_sources` накапливает уникальные Canonical source names,
|
||||
например WebSocket и REST Recovery, не превращая различие transport
|
||||
provenance в конфликт биржевого факта.
|
||||
|
||||
`trade_id` ограничен signed 32-bit диапазоном. Это соответствует
|
||||
единому rollover-aware контракту Build 060.26, но база не пытается
|
||||
сортировать события только по числовому ID. Исторический порядок
|
||||
определяется прежде всего `executed_at`.
|
||||
|
||||
### 5.2. Quotes
|
||||
|
||||
Таблица `market_data.quotes` хранит неизменяемые Canonical snapshots.
|
||||
Ключ снимка: `venue`, `symbol`, `received_at`. Nullable
|
||||
`exchange_timestamp` сохраняется как часть payload, но не используется
|
||||
как обязательный ключ.
|
||||
|
||||
`source` сохраняет первый зафиксированный путь получения, а
|
||||
`observation_sources` — уникальные источники того же snapshot. Два
|
||||
разных payload с одним `received_at` являются явным timestamp conflict;
|
||||
repository не изменяет полученное время и не создаёт скрытый surrogate.
|
||||
|
||||
### 5.3. Candles
|
||||
|
||||
Свеча может меняться до закрытия интервала. Поэтому
|
||||
`market_data.candle_revisions` не перезаписывает строку на месте, а
|
||||
хранит последовательность наблюдавшихся ревизий. Ключ:
|
||||
|
||||
```text
|
||||
venue + symbol + interval + open_time + observed_at
|
||||
```
|
||||
|
||||
Флаг `is_final` отделяет промежуточную ревизию от финальной.
|
||||
Изменение содержимого или `is_final` сохраняется как новая ревизия с
|
||||
новым `observed_at`. Разный source той же ревизии обновляет только
|
||||
`observation_sources`. Регистр `interval` сохраняется, потому что имена
|
||||
рыночных интервалов могут быть чувствительны к регистру.
|
||||
|
||||
### 5.4. Партиционирование и retention
|
||||
|
||||
Все три таблицы объявлены как range-partitioned по времени события и
|
||||
имеют default partition. Это даёт безопасную запись до появления
|
||||
автоматического partition manager.
|
||||
|
||||
Default partition остаётся страховкой для записи до создания нужного
|
||||
месяца. `PostgresMarketDataPartitionManager` под transaction-level
|
||||
advisory lock создаёт отдельную месячную таблицу, блокирует default
|
||||
partition, переносит подходящие строки и подключает таблицу как
|
||||
partition в одной транзакции.
|
||||
|
||||
Созданные Dzentra partitions регистрируются в
|
||||
`market_data.partition_registry`. Существующая незарегистрированная
|
||||
таблица, отсутствующая зарегистрированная таблица или detached
|
||||
partition считаются ошибкой конфигурации и не принимаются молча.
|
||||
|
||||
Canonical retention по умолчанию не ограничен.
|
||||
`PostgresMarketDataRetentionService` вообще не обращается к базе при
|
||||
выключенной policy. Включённый явный запуск удаляет целиком только
|
||||
зарегистрированные истёкшие partitions, затем удаляет строки до точного
|
||||
UTC cutoff из оставшихся/default partitions. Все настроенные типы
|
||||
очищаются в одной транзакции.
|
||||
|
||||
---
|
||||
|
||||
## 6. Версионированные миграции
|
||||
|
||||
`StorageMigrationRunner`:
|
||||
|
||||
1. получает соединение через переданный provider;
|
||||
2. берёт PostgreSQL transaction-level advisory lock;
|
||||
3. создаёт таблицу истории `public.storage_schema_migrations`;
|
||||
4. сверяет уже применённые версии и их неизменяемые имена;
|
||||
5. применяет отсутствующие версии строго по порядку;
|
||||
6. записывает версию в историю в той же транзакции.
|
||||
|
||||
Неизвестная версия в базе или другое имя уже применённой версии
|
||||
считается ошибкой. Миграции не должны молча принимать расхождение
|
||||
между кодом и фактической схемой.
|
||||
|
||||
Начальный набор:
|
||||
|
||||
| Версия | Назначение |
|
||||
|---|---|
|
||||
| 1 | schema `market_data` |
|
||||
| 2 | partitioned Canonical Trades |
|
||||
| 3 | partitioned Canonical Quotes |
|
||||
| 4 | partitioned immutable Candle revisions |
|
||||
| 5 | multiple Trade observation sources |
|
||||
| 6 | Quote and Candle observation sources |
|
||||
| 7 | managed monthly partition registry |
|
||||
|
||||
---
|
||||
|
||||
## 7. Connection Pool lifecycle
|
||||
|
||||
`PostgresConnectionPool` имеет явный lifecycle:
|
||||
|
||||
```text
|
||||
construct → open → borrow/return connections → close
|
||||
```
|
||||
|
||||
Конструктор не открывает сеть. `open()` ждёт готовности минимального
|
||||
числа соединений и при ошибке освобождает частично созданный pool.
|
||||
`connection()` до успешного `open()` запрещён. `close()` идемпотентен.
|
||||
|
||||
В 060.27.6 pool, migration runner, Trade repository и observation sink
|
||||
собираются без сетевых операций. Application запускает blocking startup
|
||||
через owned `asyncio.to_thread` task до Telegram и Trade Runtime:
|
||||
|
||||
```text
|
||||
pool.open → migrations.run → Telegram + Trade Runtime
|
||||
```
|
||||
|
||||
Shutdown выполняется в обратном порядке зависимостей. Сначала полностью
|
||||
останавливается Trade Runtime и завершаются его Storage-вызовы, затем
|
||||
закрывается pool и после этого bot session.
|
||||
|
||||
---
|
||||
|
||||
## 8. Обработка ошибок
|
||||
|
||||
- ошибки lifecycle pool оборачиваются в
|
||||
`PostgresConnectionPoolError`;
|
||||
- ошибки запуска или расхождения миграций оборачиваются в
|
||||
`StorageMigrationError`;
|
||||
- repository validation, conflict и database operations используют
|
||||
иерархию `MarketDataStorageError`;
|
||||
- `KeyboardInterrupt` и `SystemExit` не превращаются в storage errors;
|
||||
- ошибка включённого persistent storage фатальна и не скрывается;
|
||||
- ошибка cleanup не заменяет исходную startup/runtime ошибку;
|
||||
- cancellation ждёт уже начатый blocking lifecycle operation.
|
||||
|
||||
---
|
||||
|
||||
## 9. Границы реализации 060.27.0–060.27.8
|
||||
|
||||
Реализовано:
|
||||
|
||||
- отдельный пакет `market_data.storage`;
|
||||
- write-only Protocol contracts для Trades, Quotes и Candles;
|
||||
- результаты одиночной и пакетной идемпотентной записи;
|
||||
- отдельная иерархия ошибок Market Data Storage;
|
||||
- versioned migration runner с advisory lock;
|
||||
- partitioned PostgreSQL schema для трёх Canonical типов;
|
||||
- явно открываемый и закрываемый PostgreSQL connection pool;
|
||||
- синхронный `PostgresTradeRepository` без ownership pool lifecycle;
|
||||
- одиночная и атомарная пакетная запись Canonical Trades;
|
||||
- exact duplicate, provenance update и canonical conflict semantics;
|
||||
- стабильный порядок batch-записи и rollback всей пачки при ошибке;
|
||||
- синхронные `PostgresQuoteRepository` и `PostgresCandleRepository`;
|
||||
- immutable Quote snapshot и Candle revision conflict semantics;
|
||||
- multiple-source provenance для Trades, Quotes и Candle revisions;
|
||||
- общие validation helpers трёх PostgreSQL repositories;
|
||||
- единый write-only `MarketDataStorage` facade без lifecycle ownership;
|
||||
- UTC monthly partition descriptors для трёх Canonical типов;
|
||||
- транзакционное создание, перенос и attach managed partitions;
|
||||
- реестр Dzentra-managed partitions в migration 7;
|
||||
- disabled-by-default retention с отдельным явным запуском;
|
||||
- удаление полных partitions и точная очистка partial/default данных;
|
||||
- общий advisory lock для partition lifecycle и retention;
|
||||
- закреплённые версии `psycopg` и `psycopg-pool`;
|
||||
- общий optional `TradeObservationSinkProtocol` для Live и Recovery;
|
||||
- единственный persistence owner в `TradeStreamConsistencyController`;
|
||||
- durable write до продвижения operational checkpoint;
|
||||
- запись корректных дубликатов для обновления provenance;
|
||||
- выполнение синхронной live-записи вне asyncio event loop;
|
||||
- cancellation-safe ожидание уже начатой live-записи при shutdown;
|
||||
- отдельный `MarketDataStorageSettings` и безопасный feature flag;
|
||||
- единый bootstrap graph pool → migrations → Trade repository → sink;
|
||||
- использование одного Settings snapshot и существующих `DB_*`;
|
||||
- Storage startup до Telegram и Trade Runtime;
|
||||
- partial startup cleanup при migration failure;
|
||||
- закрытие pool только после полной остановки Trade Runtime;
|
||||
- cancellation-safe Application startup и shutdown tasks.
|
||||
- opt-in harness только для явно указанной локальной disposable базы;
|
||||
- отсутствие fallback PostgreSQL integration-тестов на application
|
||||
`DB_*`;
|
||||
- real PostgreSQL verification миграций, repositories и транзакций;
|
||||
- real concurrent migration, Trade write и partition callers;
|
||||
- real partition move, retention cutoff и multi-type rollback;
|
||||
- local WebSocket/REST Runtime persistence в настоящий PostgreSQL;
|
||||
- real reconnect/recovery provenance через общий Trade repository;
|
||||
- fatal persistence failure без ложного продвижения checkpoint;
|
||||
- проверка Application ownership pool и отсутствия оставшихся соединений.
|
||||
|
||||
Не реализовано на этом шаге:
|
||||
|
||||
- чтение истории;
|
||||
- автоматический partition/retention scheduling;
|
||||
- подключение legacy Quote/Candle consumers.
|
||||
|
||||
---
|
||||
|
||||
## 10. Критерии приёмки 060.27.0–060.27.8
|
||||
|
||||
1. Storage contracts не импортируют Runtime, Consistency или Recovery.
|
||||
2. Конструктор pool не выполняет сетевых операций.
|
||||
3. Частично открытый pool очищается при ошибке startup.
|
||||
4. Повторный `close()` безопасен.
|
||||
5. Миграции выполняются один раз, последовательно и атомарно.
|
||||
6. Concurrent migration runners сериализуются advisory lock.
|
||||
7. Schema содержит явные idempotency keys и Canonical constraints.
|
||||
8. Trade Runtime не импортирует PostgreSQL repository или pool.
|
||||
9. Целевые тесты и обычная регрессия проходят успешно.
|
||||
10. Источник доставки не является частью Canonical Trade conflict.
|
||||
11. Один конфликт или database error откатывает весь Trade batch.
|
||||
12. Repository не владеет pool lifecycle и не импортирует Runtime.
|
||||
13. Quote timestamp collision и Candle revision conflict не скрываются.
|
||||
14. Quote/Candle source обновляет provenance, а не Canonical payload.
|
||||
15. Существующие Quote/Candle consumers не подключены к PostgreSQL.
|
||||
16. Storage facade делегирует записи и не владеет pool lifecycle.
|
||||
17. Имена таблиц и колонок выбираются только из закрытого набора и
|
||||
экранируются через `psycopg.sql.Identifier`.
|
||||
18. Повторное создание месяца идемпотентно, а расхождение реестра и
|
||||
PostgreSQL является явной ошибкой.
|
||||
19. Неудачный setup partition откатывает создание, перенос и registry.
|
||||
20. Retention требует отдельного `enabled` и положительного окна.
|
||||
21. Выключенный retention не получает соединение и ничего не удаляет.
|
||||
22. Ошибка очистки одного типа откатывает всю retention-операцию.
|
||||
23. Runtime, Scheduler и существующие consumers не импортируют новый
|
||||
Storage API.
|
||||
24. Live и Recovery используют один persistence sink через общий
|
||||
`TradeStreamConsistencyController`.
|
||||
25. Validated Trade сохраняется до изменения checkpoint и dedup window.
|
||||
26. Корректный дубликат сохраняется для provenance без продвижения
|
||||
checkpoint.
|
||||
27. Ordering error, conflicting duplicate и invalid Trade не передаются
|
||||
в Storage.
|
||||
28. Ошибка Storage не скрывается и не продвигает checkpoint.
|
||||
29. Live persistence выполняется через owned `asyncio.to_thread` task
|
||||
при закрытом live-processing gate.
|
||||
30. Shutdown ждёт уже начатую live-запись и не оставляет её фоновой.
|
||||
31. Отсутствующий optional sink сохраняет прежнее поведение Composition.
|
||||
32. Bootstrap/settings не изменены, PostgreSQL pool не открывается.
|
||||
33. Целевой набор и полная обычная регрессия проходят успешно.
|
||||
34. Storage feature flag выключен по умолчанию и не строит зависимости.
|
||||
35. Включённый Storage требует включённый Trade Stream.
|
||||
36. Pool settings валидируются только при включённом Storage.
|
||||
37. Bootstrap graph не открывает PostgreSQL и не запускает migrations.
|
||||
38. Migration runner и Trade repository используют один pool identity.
|
||||
39. Один observation sink передаётся общим Live/Recovery controller.
|
||||
40. Pool открывается и migrations завершаются до root Runtime tasks.
|
||||
41. Migration failure закрывает открытый pool и не запускает Runtime.
|
||||
42. Blocking startup/shutdown выполняются вне asyncio event loop.
|
||||
43. Cancellation ждёт завершения уже начатой blocking операции.
|
||||
44. Trade Runtime полностью остановлен до закрытия pool.
|
||||
45. Storage cleanup error не заменяет исходную Runtime ошибку.
|
||||
46. Retention и partition scheduling автоматически не запускаются.
|
||||
47. PostgreSQL integration требует точный opt-in flag и отдельный DSN.
|
||||
48. Integration DSN принимает только localhost/local socket и database
|
||||
name с префиксом `dzentra_test_`.
|
||||
49. Harness не читает application `DB_*` и не управляет Docker скрытно.
|
||||
50. Migrations 1–7 реально применяются в PostgreSQL и повторный запуск
|
||||
ничего не меняет.
|
||||
51. Два real migration runners применяют каждую версию ровно один раз.
|
||||
52. Trades, Quotes и Candle revisions проходят real insert, duplicate,
|
||||
provenance и conflict paths.
|
||||
53. Trade batch реально откатывает предшествующую вставку при конфликте.
|
||||
54. Два concurrent Trade writers сходятся к одной Canonical строке.
|
||||
55. Partition DDL использует безопасные SQL literals вместо запрещённых
|
||||
PostgreSQL parameters в `ALTER TABLE`.
|
||||
56. Real partition manager переносит default rows и сериализует callers.
|
||||
57. Real retention соблюдает точный cutoff и общий transaction rollback.
|
||||
58. Live и reconnect/recovery Runtime сохраняют Trades в один repository.
|
||||
59. Закрытый pool приводит к fatal Runtime error без продвижения
|
||||
checkpoint и без записи проблемного Trade.
|
||||
60. Application открывает и мигрирует Storage до Runtime, а закрывает pool
|
||||
после Runtime; после shutdown нет owned tasks и pool connections.
|
||||
61. Выключенный opt-in PostgreSQL-набор не требует локальной базы и
|
||||
корректно пропускается обычной regression-командой.
|
||||
62. Destructive cleanup непосредственно перед DDL повторно проверяет
|
||||
точное имя disposable базы и специальный control application name.
|
||||
63. Concurrent migration и partition tests подтверждают в PostgreSQL,
|
||||
что обе caller-сессии действительно ожидают один advisory lock.
|
||||
|
||||
### 10.1. Opt-in PostgreSQL verification
|
||||
|
||||
Тесты никогда не используют production `DB_*`. Первый запуск требует
|
||||
явного одноразового PostgreSQL и выполняется командой вида:
|
||||
|
||||
```bash
|
||||
DZENTRA_RUN_POSTGRES_TESTS=1 \
|
||||
DZENTRA_TEST_POSTGRES_DSN=postgresql://test:test@127.0.0.1:55432/dzentra_test_060_27 \
|
||||
.venv/bin/python -m pytest -q -m integration \
|
||||
tests/integration/market_data/storage
|
||||
```
|
||||
|
||||
Harness проверяет имя базы и каждый local endpoint до соединения,
|
||||
запрещает service/implicit fallback, берёт session advisory lock и
|
||||
очищает только `market_data` и собственную таблицу migration history.
|
||||
Перед каждым destructive cleanup он повторно подтверждает фактическое
|
||||
имя disposable базы и identity контрольного соединения.
|
||||
|
||||
---
|
||||
|
||||
## 11. Финальная приёмка 060.27.8
|
||||
|
||||
Финальный этап выполнялся по verification-only правилу: production-код
|
||||
не изменялся, потому что проверки не выявили нового дефекта.
|
||||
|
||||
Результаты на 2026-08-01:
|
||||
|
||||
```text
|
||||
Targeted Storage/Runtime unit suite: 307 passed
|
||||
PostgreSQL control run: 15 passed
|
||||
PostgreSQL repeated ten times: 150 passed
|
||||
Combined integration suite: 26 passed
|
||||
Fixed stress: 3 passed, 1 deselected
|
||||
PostgreSQL suite without opt-in: 15 skipped
|
||||
Full offline regression: 2119 passed, 31 deselected
|
||||
git diff --check: clean
|
||||
Untracked files diff check: clean
|
||||
```
|
||||
|
||||
Во всех integration и stress проверках `ResourceWarning` считался
|
||||
ошибкой. Одноразовый PostgreSQL 16 использовал временное `tmpfs`
|
||||
хранилище и после проверки был остановлен и удалён.
|
||||
|
||||
Итоговый read-only review повторно проверил архитектурные зависимости,
|
||||
SQL/DDL safety, durable-before-checkpoint ordering, fatal persistence
|
||||
failure, lifecycle pool и Runtime, opt-in isolation и test coverage.
|
||||
Новых findings не обнаружено.
|
||||
|
||||
## 12. Следующий Build
|
||||
|
||||
Build 060.27 завершён и принят. Следующий этап — Build 060.28,
|
||||
Persistent Checkpoint and Startup Recovery.
|
||||
Reference in New Issue
Block a user