Схемы шардинга в PostgreSQL
Привет, Хабр!
Шардинг — это процесс разделения горизонтальных частей данных на разные БД, или шарды, которые могут располагаться на различных серверах или даже в разных геолокациях. Шардинг не просто режет данные на части, но организует их таким образом, чтобы каждый шард мог быть оптимизирован для выполнения определенного набора задач
Но нужно помнить, что шардинг — это не панацея, и его стоит рассматривать только тогда, когда другие методы масштабирования начинают упираться в свои пределы.
В этой статье кратко рассмотрим то, как реализуются различные схемы шардина в PostgreSQL
Реализация схем шардинга
Горизонтальный шардинг
Горизонтальный шардинг, или шардинг на уровне строк, подразумевает разделение данных одной таблицы на множество шардов, где каждый шард хранится в отдельной базе данных или на отдельном сервере. Данные распределяются на основе определенного ключа шардинга, который может быть, например, ид юзера
Горизонтальный шардинг подходит для большого объема транзакций и операций, где нужна высокая доступность.
Рассмотрим, как можно реализовать горизонтальный шардинг на примере использования триггеров и таблиц-партиций. Партиционирование заключается в разделении одной большой таблицы на несколько меньших таблиц, называемых партициями, которые логически представляют собой одну таблицу.
Допустим, есть таблица с заказами orders
, которую нужно партиционировать по годам:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
order_date DATE NOT NULL,
customer_id INT NOT NULL,
amount DECIMAL NOT NULL
) PARTITION BY RANGE (order_date);
CREATE TABLE orders_2020 PARTITION OF orders FOR VALUES FROM ('2020-01-01') TO ('2021-01-01');
CREATE TABLE orders_2021 PARTITION OF orders FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');
-- и так далее для каждого года
При вставке данных в таблицу orders
, PostgreSQL автоматически направит данные в соответствующую партицию на основе значения order_date
.
Если есть таблица пользователей, которую нужно партиционировать по стране пользователя, можно сделать это следующим образом:
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
country TEXT NOT NULL
) PARTITION BY LIST (country);
CREATE TABLE users_usa PARTITION OF users FOR VALUES IN ('USA');
CREATE TABLE users_canada PARTITION OF users FOR VALUES IN ('Canada');
CREATE TABLE users_germany PARTITION OF users FOR VALUES IN ('Germany');
-- и так далее для каждой страны
В случаях, когда более гранулярное управление шардингом предпочтительно, можно использовать триггеры для распределения данных по шардам:
CREATE FUNCTION orders_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF (NEW.order_date < '2021-01-01') THEN
INSERT INTO orders_2020 VALUES (NEW.*);
ELSIF (NEW.order_date >= '2021-01-01' AND NEW.order_date < '2022-01-01') THEN
INSERT INTO orders_2021 VALUES (NEW.*);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER orders_before_insert
BEFORE INSERT ON orders
FOR EACH ROW EXECUTE FUNCTION orders_insert_trigger();
Перед вставкой в таблицу orders
триггер проверяет дату заказа и перенаправляет запись в соответствующую партицию.
Вертикальный шардинг
Вертикальный шардинг включает разделение таблицы по столбцам, где каждый шард содержит определенный набор столбцов исходной таблицы. В отличие от горизонтального шардинга, который разделяет строки, вертикальный шардинг делит данные на основе функциональности или контекста данных.
Подходит для приложений, где отдельные модули или функции работают с разными наборами данных.
Предположим, есть таблица users
, которая содержит большое количество столбцов, некоторые из которых используются гораздо чаще, чем другие. Для оптимизации можно разделить эту таблицу на две: одна будет содержать часто используемые данные например, user_id
, name
, email
, а другая — менее часто используемые данные например, address
, phone_number
и т.д.
-- Таблица для часто используемых данных
CREATE TABLE users_common (
user_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
);
-- Таблица для менее часто используемых данных
CREATE TABLE users_details (
user_id INTEGER NOT NULL REFERENCES users_common(user_id),
address TEXT,
phone_number TEXT,
CONSTRAINT pk_users_details PRIMARY KEY (user_id)
);
Для реализации вертикального шардинга на нескольких серверах можно использовать FDW, который позволяет PostgreSQL взаимодействовать с данными, хранящимися за пределами текущей базы данных. Используя FDW, можно создать логическое разделение столбцов на разных серверах.
Допустим, хочется хранить детальную информацию о пользователях на отдельном сервере.
Устанавливаем расширение
postgres_fdw
на основном сервере:
CREATE EXTENSION postgres_fdw;
Создаем серверную конфигурацию для доступа к внешнему серверу:
CREATE SERVER foreign_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'foreign_host', dbname 'foreign_db', port '5432');
Создаем пользовательское отображение для внешнего сервера:
CREATE USER MAPPING FOR local_user
SERVER foreign_server
OPTIONS (user 'foreign_user', password 'foreign_password');
Создаем внешнюю таблицу, которая будет представлять собой вертикальный шард на внешнем сервере:
CREATE FOREIGN TABLE users_details_foreign (
user_id INTEGER NOT NULL,
address TEXT,
phone_number TEXT
) SERVER foreign_server
OPTIONS (schema_name 'public', table_name 'users_details');
Функциональный шардинг
Функциональный шардинг — это подход, при котором данные разделяются и распределяются на основе бизнес-логики или функциональности приложения. Т.е разделение данных по типам пользователей, гео регионам и т.п
Например, разделим данные пользователей и заказов по разным схемам в рамках одной базы данных
CREATE SCHEMA users_data;
CREATE SCHEMA orders_data;
Создадим таблицы в соответствующих схемах
-- Таблица пользователей в схеме users_data
CREATE TABLE users_data.users (
user_id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
);
-- Таблица заказов в схеме orders_data
CREATE TABLE orders_data.orders (
order_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users_data.users(user_id),
order_date DATE NOT NULL,
amount DECIMAL(10, 2) NOT NULL
);
Для доступа к данным в разных схемах, просто укзазываем полное имя таблицы, включая имя схемы:
-- Вставка данных в таблицу пользователей
INSERT INTO users_data.users (username, email) VALUES ('user1', 'user1@example.com');
-- Вставка данных в таблицу заказов
INSERT INTO orders_data.orders (user_id, order_date, amount) VALUES (1, '2023-01-01', 100.00);
Можно так же юзать FDW:
CREATE EXTENSION postgres_fdw;
Создадим сервер FDW:
CREATE SERVER remote_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'remote_host', dbname 'remote_db', port '5432');
Создадим пользователя:
CREATE USER MAPPING FOR CURRENT_USER
SERVER remote_server
OPTIONS (user 'remote_user', password 'remote_password');
Создадим внешнюю таблицу:
CREATE FOREIGN TABLE users_data.users_remote (
user_id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
) SERVER remote_server OPTIONS (schema_name 'public', table_name 'users');
В завершение хочу порекомендовать бесплатный вебинар курса BI-аналитика.