Raw-архив — bronze-слой сырых данных
Решение (2026-06-19): ввести durable immutable raw-архив сырых JSON dicechess.com перед нормализацией. При найденных багах конвертации → переконвертировать архив, а не перескрейпить Cloudflare-gated сайт.
Зачем (выгоды конкретны, не гипотетичны)
- Защита от перескрейпа. Сайт за Cloudflare (проходит только
curl+ OpenSSL ≥ 3.5), single-flight ~1.5–2 партии/сек.game-move-history— point-in-time снимок: при 422 сырьё уже не перезапросить. Сохранённое сырьё — единственный способ сделать 422 и баги парсера восстановимыми. - Реплей при багах конвертации — главная выгода. Нормализатор — хрупкие эвристики: en passant #351, инференс цвета по часам, double-decline vs draw, terminal-color (#161/#163). Каждый баг = правка кода, требующая переконвертации уже скачанных партий.
- Развязка добычи и конвертации. Медленный/сломанный конвертер не стопорит добычу; падение конвертера не теряет партию, прошедшую Cloudflare.
Ключевой факт: это НЕ новая система
Это формализация уже построенного в dicechess-sync (см. dicechess-sync). Там стадия
cache(raw) уже есть: таблица raw_game_data (SQLite, move_history_json), fetchGame()→markFetched()
без нормализации, postGame() читает кэш → нормализует → POST, есть replayRejected()/replayNormalizeFailures().
Что доделать:
- Поднять
raw_game_dataиз «transient cache» в архив-источник-истины: gzipBYTEA+ provenance (source,content_hash,schema_version, отдельныйmeta_fetched_at), retention = хранить вечно. - Запись = bundle
{discovery_meta + move_history}(два артефакта, приходят в разное время), PK(source, external_id)— НЕ id один (dicechess UUID vs beturanga ObjectId коллизируют). - Хранить gzip BYTEA, не JSONB (×20 по месту, байт-в-байт для хэша/реплея).
- Сырьё реплеим, а не запрашиваем по полям → NoSQL не нужен.
Решения по границам
- В публичный
dicechess-analyticsraw НЕ добавляем (он source-agnostic;metadata_json= NULL). Архив живёт только на приватной стороне. - Запрос «по игроку» = вторичный индекс над неизменяемыми партиями.
player/history— растущий изменяемый снимок, НЕ immutable → ключ архива = ПАРТИЯ, страницу истории хранить лишь какdiscovery_metaв bundle.
Главная дыра сейчас
observer (rpi4) и extension — memory-only, теряют сырьё live-партий безвозвратно.
Самый ценный конкретный шаг — заставить observer писать bundle в архив до normalizeStateMap().
Масштаб и размещение
~2.4 КБ/партия gzipped (сырьё ~48 КБ, сжатие ~20×). 10M ≈ 24 ГБ — не big-data. → Архив на dexus (192.168.10.4): 825 ГБ свободного HDD, уже крутит borgbackup. HDD идеален для append-only write-once-read-rarely. SQLite на single-writer достаточно; PG — если несколько писателей.
Топология
[curl fetch] → [RAW ARCHIVE (dexus)] → [converter читает архив] → [analytics POST].
Не два отдельных микросервиса — один процесс на источник, но «запись сырого» = жёсткая точка коммита
перед нормализацией. Реплей после фикса движка = перезапуск converter по архиву.
Связано: dicechess-sync, Dice Chess Observer, Апгрейд aurora — план и чек-лист.