feat(db): перевести хранилище на PGlite ради нативных таймстемпов - #21
Merged
Conversation
Таймстемпы лежали в integer с unix-миллисекундами, и что в колонке время, знал только кодек drizzle да комментарий рядом. Теперь под drizzle postgres — PGlite, wasm-сборка внутри процесса, — и колонки объявлены timestamptz: тип говорит сам за себя. Отдельного сервиса по-прежнему нет, база так же пересоздаётся при каждом старте, а из образа ушёл apt-шаг с python3/make/g++: нативных модулей не осталось. Шесть sqlite-миграций схлопнуты в одну — воспроизвести их на postgres нельзя, история осталась в git. id стал identity generated by default: always отвергал бы вставку с явным id, а в sqlite он был алиасом rowid и подставлялся сам. Что вылезло при переезде: - .run() в транзакции удаления курса — синхронный sqlite-API, теперь await на каждом delete. Там же db.get(sql`select 1`) в healthCheck стал db.execute. - 503 на всё под нагрузкой. Порог event loop delay был выставлен заведомо большим (600000), но гистограмма monitorEventLoopDelay под нагрузкой отдаёт mean = Infinity, и любое конечное значение оказывается меньше. Ноль under-pressure понимает как «проверку не делать вовсе». - 500 на NUL (U+0000) в тексте — нашёл контрактный прогон. Postgres такую строку не хранит в принципе, sqlite хранил. Отвечаем 400 из общего хука: это свойство хранилища, а не бизнес-правило конкретной модели, и касается каждого текстового поля. 400 задокументирован у всех операций, контракт не менялся. - findFirst() без ORDER BY в postgres порядок не обещает, и после UPDATE запись уезжает в конец: тесты, перечитывавшие «первую» строку, читали не ту. Перечитывания теперь по id. - pluginTimeout поднят с дефолтных 10 секунд: инициализация базы выросла с микросекунд до ~1.4 с (initdb плюс миграции), и запаса не хватало ни на параллельном прогоне тестов, ни на холодном старте в контейнере. Прогон тестов подорожал: ~2 минуты на 75 тестов, из них львиная доля — 70 подъёмов приложения по ~1.4 с на базу. Снимок через dumpDataDir/loadDataDir сокращает это до ~0.3 с на инстанс, но требует шва в plugins/drizzle.ts ради тестов и сделан не был. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Таймстемпы лежали в
integerс unix-миллисекундами, и что в колонке время, знал только кодек drizzle да комментарий рядом. Под drizzle теперь postgres — PGlite, wasm-сборка внутри процесса, — и колонки объявленыtimestamptz.Отдельного сервиса по-прежнему нет, база так же пересоздаётся при каждом старте. Из образа ушёл apt-шаг с python3/make/g++: нативных модулей не осталось.
Шесть sqlite-миграций схлопнуты в одну — воспроизвести их на postgres нельзя, история осталась в git.
idстал identity generated by default: always отвергал бы вставку с явным id, а в sqlite он был алиасом rowid и подставлялся сам.Что вылезло при переезде
.run()в транзакции удаления курса — синхронный sqlite-API, теперьawaitна каждом delete. Там жеdb.get(sql\select 1`)в healthCheck сталdb.execute`.monitorEventLoopDelayпод нагрузкой отдаётmean = Infinity, и любое конечное значение оказывается меньше. Ноль under-pressure понимает как «проверку не делать вовсе».findFirst()без ORDER BY в postgres порядок не обещает, и после UPDATE запись уезжает в конец: тесты, перечитывавшие «первую» строку, читали не ту. Перечитывания теперь по id.pluginTimeoutподнят с дефолтных 10 секунд: инициализация базы выросла с микросекунд до ~1.4 с (initdb плюс миграции), и запаса не хватало ни на параллельном прогоне тестов, ни на холодном старте в контейнере.Проверено локально
pnpm test(75/75),make lint,make test-coverage(выше порогов),make migration-check,make contract-test(обе версии, несколько прогонов),make smoke-testна собранном образе, холоднаяpnpm install --frozen-lockfileс вынесеннымnode_modules.Цена
Прогон тестов подорожал: ~2 минуты на 75 тестов, львиная доля — 70 подъёмов приложения по ~1.4 с на базу. Снимок через
dumpDataDir/loadDataDirсокращает это до ~0.3 с на инстанс, но требует шва вplugins/drizzle.tsради тестов — не делал, вынесено на обсуждение.🤖 Generated with Claude Code