Что общего у партиционирования с шардированием и чем они отличаются: достоинства, недостатки, границы применения и примеры реализации этих подходов в реляционных и нереляционных базах данных.
Ускорение работы БД через сокращение объема данных
Как бы мы не ускоряли код приложения, например, используя низкоуровневый язык программирования, многопоточные и асинхронные вычисления, и пр., база данных (БД) все равно останется узким местом любой системы, которая ограничивает ее производительность. Поэтому для ускорения работы информационной системы необходимо ускорить хранилище. Для этого есть довольно много приемов, от выбора оптимальной структуры хранения данных и вычислительного движка до тюнинга запросов и настройки параметров оптимизатора системы управления базой данных (СУБД). Однако, не все эти способы применимы на уже работающей системе. Например, не всегда есть возможность перекроить структуру таблиц, которые уже содержат данные. Более того, часто бывает так, что эта структура вполне подходящая для большинства вариантов использования системы.
Но это утверждение справедливо до определенного момента, пока данных не стало слишком много. Объем данных напрямую влияет на скорость работы с ними, т.к. в реляционных и колоночных БД полное сканирование таблицы зачастую является одной из самых ресурсоемких операций. Оно требует последовательного чтения всего набора данных, что приводит к высоким затратам дискового ввода-вывода, чрезмерному потреблению процессорных ресурсов и оперативной памяти. Поэтому самым простым решением проблемы долгого чтения большого объема данных является его уменьшение, т.е. разделение большого набора данных на несколько более мелких. Это делается следующими способами:
- партиционирование;
- шардирование.
Оба подхода можно рассматривать как способы горизонтального масштабирования, повышения производительности и доступности БД за счет разделения больших объемов данных на более мелкие логические или физические части. Однако, несмотря на общий принцип, они довольно сильно отличаются друг от друга в деталях реализации и областях применения. Рассмотрим каждый из этих способов по порядку.
Партиционирование
Партицонирование (partitioning) или секционирование – это разделение большой таблицы на разделы (партиции, partition), обычно в рамках одной физической БД и на одном сервере. Размещение данных по разделам производится на основании выбранного ключа партиционирования. Например, PostgreSQL из коробки поддерживает разделение по диапазонам, по списку и по хэшу. Исходная, т.е. разделенная большая таблица является виртуальной, т.е. логическим, а не физическим объектом хранения. При этом обращение идет к исходной таблице: СУБД сама находит нужный раздел, чтобы вставить туда данные, удовлетворяющие условию партиционирования. Если строка перестала удовлетворять этому условию, СУБД перемещает ее в другой раздел. При этом пользователю, т.е. разработчику приложения или аналитику, работающему с БД, совсем не обязательно знать о том, в каком именно разделе хранятся нужные данные.
Разделение указывается при создании таблицы, в DDL-запросе CREATE TABLE. Например, создадим таблицу в PostgreSQL таблицу для хранения данных о продажах, разделив их по месяцам:
CREATE TABLE sales ( id SERIAL PRIMARY KEY, customer INT NOT NULL, sale_date DATE NOT NULL, amount DECIMAL NOT NULL ) PARTITION BY RANGE (sale_date);
Создадим несколько разделов для хранения данных за 3 месяца 1-го квартала 2025 года:
CREATE TABLE sales_2025_01 PARTITION OF sales FOR VALUES FROM ('2025-01-01') TO ('2025-02-01'); CREATE TABLE sales_2025_02 PARTITION OF sales FOR VALUES FROM ('2025-02-01') TO ('2025-03-01'); CREATE TABLE sales_2025_03 PARTITION OF sales FOR VALUES FROM ('2025-03-01') TO ('2025-04-01');
Когда раздел стал ненужным, его можно удалить, используя команду DROP TABLE или убрать раздел из исходной таблицы, сохранив возможность обращаться к ней как к самостоятельной таблице. В PostgreSQL это делается с помощью инструкции ALTER TABLE. Например, удалим раздел с данными за январь 2025 года из исходной таблицы, сохранив их как отдельную таблицу sales_2025_01:
ALTER TABLE sales DETACH PARTITION sales_2025_01;
Партиционирование поддерживается не только в PostgreSQL. Например, в колоночной СУБД ClickHouse тоже есть способ оптимизации и работает аналогичным образом. Правда, он работает не для всех движков таблиц, которых в ClickHouse около 20, но это тема отдельного разговора. Вообще большинство современных СУБД поддерживают партиционирование «из коробки», но не все. Например, реляционная резидентная SQLLite не позволяет разделить 1 таблицу на несколько более мелких, т.к. в принципе не предназначена для работы с большими объемами данных. А колоночная in-memory DuckDB, о которой я тоже писала здесь, поддерживает выражение PARTITION BY для разделения данных.
Таким образом, партиционирование как способ повышения скорости работы БД имеет следующие достоинства:
- простота реализации и использования;
- распространенность;
- управление на уровне СУБД.
Однако, этот метод по-прежнему ограничивается ресурсами одного сервера и не повышает отказоустойчивость системы в целом, т.к. в случае сбоя сервера, все разделы становятся недоступны. Поэтому партиционирование подходит для проектов, когда объем данных в некоторых таблицах уже довольно велик и постоянно растет, но еще не требует масштабирования на множество серверов. Например, для таблицы в PostgreSQL партиционирование может быть уместно при объеме данных свыше 10 ГБ или несколько десятков миллионов строк. Когда данных становится больше, возможностей сервера уже не хватает. И в этом случае имеет смысл рассматривать шардирование, о котором поговорим далее.
Шардирование
Шардирование (sharding) – это разделение данных по нескольким серверам (шардам), каждый из которых хранит часть данных. В отличии от партиционирования, обычно управляется на уровне приложения или специализированного промежуточного программного обеспечения (middleware), требует дополнительной логики маршрутизации запросов и управления узлами. Каждый шард представляет собой независимую базу данных, хранящую часть данных. Поэтому при шардировании данные распределены по нескольким физическим серверам согласно заданному ключу. Решение, на какой шард записать данные или откуда их считать, принимает клиентское приложение или специальный маршрутизатор.
Хотя шардирование реализует подход горизонтального масштабирования, когда емкость кластера расширяется путем добавления очередного узла, у этого метода есть 2 варианта: горизонтальное и вертикальное.
При горизонтальном шардировании большая таблица делится по горизонтали, т.е. каждый шард содержит одинаковую структуру (набор столбцов), но разные строки данных. Например, данные о клиентах из столичного региона хранятся на одном шарде, а из других городов – на другом. Это легко масштабируется благодаря простому добавлению новых шардов при росте нагрузки. Распределение данных по разным серверам повышает производительность, отказоустойчивость и доступность системы. Однако, для эффективной утилизации ресурсов следует выбирать такой ключ шардирования, чтобы обеспечить равномерное распределение данных по шардам.
При вертикальном шардировании большая и широкая таблица делится по вертикали так, что часть столбцов хранится на одном шарде, а часть – на другом. Благодаря такому поколоночному делению можно оптимизировать расходы на хранение данных. Например, столбцов с данными, которые используются часто и требуют высокой скорости чтения-записи, можно хранить на более дорогих быстрых и производительных накопителях (SSD, NVMe), а менее востребованные, архивные или редко используемые столбцы – размещать на шардах с дешевыми и медленными дисками (HDD или облачное хранилище). Однако, при вертикальном шардировании следует избегать кросс-шардовых запросов, которые работают с данными, размещенными на разных серверах, т.к. они будут сложнее и медленнее. Кроме того, кросс-шардовые запросы обычно требуют консолидации результатов на уровне приложения, дополнительно увеличивая задержку.
Таким образом, шардирование в целом дает следующие преимущества:
- возможность горизонтального масштабирования за счет добавления новых серверов;
- повышение отказоустойчивости и доступности системы в целом, т.к. при сбое одного шарда остальные продолжают работать;
- гибкие возможности эффективной утилизации ресурсов и затрат на инфраструктуру.
Однако, для этого способа повышения производительности БД характерна высокая сложность реализации и обслуживания. Поэтому применять его следует в системах с очень большим объемом данных, высокими показателями нагрузки и необходимости горизонтального масштабирования, когда возможностей одного сервера для работы с данными уже недостаточно.
Например, когда объем данных превышает 1 Тб, количество записей в таблице более сотни миллионов, а нагрузка стремительно и постоянно растет. Поэтому в NoSQL-хранилищах, изначально спроектированных для работы с большими объемами данных, шардирование обычно поддерживается из коробки. Например, в колоночной СУБД Clickhouse для работы с данными, распределенными по разным шардам, используется специальный табличный движок Distributed, который не обеспечивает хранение данных, а маршрутизирует запросы на шардированные таблицы с последующей обработкой результатов. Данные распределяются согласно ключу шардирования, основанном на хэш-функции конкретного поля, диапазоне значений или вычисляемом динамически, в зависимости от нагрузки. Это указывается в DDL-запросе на создание таблицы:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = Distributed(cluster, database, table[, sharding_key[, policy_name]]);
В реляционных БД шардирование обычно не поддерживается, т.к. изначально они предназначены для OLTP-нагрузок с не очень большими объемами данных. Например, PostgreSQL из коробки не поддерживает шардирование. Но есть сторонние решения, которые могут добавить в PostgreSQL возможности горизонтального масштабирования, позволяя распределять данные и запросы по множеству узлов. Например, расширение Citus, которое преобразует отдельные экземпляры PostgreSQL в распределенный кластер, размещая данные по шардам согласно заданному ключу шардирования. Впрочем, установка Citus не превращает PostgreSQL в Greenplum, поскольку оба решения имеют разные архитектурные подходы и оптимизированы для различных типов рабочих нагрузок. Подробнее об этом я рассказываю здесь, в блоге нашей Школы Больших Данных.
Про это и другие тонкости работы с базами данных я рассказываю на моих курсах Школы прикладного бизнес-анализа и проектирования ИС в нашем лицензированном учебном центре обучения и повышения квалификации системных и бизнес-аналитиков в Москве: