Если дашь больше подробностей я могу очень сильно сократить размер вашей бд и упростить/ускорить все (в десятки, сотни раз). Я вполне серьезно, но подходы у меня не совсем стандартные. Я не пользуюсь готовыми решениями, но могу накатать свой сервис на Rust/RocksDB заточеный под конкретные задачи
Обычный бекенд сайта - это тоже лютое. И php сложный язык с кучей нюансов. Конечно c++ мы сразу отбрасываем, потому то он не совсем для веба и на нем я не писал и не хочу писать и даже никому не советую, т.к это крайне не безопасный язык и подходит для избранных. Но тот же Go, Rust, да даже старичек python https://programming-language-benchmarks.vercel.app/php-vs-python сильно лучше php и в скорости и в безопасности. Насчет меня - я пишу на rust и очень советую пересаживаться при первой же возможности. Потом скажете спасибо
Вместо того, чтоб конструктивно ответить минусите. Давайте не обижаемся)
Вот я постоянно наблюдаю в логах прокси фаервола попытки сканирования на php скрипты, ну 80 процентов точно есть. Еще 10 эксплуатируют уязвимости next (nodejs/deno framework). По скорости php всегда в топе снизу любых бенчмарков, даже не смотря на то что yii один из самых быстрых php фреймворков. Апелировать скоростью разработки уже не получится, т.к агентные llm пишут код на раз два. Главное спланируй правильно и понимай архитектуру общую. Так зачем?
Вы меня простите, но зачем писать на этом динозавре? Он же очень медленный и дырявый в сравнении даже с тем же js калом под нод жс, не говоря уже о go или rust
Ты в точку попал — если требования прыгают каждую неделю ("а теперь по моделям", "а теперь по водителям", "а теперь по пробегу"), то предрасчёт только по "актив → площадка → день" может оказаться бесполезным, и придётся либо всё перестраивать, либо держать сырые интервалы.
Но тут как раз фишка в том, что если хранить сырые интервалы в обычной таблице (как в статье), а предвычисленные снэпшоты по дням — в key-value, то ты получаешь и то, и другое:
Сырые интервалы — для гибкости, когда начальник придумал новый срез (добавляешь поле в таблицу или отдельную табличку — и всё).
Предвычисленные битмапы — только для самого горячего запроса ("где был список активов на дату"), который обычно 90% нагрузки и должен летать.
Обновляешь битмапы раз в сутки батчем — и никаких проблем с новыми требованиями, потому что сырые данные остаются на месте. А если новый срез тоже стал горячим — просто добавляешь ещё один слой предрасчёта (битмапы по новому измерению).
По времени расчётов — да, в реляционке оно предсказуемое, но в key-value для типичного запроса оно просто на порядок меньше и тоже предсказуемое (один get + пару микросекунд на битмапы).
Короче, я не за "бросаем всё в NoSQL и в кучу", а за гибрид: сырые данные в Postgres/CH (где удобно), а горячие ежедневные снэпшоты — в key-value для скорости. Так и начальник доволен (новые срезы быстро добавляются), и пользователи не ждут по 10 мс на каждый отчёт :)
А автор как раз показал крутой инструмент для сырых данных — респект ему за это! Просто я добавил вариант, как выжать ещё больше скорости из самого частого сценария. Всё от задачи, как ты и сказал 👍
Ок, понял, ты прав насчёт ACID и конкурентных обновлений — если история меняется онлайн с разных сторон, то PostgreSQL с exclusion constraint'ами реально надёжнее всего, и спорить глупо.
Но в 90% случаев с учётом спецтехники/ОС обновления приходят батчами раз в день/неделю из 1С или ERP, а днём только читают тоннами. Вот тут key-value с предвычисленными битмапами просто рвёт PostgreSQL:
Чтение: <1 мс на 1000 активов против 5–15 мс в твоём тесте.
Размер: 25–30 МБ против 150–200+ МБ с индексами.
Сложные вопросы типа «кто был на площадках 10–15 за месяц» — пара операций над битмапами, без подзапросов и window-функций.
Короче, если у вас обновления не онлайн и не критичны по секундам — делайте предрасчёт в key-value, будет в разы быстрее и легче. Если же реально конкурентная запись — оставайтесь на Postgres, там всё честно и без сюрпризов.
Крутая статья, спасибо за детальный разбор с PostgreSQL и daterange — это реально полезный подход для реляционных баз, особенно когда нужно гарантировать целостность данных на уровне БД. Но давай честно: SQL здесь мягко говоря не самый оптимальный вариант, особенно если задача — максимизировать скорость чтения и минимизировать место, а не мучаться с обновлениями и индексами. Я бы предложил перейти на key-value базу данных с предвычисленными данными по дням и использованием Roaring Bitmaps для сжатия множеств активов. Это как раз то, что позволит выжать максимум производительности без компромиссов.
Давай сравню наглядно твой подход с PostgreSQL (на основе твоих тестов) и альтернативный:
По времени исполнения запросов:
- В PostgreSQL: для 333 активов на дату — 3 мс (с BTree-индексом), но это с join'ами, сканированием интервалов и ORDER BY/LIMIT. Если список активов вырастет до 1000, или добавятся фильтры — время легко уйдёт в 5-10 мс+ из-за overhead'а индексов.
- В key-value с Roaring: один точечный lookup по ключу (дню) — <1 мс даже для 1000 активов. Почему? Данные предвычислены: загружаешь один blob (~2-3 КБ), десериализуешь в массив из 100 битмапов, и для каждого актива просто проверяешь contains() в O(1). Нет join'ов, нет сканирования — чистая скорость, как в кэше.
По объёму занимаемого места:
- В PostgreSQL: 1.27 млн записей + индексы (GIST и BTree) — легко 100-200 МБ+ на диске, особенно с overhead'ом на строки, даты и проверки целостности. Индексы сами по себе жрут место и замедляют вставки.
- В key-value с Roaring: вся история (~9500 дней) укладывается в <30 МБ. Каждый день — один ключ с сжатым blob'ом из 100 Roaring Bitmaps (по 20-30 байт на площадку, т.к. в среднем 10 активов на ней). Сжатие Roaring бьёт любые таблицы: нет дублирования дат, нет строк — чистые биты.
По сложности более сложных фильтров:
- В PostgreSQL: базовый запрос на дату — ок, но если добавить фильтры (например, "активы на площадках X и Y за период" или "пересечение с другими условиями")? Придётся городить подзапросы, window functions или даже материализованные views — это усложняет код, замедляет (время вырастет в разы) и требует тюнинга индексов. Целостность круто, но для чтения-ориентированной задачи это overkill.
- В key-value с Roaring: фильтры — это нативные операции над битмапами! Например, пересечение (intersection) двух площадок — bitmap1 & bitmap2 в O(N), где N маленькое. Хочешь активы на нескольких площадках за день? Просто загрузи данные дня и сделай bitwise ops. Для периода — загрузи несколько дней (параллельно, если нужно) и union/intersect. Это проще в коде, быстрее (миллисекунды) и масштабируемо на аналитику без перестройки индексов. Плюс key-value даёт преимущества в поиске: атомарные get'ы без блокировок, лёгкая репликация и кэширование, а Roaring позволяет делать сложные set-операции (union, difference) на лету, чего в SQL без костылей не добьёшься.
В общем, если твоя задача — не частые обновления, а быстрые чтения (как в аналитике или отчётах), то SQL здесь выглядит архаично: медленнее, жирнее и сложнее в расширении.
Мне кажется лучше даже не начинать проек с sql базой. За кажущейся легкостью в начале потом настает час расплаты и много боли. Лучше юзать самую простую бд типа ключ значение и хорошо думать как все индексировать на лету. UUID - dense id связи, roaring bitmap и т.д. "Отшардировать" потом будет тоже проще
А почему не используете ZFS в своей системе? Она позволяет делать "моментальные снэпшоты" с минимальным потреблением ресурсов (чем-то ее работа аналогична гиту). Клиент сможет делать снэпшоты хоть каждую минуту. А для сохранности сами данных, вроде как можно пул настроить так, что репликация будет работать по сети, причем с минимальным потреблением трафика (отправляется только то что изменилось). + сквозная шифрока и компрессия работает. Если я что-то упускаю - было бы интересно почитать ваше мнение.
Писать на rust
Если дашь больше подробностей я могу очень сильно сократить размер вашей бд и упростить/ускорить все (в десятки, сотни раз). Я вполне серьезно, но подходы у меня не совсем стандартные. Я не пользуюсь готовыми решениями, но могу накатать свой сервис на Rust/RocksDB заточеный под конкретные задачи
Обычный бекенд сайта - это тоже лютое. И php сложный язык с кучей нюансов. Конечно c++ мы сразу отбрасываем, потому то он не совсем для веба и на нем я не писал и не хочу писать и даже никому не советую, т.к это крайне не безопасный язык и подходит для избранных. Но тот же Go, Rust, да даже старичек python https://programming-language-benchmarks.vercel.app/php-vs-python сильно лучше php и в скорости и в безопасности. Насчет меня - я пишу на rust и очень советую пересаживаться при первой же возможности. Потом скажете спасибо
pingora
Вместо того, чтоб конструктивно ответить минусите. Давайте не обижаемся)
Вот я постоянно наблюдаю в логах прокси фаервола попытки сканирования на php скрипты, ну 80 процентов точно есть. Еще 10 эксплуатируют уязвимости next (nodejs/deno framework). По скорости php всегда в топе снизу любых бенчмарков, даже не смотря на то что yii один из самых быстрых php фреймворков. Апелировать скоростью разработки уже не получится, т.к агентные llm пишут код на раз два. Главное спланируй правильно и понимай архитектуру общую. Так зачем?
Вы меня простите, но зачем писать на этом динозавре? Он же очень медленный и дырявый в сравнении даже с тем же js калом под нод жс, не говоря уже о go или rust
llm рано или поздно сольет депозит также как и человек, т.к он похож на человека.
Ты в точку попал — если требования прыгают каждую неделю ("а теперь по моделям", "а теперь по водителям", "а теперь по пробегу"), то предрасчёт только по "актив → площадка → день" может оказаться бесполезным, и придётся либо всё перестраивать, либо держать сырые интервалы.
Но тут как раз фишка в том, что если хранить сырые интервалы в обычной таблице (как в статье), а предвычисленные снэпшоты по дням — в key-value, то ты получаешь и то, и другое:
Сырые интервалы — для гибкости, когда начальник придумал новый срез (добавляешь поле в таблицу или отдельную табличку — и всё).
Предвычисленные битмапы — только для самого горячего запроса ("где был список активов на дату"), который обычно 90% нагрузки и должен летать.
Обновляешь битмапы раз в сутки батчем — и никаких проблем с новыми требованиями, потому что сырые данные остаются на месте. А если новый срез тоже стал горячим — просто добавляешь ещё один слой предрасчёта (битмапы по новому измерению).
По времени расчётов — да, в реляционке оно предсказуемое, но в key-value для типичного запроса оно просто на порядок меньше и тоже предсказуемое (один get + пару микросекунд на битмапы).
Короче, я не за "бросаем всё в NoSQL и в кучу", а за гибрид: сырые данные в Postgres/CH (где удобно), а горячие ежедневные снэпшоты — в key-value для скорости. Так и начальник доволен (новые срезы быстро добавляются), и пользователи не ждут по 10 мс на каждый отчёт :)
А автор как раз показал крутой инструмент для сырых данных — респект ему за это! Просто я добавил вариант, как выжать ещё больше скорости из самого частого сценария. Всё от задачи, как ты и сказал 👍
Ок, понял, ты прав насчёт ACID и конкурентных обновлений — если история меняется онлайн с разных сторон, то PostgreSQL с exclusion constraint'ами реально надёжнее всего, и спорить глупо.
Но в 90% случаев с учётом спецтехники/ОС обновления приходят батчами раз в день/неделю из 1С или ERP, а днём только читают тоннами. Вот тут key-value с предвычисленными битмапами просто рвёт PostgreSQL:
Чтение: <1 мс на 1000 активов против 5–15 мс в твоём тесте.
Размер: 25–30 МБ против 150–200+ МБ с индексами.
Сложные вопросы типа «кто был на площадках 10–15 за месяц» — пара операций над битмапами, без подзапросов и window-функций.
Короче, если у вас обновления не онлайн и не критичны по секундам — делайте предрасчёт в key-value, будет в разы быстрее и легче. Если же реально конкурентная запись — оставайтесь на Postgres, там всё честно и без сюрпризов.
Крутая статья, спасибо за детальный разбор с PostgreSQL и daterange — это реально полезный подход для реляционных баз, особенно когда нужно гарантировать целостность данных на уровне БД. Но давай честно: SQL здесь мягко говоря не самый оптимальный вариант, особенно если задача — максимизировать скорость чтения и минимизировать место, а не мучаться с обновлениями и индексами. Я бы предложил перейти на key-value базу данных с предвычисленными данными по дням и использованием Roaring Bitmaps для сжатия множеств активов. Это как раз то, что позволит выжать максимум производительности без компромиссов.
Давай сравню наглядно твой подход с PostgreSQL (на основе твоих тестов) и альтернативный:
По времени исполнения запросов:
- В PostgreSQL: для 333 активов на дату — 3 мс (с BTree-индексом), но это с join'ами, сканированием интервалов и ORDER BY/LIMIT. Если список активов вырастет до 1000, или добавятся фильтры — время легко уйдёт в 5-10 мс+ из-за overhead'а индексов.
- В key-value с Roaring: один точечный lookup по ключу (дню) — <1 мс даже для 1000 активов. Почему? Данные предвычислены: загружаешь один blob (~2-3 КБ), десериализуешь в массив из 100 битмапов, и для каждого актива просто проверяешь contains() в O(1). Нет join'ов, нет сканирования — чистая скорость, как в кэше.
По объёму занимаемого места:
- В PostgreSQL: 1.27 млн записей + индексы (GIST и BTree) — легко 100-200 МБ+ на диске, особенно с overhead'ом на строки, даты и проверки целостности. Индексы сами по себе жрут место и замедляют вставки.
- В key-value с Roaring: вся история (~9500 дней) укладывается в <30 МБ. Каждый день — один ключ с сжатым blob'ом из 100 Roaring Bitmaps (по 20-30 байт на площадку, т.к. в среднем 10 активов на ней). Сжатие Roaring бьёт любые таблицы: нет дублирования дат, нет строк — чистые биты.
По сложности более сложных фильтров:
- В PostgreSQL: базовый запрос на дату — ок, но если добавить фильтры (например, "активы на площадках X и Y за период" или "пересечение с другими условиями")? Придётся городить подзапросы, window functions или даже материализованные views — это усложняет код, замедляет (время вырастет в разы) и требует тюнинга индексов. Целостность круто, но для чтения-ориентированной задачи это overkill.
- В key-value с Roaring: фильтры — это нативные операции над битмапами! Например, пересечение (intersection) двух площадок — bitmap1 & bitmap2 в O(N), где N маленькое. Хочешь активы на нескольких площадках за день? Просто загрузи данные дня и сделай bitwise ops. Для периода — загрузи несколько дней (параллельно, если нужно) и union/intersect. Это проще в коде, быстрее (миллисекунды) и масштабируемо на аналитику без перестройки индексов. Плюс key-value даёт преимущества в поиске: атомарные get'ы без блокировок, лёгкая репликация и кэширование, а Roaring позволяет делать сложные set-операции (union, difference) на лету, чего в SQL без костылей не добьёшься.
В общем, если твоя задача — не частые обновления, а быстрые чтения (как в аналитике или отчётах), то SQL здесь выглядит архаично: медленнее, жирнее и сложнее в расширении.
Мне кажется лучше даже не начинать проек с sql базой. За кажущейся легкостью в начале потом настает час расплаты и много боли. Лучше юзать самую простую бд типа ключ значение и хорошо думать как все индексировать на лету. UUID - dense id связи, roaring bitmap и т.д. "Отшардировать" потом будет тоже проще
А почему не используете ZFS в своей системе? Она позволяет делать "моментальные снэпшоты" с минимальным потреблением ресурсов (чем-то ее работа аналогична гиту). Клиент сможет делать снэпшоты хоть каждую минуту. А для сохранности сами данных, вроде как можно пул настроить так, что репликация будет работать по сети, причем с минимальным потреблением трафика (отправляется только то что изменилось). + сквозная шифрока и компрессия работает. Если я что-то упускаю - было бы интересно почитать ваше мнение.