Pull to refresh

Comments 35

Отличная статья! Я бы только дополнил её описанием составных типов данных для реализации объектных хранилищ и рассказом про PostGIS для реализации геоинформационных систем.

Вот только PostgreSQL, как реляционная СУБД, обязан обеспечивать ACID, что не всегда необходимо и всегда приводит к деградации производительности. И шардирование тут не панацея, так перекрестные внешние ключи между шардами всё равно возникают, как бы не пытались их избежать.

На OLAP нагрузке PostgreSQL выглядит очень бледно, по сравнению с ClickHouse. Но зато его удобно держать единой точкой входа, запрашивая при необходимости агрегаты из ClickHouse через FDW.

А в истории пропустили очень интересные шаги в виде таких, как иерархические (IMS) сетевые (IDMS) и адаптивные (Adabas) СУБД.

Получается колоночного движка в PostgreSQL нет (или есть в какой-то модификации). Или есть - но в статье не упомянут?

Сейчас, пожалуй, противостояние противопоставление колоночных, документных и реляционных баз - самое ключевое! И удобно было бы иметь СУБД - где эти технологи были бы вместе. Конечно геораспределённые данные, векторные и сетевые - это всё тоже интересно и актуально - и здорово тоже иметь поддержку в составе единой СУБД. Но, всё же чаще нужны: колоночные, документальные (как общий случай ключ-значения) и реляционные структуры + гибкость в организации репликации/шардирования!

Получается колоночного движка в PostgreSQL нет

В ванильном нет, в расширениях есть, например, от Citus. Но так как PostrgeSQL ACID, то он обеспечивает консистентность и изоляцию, а ClickHouse на это наплевать. Поэтому последний существенно быстрее, так как может использовать такие технические приёмы, которые при контроле за консистентностью недоступны.

Например, в PostgreSQL уникальный индекс - это действительно запрет дубликатов. В ClickHouse уникальность - это лишь то, что если добавлена запись с таким же ключом, то предыдущие записи с тем же ключом при выборках будут игнорироваться.

PostgreSQL при выборке должен обеспечивать изоляцию снапшотом, а ClickHouse даже не пытается это делать.

противопоставление колоночных, документных и реляционных баз - самое ключевое!

Нет никакого противопоставление. Для разных сценариев от чего то в ACID можно отказываться или нельзя. Отсюда и выбор.

гибкость в организации репликации/шардирования!

Я же писал выше, что в сочетании с консистентностью и атомарностью, шардирование - не панацея.

Колочёное хранение данных в первую очередь нужно и эффективно для у редко изменяемых данных - чтобы повысить скорость чтения и эффективность сжатия. Для такого сценария использования наличие ACID не шибко важно. Насчёт изоляции снапшотами - интересно бы знать насколько это снижает производительность в условно чисто операциях только чтения? И позволяет ли (какое-то расширение, раз в ванильном этого вовсе нет) как-то управлять таким уровне изоляции - уменьшив его хотя бы на уровне всей базы данных. Вот SQL Server такое позволяет вроде бы.

Нет никакого противопоставление. Для разных сценариев от чего то в ACID можно отказываться или нельзя. Отсюда и выбор.

Я же писал выше, что в сочетании с консистентностью и атомарностью, шардирование - не панацея.

Просто ситуация разные бывают - когда бизнес-процессы в большей степени склоняются в с сторону скорости чтения, хранения широких объёмов аналитики или требования ACID - это всё понятно. Но когда нужно сочетания и того и другого (не обязательно по одной таблице, но в раках применения разных таблиц в, условно, одном пакете запроса) - то уже хочется чтобы всё было в одной СУБД. Поэтому сейчас мне стало интересно смотерь в сторону New SQL (которые по природе своей реаляционные с ACID) или некоторых NoSQL (которые пытаются эмулировать ACID.

В сочетании с ACID шардирование может и не даёт особого прироста производительности на запись - но даёт прироста производительности на чтение!

Но и с записью всё не так просто - в ограниченном объёме шардирование даёт и прирост производительности на запись - т.к. нагрузка разных запросов перераспределяется по отдельным узлам (но только когда так идут сами запросы, особенно когда чтение и запись пакетного запрсоа укладывается в одну шарду). Ну если я правильно понимаю как работает шардирование, берущее начало с секционирования таблиц!

С репликацией почти тоже самое! Но... я тут тоже не спец. Если я правильно понимаю, то при репликации ACID не соблюдается в реляционных базах (ну или этим можно в настройка управлять) - т.е. реплика будет отставать от основного узла СУБД. Тогда реплики в операциях чтения с ACID просто не могут использоваться (не знаю - могут ли это динамически ограничивать сами реляционные СУБД) - но если запросу приложения не важна эта ACID - то он может и к реплике обратиться! На реплики как раз может направляться аналитическая нагрузка или какая-то support-нагрузка - когда прям 100% актуальность данных не нужна (или нужна частично - тогда такие данные просто будут получены с основного узла).

То есть в и шарды и реплики в реляционных СУБД вполне себе очень даже полезны - но да - настройка их использования - это не простая тема!

Но будущее, всё-таки, за гибридными СУБД - которые гибко сами могут настраивать как хранить и обрабатывать части своих данных - исходя из заданных требований к качеству сервиса по каждому источнику данных и даже по каждому виду запроса - это задаст уровень надёжности! А приложения сами будут решать - в какой мере им нужно соблюдать ACID, условно, при каждом обращении (ну или на всё соединение задавать профиль по умолчанию)! Ну а сбор статистики поможет оптимизировать производительность и эффективность хранения!

Колочёное хранение данных в первую очередь нужно и эффективно для у редко изменяемых данных - чтобы повысить скорость чтения и эффективность сжатия. Для такого сценария использования наличие ACID не шибко важно.

Так про то и речь, что когда ACID не важен, не используйте реляционную СУБД, которая архитектурно без него жить не может.

Насчёт изоляции снапшотами - интересно бы знать насколько это снижает производительность в условно чисто операциях только чтения?

Зависит от количества строк, созданных после начала текущей транзакции в других транзакциях. Читать их всё равно ведь приходится, отсекая уже по версиям строк в страницах данных (xmin/xmax). Ну и отдельная тема - не замороженные строки и страницы вне карты видимости.

то уже хочется чтобы всё было в одной СУБД

Я выше писал решение: держите PostgreSQL единой точкой входа, запрашивая при необходимости агрегаты из ClickHouse через FDW, там, где консистентность не важна.

В сочетании с ACID шардирование может и не даёт особого прироста производительности на запись - но даёт прироста производительности на чтение!

Далеко не всегда. Если реляционные связи между таблицами оказываются между шардами, то производительность наоборот снизится. А избежать этого на практике невозможно.

берущее начало с секционирования таблиц

С секционированием та же проблема. Как только требуется реляционная связь, не включающая в себя ключ секционирования полностью, производительность резко падает. И приходится применять дополнительные приемы, дублируя данные и тревожа дедушку Кодда.

Но будущее, всё-таки, за гибридными СУБД - которые гибко сами могут настраивать как хранить и обрабатывать части своих данных - исходя из заданных требований к качеству сервиса по каждому источнику данных и даже по каждому виду запроса - это задаст уровень надёжности!

Сомневаюсь. Универсальный комбайн - это всегда компромиссы. Невозможно сделать внедорожник, не уступающий на асфальте гоночному болиду.

Зависит от количества строк, созданных после начала текущей транзакции в других транзакциях. Читать их всё равно ведь приходится, отсекая уже по версиям строк в страницах данных (xmin/xmax). Ну и отдельная тема - не замороженные строки и страницы вне карты видимости.

Ну если так - то снапшоты формально дают почти нулевые потери производительности. Ибо - не будь снапшотов - читать те же строки всё-равно пришлось бы! Если я всё правильно понял.

Я выше писал решение: держите PostgreSQL единой точкой входа, запрашивая при необходимости агрегаты из ClickHouse через FDW, там, где консистентность не важна.

Возможно интересное решение - вот только с FDW я не знаком - можно пояснить

Далеко не всегда. Если реляционные связи между таблицами оказываются между шардами, то производительность наоборот снизится. А избежать этого на практике невозможно.

Это понятно - но тут есть два "решения":

  1. Порой шаржируют на другие узлы данные, которые не требуют высокой производительности - т.е. достаточно холодные данные

  2. Или шардируют прям комплектами - когда на узле есть всё что нужно для большинства сценариев доступа - в т.ч. с дублями - когда эти дубли, конечно, крайне редко меняются - и тут много зависит от возможностей СУБД автоматически управлять таким распределением и синхронизацией - иначе придётся выносить на триггеры или контролировать самому приложению

Если я всё правильно понял.

Неправильно. Версии строк, замороженные строки и карты видимости - это и есть MVCC, средствами которого реализуются снапшоты.

Так как PostgreSQL без снапшотов в принципе не умеет, то сравнивать в нем не получится. Но в том же MS SQL ISOLATION LEVEL READ COMMITTED показывает лучшую производительность выборки данных, чем ISOLATION LEVEL SNAPSHOT, А последний дает преимущество именно при множестве конкурентных обновлений, позволяя снизить количество блокировок и вероятность блокирования процессов друг другом.

Снапшот - всегда издержки. Просто блокировки процессов друг другом - еще большие издержки. Но так как изначально вопрос был о "чисто операциях только чтения" то снапшот тут явно проигрывает.

Возможно интересное решение - вот только с FDW я не знаком - можно пояснить

Может Вам стоит всё же лучше изучить PostgreSQL, прежде чем писать о нем статьи? FDW и конкретно ClickHouse FDW, хотя их немало разных.

холодные данные

Это называется архивной DWH и к шардированию имеет очень слабое отношение, так как принципы присвоения архивного статуса данным определяются бизнесом, а не логикой СУБД.

когда эти дубли, конечно, крайне редко меняются

А когда часто? И каковы издержки ACID будут при дублировании этих данных?

Например, в ERP могут возникать целые сети связанных операций, которые должны ссылаться друг на друга. И предугадать в каком шарде лучше размещать очередную операцию - физически невозможно.

Может Вам стоит всё же лучше изучить PostgreSQL, прежде чем писать о нем статьи?

Я не пишу статьи про PossgreSQL

Снапшот - всегда издержки

Пока не понятно насколько велики эти издержки (чтения) в случаях когда в таблицу не пишут, и в случаях когда пишут / в противовес когда пишу вообще без изоляции?

А когда часто? И каковы издержки ACID будут при дублировании этих данных?

Когда часто ACID на репликах полюбому будет тормозить... и тут можно думать только о минимизации этих издержек. Но это уже особенности базы с поддержкой ACID. Для решения этих проблем и появились New SQL СУБД! и NoSQL если ACID не шибко надо!

Пока не понятно насколько велики эти издержки (чтения) в случаях когда в таблицу не пишут, и в случаях когда пишут

Я уже писал выше, что снапшоты всегда увеличивают издержки, снижая вероятность возникновения блокировок при конкурентном доступе. Что с чем Вы собрались сравнивать? Скорость выборки с временем ожидания в блокировках?

в противовес когда пишу вообще без изоляции?

Такой сценарий для реляционной СУБД вообще не применим.

Когда часто ACID на репликах полюбому будет тормозить

Вот именно. И значит не нужно использовать реляционную СУБД, в которой всегда ACID, там, где от какой-то буквы из ACID можно отказаться. Так же как бессмысленно соревноваться на внедорожнике с гоночным болидом на асфальте.

Я уже писал выше, что снапшоты всегда увеличивают издержки, снижая вероятность возникновения блокировок при конкурентном доступе. Что с чем Вы собрались сравнивать? Скорость выборки с временем ожидания в блокировках?

Я сторонник вершинного подхода.

Тогда для чтения сравнивать скорость чтения с изоляцией SNAPSHOT со скоростью чтению чтения Read_Commited - в таком случае для чтения нет конкуренции блокировок в обоих случаях

Для записи тоже самое - но тут есть конкуренция - но, насколько я понимаю для ворсиной СУБД в режиме Read_Commited не будет блокировки для чтения - но, само собой буду блокировки записи - но этот случай не интересен - если уж столкнулись на обновление - пеняй на себя - решай архитектурным путём - коли важно! Но я всё же больше про чтение говорил, добавление новых записей, и редкое обновление/удаление имеющихся! И хотел понять - насколько буду велики издержки снапшотов!

в противовес когда пишу вообще без изоляции?

Такой сценарий для реляционной СУБД вообще не применим.

Согласен. Немного погорячился.

Вот именно. И значит не нужно использовать реляционную СУБД, в которой всегда ACID, 

Так ведь речь о том, что ACID не всегда нужен - при работе в едином пространстве данных

Я сторонник вершинного подхода.

Я не знаю, что это.

для ворсиной СУБД в режиме Read_Commited не будет блокировки для чтения

Как это не будет? ACCESS SHARE конфликтует с ACCESS EXCLUSIVE. Так что, если данные монопольно заблокированы (например, VACUUM FULL, ALTER INDEX, REINDEX и т.п), то блокировки для чтения будут. И ещё возможны блокировки по ожиданию CPU, ввода-вывода, выделению памяти, получению xid, записи в WAL и т.п.

насколько буду велики издержки снапшотов

Я уже ответил выше. Нужно атомарно получить уникальный строго возрастающий xid (вот уже конкурентная запись), vxid и, возможно, GID. В каждой записи данных необходимо сравнить xid с xmin, а если xmax не нулевой, то еще и с ним. С индексами чуть сложнее и зависит от их типа, но тоже сводится к таким же сравнениям.

Но я не пойму, что значит "насколько"? Что и с чем Вы собрались сравнивать? Теплое с мягким?

Так ведь речь о том, что ACID не всегда нужен

Вот и не пользуйтесь реляционной СУБД в тех случаях, когда Вам не нужен ACID.

В том же ClickHouse, Вы вполне с удивлением сможете иногда получать разные значения x и y в одном запросе вида:

WITH Xdata AS (SELECT COUNT(1) AS x FROM Some_Table),
  Ydata AS (SELECT COUNT(1) AS y FROM Some_Table)
SELECT x, y
FROM Xdata
CROSS JOIN Ydata;

В PostgreSQL x всегда будет равен y.

Я сторонник вершинного подхода.

Я не знаю, что это.

Это опечатка. Имелась в виду версионная модель доступа к данным

Как это не будет? ACCESS SHARE конфликтует с ACCESS EXCLUSIVE. Так что, если данные монопольно заблокированы (например, VACUUM FULL, ALTER INDEX, REINDEX и т.п), то блокировки для чтения будут. И ещё возможны блокировки по ожиданию CPU, ввода-вывода, выделению памяти, получению xid, записи в WAL и т.п.

Я согласен. Но в версионном режиме доступа к данным блокировки записи не блокируют чтение. Если я что-то забыл... то и фиг с ним - изменения или добавления, в данном случае, операция не шибко долгая - в отличии от возможной блокировки намеренья изменения!

Я уже ответил выше. Нужно атомарно получить уникальный строго возрастающий xid (вот уже конкурентная запись), vxid и, возможно, GID. 

Насколько я знаю - для этого есть неблокирующие алгоритмы (когда не требуется строгая упорядоченность - а она зло). Да и если и нужна конкурентная блокировка - её время просто незначительно!

Но опять же - это блокировки добавления - не влияющие на чтение!

Но в версионном режиме доступа к данным блокировки записи не блокируют чтение.

Я Вам целый список блокировок привел, от которых версионность не спасает. Блокировки в ожидании освобождения памяти или окончания ввода-вывода я регулярно вижу.

операция не шибко долгая

Бывало, когда кривой запрос только на чтение съедал всю память и его сотни запросов ожидало десятки минут. Это не долго?

для этого есть неблокирующие алгоритмы (когда не требуется строгая упорядоченность 

Пруф?

Да и если и нужна конкурентная блокировка - её время просто незначительно!

Вот только выбор максимального CSN в Shardman заметно тормозит, по сравнению с монолитным PostgreSQL.

Все ваши примеры понятно - но это всё уже из "другой оперы" - условно не рядовой процесс или неизбежность для всех случаев!

для этого есть неблокирующие алгоритмы (когда не требуется строгая упорядоченность

Всё же жду пруф. Нагуглить мне это не удалось, а сам я не могу придумать, как обеспечить строго возрастающие значения для процессов на множестве ядрах. Пусть даже без строгой упорядоченности (с пропусками).

Я как раз пометил - что только для случая когда не требуется строгая упорядоченность, а только уникальность, ну или ещё не строгая, но общая упорядоченность - это большинство случаев

не требуется строгая упорядоченность

Для версионирования строгой не требуется, пропуски вполне допустимы

только уникальность

А так версионировать транзакции не получится

общая упорядоченность

Вот на это пруф и прошу. Как версионировать транзакции в реляционной СУБД без блокируемого счетчика xid?

Вот на это пруф и прошу. Как версионировать транзакции в реляционной СУБД без блокируемого счетчика xid?

Последовательности не катят?

Или тип SERIAL?

Ну или... простите код писать лень. Только общая идея:

Имеем таблицу со свободными номерами некоторого количества (не принципиально, но заведомо больше чем максимум потребляется за период её обновления), номера упорядочены по кластерному индексу (по возрастанию для определённости).

Когда нужен номер - берём первый меньший номер и удаляем строку с ним. Тут есть нюанс - мы должны делать такое чтение с уровне изоляции READ_UNCOMMITTED - чтобы не блокироваться на незавершённых транзакциях. Возможно для этого потребуется отельное соединение с СУБД для организации отдельного уровня изоляции транзакции (но PostreeSQL такой уровень изоляции не поддерживает - но там есть обходные пути)

Периодически добавляем в таблицу новые строки с новыми номерами от максимально до заданного количества

Я в шоке. Вы предлагаете начать транзакцию выполнением предложения который вернёт xid транзакции, который должен быть известным до начала транзакции.

Нет я такого не предлагал - когда получать XID решать только алгоритму

А тогда почему пишете о SERIAL, таблицах, уровнях изоляции? Всё это становится доступно только после начала транзакции, а значит получения строго возрастающего xid.

Я уже писал выше. И Вы утверждали, что знаете ответ.

Про проблемы с транзакциями не понял

А с атомарными операциями в СУБД вообще беда

Вы вообще знаете, как в PostgreSQL реализован MVCC? А в курсе, что обозначает первая буква в ACID?

Я же Вас не про атомарность спрашивал

Хорошо, разверну подробней:

Про проблемы с транзакциями не понял

Вы вообще знаете, как в PostgreSQL реализован MVCC?

А с атомарными операциями в СУБД вообще беда

А в курсе, что обозначает первая буква в ACID?

Про MVCC PostgreSQL  знаю в общи чертах - каждое изменение записи - это новая запись - новая версия

A в ACID - это атомарность - но то что с ней беда в PostgreSQL никуда не девается - потому что есть много операций которые с ACID требуют выполнения в составе транзакции с излишним уровнем изоляции - что снижает эффективность когерентности из-за лишних продолжительных блокировок

В таком случае предлагаю закончить эту бессмысленную беседу и продолжить её после того, как изучите реализацию MVCC в PostgreSQL и разберетесь, чем первая буква в ACID отличается от третьей.

PostgreSQL не предназначен для работы с OLAP сценариями (как раз из за ACID, как писали в комментарии выше). PostgreSQL это только про OLTP. Это, к сожалению, фундаментальное ограничение PostgreSQL.

Так что если нужна сложная аналитика, то тут и вправду лучше ClickHouse взять.

Без OLAP смысла "Один за всех" тогда и вовсе нет!

Кстати, я бы не стал отождествлять нагрузку OLTP и ACID - они не сочетаются 100% - т.к. ACID снижает производительность записи - а от OLTP как раз требуется высокая производительность и записи и чтения, но, и консистетности тоже! Поэтому для OLTP - это требуются куда более сложные гибридные схемы - чем те, что есть обычных реляционных СУБД! И тут рулиn уже стиль In memory database - и кластеризация с контролем консистентности со стороны приложения, а не со стороны СУБД!

Ну как сказать. Те "нереляционные" возможности, которые у Постгреса есть, уже весьма и весьма существенно жизнь упрощают. BSON, составные типы данных и PostGIS я в своей практике регулярно использую. TimescaleDB не пробовал - мне пока стандартного сегментирования хватает. Так что и на том спасибо.

А ограничение по OLAP лучше разруливать на более высоком архитектурном уровне. Разделять хранилища по типу нагрузки. Даже если бы Постгрес умел готовить OLAP, то я бы всё равно не стал одну и ту же базу одновременно для OLTP и OLAP использовать - разделил бы на 2 хранилища как минимум. Потому что для OLTP важно малое время отклика, а для OLAP нет. А злой аналитический запрос запросто может просадить производительность всей СУБД, и увеличить время отклика для OLTP операций.

Думал будет что то новенькое, ан нет, все уже давно известное. Не понимаю смысл писать одно и то же по сто раз.

Sign up to leave a comment.