Налаштування шардування бази даних для веб-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Налаштування шардування бази даних для веб-застосунку
Складний
~1-2 тижні
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1360
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    957
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    948

Зауважте: коли кількість записів у таблиці orders перевалює за 200 мільйонів, а write-навантаження сягає 5000 транзакцій за секунду, PostgreSQL на одному сервері не справляється: latency зростає до 100 мс, checkpoint-и сповільнюються до кількох хвилин, диск переповнений (10 ТБ). Ви вже спробували партиціонування за датою, реплікацію master-slave та кешування з Redis — але write-конфлікти та блокування на запис залишаються. Тоді залишається одне: горизонтальне шардування бази даних. Ми проєктуємо та впроваджуємо такі рішення для веб-застосунків із високим навантаженням. Наш досвід — 50+ проєктів з розподіленими системами, 8 років практики, і ми гарантуємо надійність. Замовте аудит своєї бази даних — ми знайдемо вузькі місця.

Партиціонування vs шардування: що обрати?

Партиціонування розбиває одну таблицю на фізичні частини всередині одного екземпляра PostgreSQL. Шардування розподіляє дані по кількох незалежних серверах. Партиціонування простіше і часто достатньо — починаємо з нього. Згідно з PostgreSQL Documentation, партиціонування рекомендується для таблиць більше 100 ГБ.

-- Range partitioning по даті (логи, події)
CREATE TABLE events (
    id         BIGSERIAL,
    user_id    BIGINT       NOT NULL,
    event_type VARCHAR(50)  NOT NULL,
    created_at TIMESTAMPTZ  NOT NULL,
    data       JSONB
) PARTITION BY RANGE (created_at);

CREATE TABLE events_2024_q1 PARTITION OF events
    FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');

CREATE TABLE events_2024_q2 PARTITION OF events
    FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');

-- Hash partitioning для рівномірного розподілу
CREATE TABLE user_sessions (
    id      BIGSERIAL,
    user_id BIGINT NOT NULL,
    token   VARCHAR(255) NOT NULL,
    data    JSONB
) PARTITION BY HASH (user_id);

CREATE TABLE user_sessions_0 PARTITION OF user_sessions
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);
-- і т.д. до REMAINDER 3

Якщо партиціонування вже не рятує (write-навантаження впирається в ЦПУ, дані не влізають на диск), переходимо до шардування.

Як вибрати ключ шардування?

Ключ шарда — головне архітектурне рішення. Хороші варіанти: user_id для user-centric застосунків, tenant_id для multi-tenant SaaS, region для географічно розподілених даних. Погані варіанти: created_at — hot spot на останньому шарді, status — нерівномірний розподіл, UUID v4 — немає locality, поганий cache hit.

Чому варто використовувати Citus замість саморобного шардування?

Citus — розширення PostgreSQL, що перетворює його на розподілену БД. Воно в 5 разів швидше в розробці порівняно з саморобним шардуванням, оскільки автоматично керує розподілом, ребалансуванням та локалізацією JOIN. Ліцензія Citus Enterprise коштує ~$1000 на місяць, але економія на інфраструктурі може становити $5000 на місяць за рахунок зниження кількості серверів на 30%.

-- Підключаємо воркери
SELECT citus_add_node('worker1', 5432);
SELECT citus_add_node('worker2', 5432);

-- Створюємо розподілену таблицю
CREATE TABLE orders (
    id         BIGSERIAL,
    tenant_id  INT          NOT NULL,
    user_id    BIGINT       NOT NULL,
    status     VARCHAR(20)  NOT NULL,
    total      DECIMAL(12,2),
    created_at TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id, tenant_id)
);

SELECT create_distributed_table('orders', 'tenant_id', shard_count => 32);

-- Таблиця для colocation (JOIN по tenant_id буде локальним)
CREATE TABLE order_items (
    id         BIGSERIAL,
    tenant_id  INT    NOT NULL,
    order_id   BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    quantity   INT    NOT NULL,
    PRIMARY KEY (id, tenant_id)
);

SELECT create_distributed_table('order_items', 'tenant_id', colocate_with => 'orders');

-- Reference table: реплікується на всі воркери
CREATE TABLE categories (id BIGSERIAL PRIMARY KEY, name VARCHAR(200));
SELECT create_reference_table('categories');

Після цього запити з фільтром за tenant_id маршрутизуються на конкретний шард. JOIN між orders і order_items за tenant_id виконується локально на воркері.

Порівняння підходів:

Параметр Citus Саморобне
Час впровадження 2–3 дні 3–5 днів
Складність Низька Висока
Ребалансування Автоматичне Ручне
Підтримка JOIN Локальні + розподілені Тільки локальні з colocation
Вартість ліцензії ~$1000/міс 0

Саморобне шардування: коли повний контроль?

Без Citus (або коли потрібен повний контроль) реалізуємо шардування на рівні застосунку. Використовуємо consistent hashing із 150 віртуальними вузлами — це мінімізує переміщення даних при решардуванні.

# sharding/router.py
import hashlib
from dataclasses import dataclass
from typing import Any

@dataclass
class ShardConfig:
    host: str
    port: int
    database: str

SHARDS: dict[int, ShardConfig] = {
    0: ShardConfig('db-shard-0', 5432, 'myapp_0'),
    1: ShardConfig('db-shard-1', 5432, 'myapp_1'),
    2: ShardConfig('db-shard-2', 5432, 'myapp_2'),
    3: ShardConfig('db-shard-3', 5432, 'myapp_3'),
}
SHARD_COUNT = len(SHARDS)

def get_shard_id(shard_key: Any) -> int:
    key_bytes = str(shard_key).encode('utf-8')
    hash_value = int(hashlib.md5(key_bytes).hexdigest(), 16)
    return hash_value % SHARD_COUNT

def get_shard_config(shard_key: Any) -> ShardConfig:
    return SHARDS[get_shard_id(shard_key)]

Підключення до шардів:

from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from functools import lru_cache

@lru_cache(maxsize=None)
def _get_engine(shard_id: int):
    cfg = SHARDS[shard_id]
    dsn = f"postgresql+psycopg2://user:pass@{cfg.host}:{cfg.port}/{cfg.database}"
    return create_engine(dsn, pool_size=5, max_overflow=10)

@contextmanager
def get_shard_session(shard_key):
    shard_id = get_shard_id(shard_key)
    Session = sessionmaker(bind=_get_engine(shard_id))
    session = Session()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()

Як обробляти запити без ключа шарда?

Запити без ключа шарда — найскладніше. Є два підходи. Scatter-gather паралельно опитує всі шарди: простота реалізації, але latency зростає з кожним новим шардом. Global index зберігає мапінг в окремій БД: lookup швидкий, але overhead при записі вищий. Scatter-gather підходить для рідкісних аналітичних запитів, global index — якщо cross-shard запити трапляються часто.

import asyncio
import asyncpg

async def get_all_orders_by_status(status: str) -> list[dict]:
    async def query_shard(shard_id: int) -> list[dict]:
        cfg = SHARDS[shard_id]
        conn = await asyncpg.connect(host=cfg.host, database=cfg.database, user='app', password='pass')
        rows = await conn.fetch("SELECT * FROM orders WHERE status = $1 ORDER BY created_at DESC LIMIT 100", status)
        await conn.close()
        return [dict(r) for r in rows]
    results = await asyncio.gather(*[query_shard(i) for i in range(SHARD_COUNT)])
    all_orders = [o for shard_result in results for o in shard_result]
    all_orders.sort(key=lambda x: x['created_at'], reverse=True)
    return all_orders[:100]

Процес роботи

  1. Аналіз поточного навантаження та bottleneck-ів: вимірюємо write-потік, latency, розмір бази, патерни запитів.
  2. Проєктування схеми: вибір ключа шарда, кількості шардів, стратегії реплікації.
  3. Розробка роутера та міграція даних: реалізація маршрутизації (Citus або application-level), перенесення даних з мінімальним downtime.
  4. Навантажувальне тестування: емулюємо пікове навантаження, перевіряємо latency та пропускну здатність.
  5. Деплой та моніторинг: налаштовуємо алерти на гарячі точки, повільні запити, збої ребалансування.

Решардування: як додати новий шард без простою?

При використанні consistent hashing із віртуальними вузлами (vnodes) переміщується лише ~1/N даних. Citus автоматично перерозподіляє дані викликом citus_rebalance_start(). Без Citus процес складніший: зупиняєте застосунок, перерозподіляєте дані за новим кільцем, оновлюєте конфігурацію роутера. Для мінімізації downtime використовуйте поступовий переїзд із read-only старих шардів.

Що входить у роботу

  • Архітектурна схема розподіленої БД із зазначенням ключів шардів та схеми маршрутизації.
  • Конфігурація шардів (налаштування PostgreSQL, пули з'єднань, моніторинг).
  • Реалізація роутера на рівні застосунку або через Citus.
  • Налаштування моніторингу (Prometheus + Grafana) для відстеження гарячих точок та латентності.
  • Документація з експлуатації та відновлення після збоїв.
  • Навчання команди роботі з розподіленою схемою.
  • Підтримка протягом 30 днів після запуску.
Приклад конфігурації Citus для високонавантаженого SaaS
coordinator: 4 vCPU, 16 GB RAM, SSD
worker1: 8 vCPU, 32 GB RAM, NVMe
worker2: 8 vCPU, 32 GB RAM, NVMe
shard_count: 64
replication_factor: 2

Терміни орієнтовно

Тип роботи Термін
Партиціонування PostgreSQL для існуючої таблиці 1–2 дні
Встановлення та налаштування Citus для нового проєкту 2–3 дні
Application-level шардування (scatter-gather + global index) 3–5 днів
Решардування з consistent hashing 1–2 дні

Вартість розраховується індивідуально. Економія на інфраструктурі за рахунок правильного шардування може сягати 40%. Отримайте консультацію — ми проаналізуємо ваше навантаження та запропонуємо оптимальну архітектуру.

Рекомендуємо також ознайомитися з Consistent hashing та документацією Citus.

Послуги бекенд-розробки: production-grade надійність

На production-сервері о 3:14 ночі черга Laravel Jobs перестала оброблятися — 40 000 необроблених завдань у Redis. Причина: worker упав через memory leak у статичній змінній Eloquent observer, supervisor не перезапустив через misconfigured stopwaitsecs. Ми розбирали такий інцидент на проекті з 500 RPS: діагностика 4 години, фікс — 20 хвилин. Щоб ви не втрачали гроші, пропонуємо послуги бекенд-розробки з акцентом на production-grade надійність — 10+ років досвіду, 50+ проектів, 5 років на ринку. Оцінимо ваш проект за 2 дні.

Які проблеми вирішуємо

N+1 запити: головний вбивця швидкості

N+1 — найпоширеніша причина повільних сторінок у Laravel-додатках. Стандартна історія: сторінка працювала нормально на dev з 10 записами, на production з 10 000 — 8-секундне завантаження.

Laravel Debugbar у dev-оточенні показує кількість запитів. Більше 20 — сигнал для audit.

Model::preventLazyLoading(! app()->isProduction());

Telescope для профілювання: логує всі запити, jobs, mail, notifications з деталізацією. Після впровадження eager loading час завантаження сторінки падає з 8 с до 0.3 с — у 27 разів.

Memory leak у статичних змінних

У Laravel Octane або Swoole додаток тримається в пам’яті між запитами. Статичні змінні не скидаються — призводять до неконтрольованого росту пам’яті. Використовуємо defer-функції та контейнерні біндинги для коректного скидання стану.

Неправильний connection pool

Rails, Laravel, Django відкривають нове з'єднання PostgreSQL на кожен PHP/Python процес. 100 воркерів — 100 з'єднань. PostgreSQL деградує від 200+ активних з'єднань через overhead на управління.

PgBouncer у transaction pooling: 1000 воркерів → 20–50 реальних з'єднань. Це знижує latency на 40% та зменшує витрати на хостинг на 30% — при середній вартості хостингу $2,000/міс економить $600/міс. GIN-індекс для JSONB до 100 разів швидший за B-tree при пошуку.

Як Octane справляється з високим навантаженням?

Laravel Octane (RoadRunner або Swoole) прибирає overhead bootstrap на кожен HTTP-запит. Приріст: 3–8x на синтетичних бенчмарках, 2–4x на реальних додатках. Важливо: не зберігати стан у статичних змінних — застосовуємо це на проектах >1000 RPS.

Як PostgreSQL допомагає уникнути повільних запитів?

Використовуємо composite indexes для WHERE + ORDER BY, partial indexes для фільтрів з високою селективністю, GIN-індекси для JSONB та full-text search. to_tsvector + GIN замість LIKE '%query%' — запобігає seq scan навіть на мільйонах записів. Аналізуємо плани через EXPLAIN ANALYZE та pg_stat_statements.

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

Стек Коли використовувати
Laravel + Octane CRUD, бізнес-логіка, REST/GraphQL API, адмінки
Node.js (Fastify) Realtime WebSocket, streaming, serverless, висока I/O concurrency
Go Високонавантажені мікросервіси (>10k RPS), gRPC, DevOps-інструменти
Django + DRF ML-пайплайни, інтеграція з AI, складна обробка даних
Ruby on Rails Швидкий MVP з багатим екосистемою гемів

Node.js виправданий для realtime: Laravel публікує події в Redis Pub/Sub, Node.js підписується та транслює клієнтам. Go — для goroutines (10k з'єднань на сервер — норма), але розробка повільніша, ніж Laravel.

Чому Redis критичний для продуктивності?

Redis виконує кілька ролей:

Роль Деталі
Кеш Кешування результатів важких запитів, фрагментів HTML
Черги Backend для Laravel Queue / Celery
Session store Distributed sessions в multi-instance оточенні
Pub/Sub Realtime події між сервісами
Rate limiting Sliding window counters для API throttling
Leaderboards Sorted Sets для рейтингів

Redis Cluster для горизонтального масштабування, Sentinel для автоматичного failover. Замовте консультацію щодо оптимізації Redis для вашого проекту.

Що входить в роботу під ключ

  • Архітектурне проектування (документація API, схема БД, діаграма сервісів)
  • Реалізація за узгодженим ТЗ з code review
  • Налаштування CI/CD (GitHub Actions, Docker), моніторингу (Sentry, Grafana), алертингу
  • Навантажувальне тестування (k6, wrk) зі звітом
  • Передача вихідних кодів, доступів, інструкція з деплою
  • Навчання команди замовника (2–3 сесії)
  • Гарантійна підтримка 1 місяць після здачі

Орієнтири по термінах

Задача Термін
REST API для мобільного/SPA (середня складність) 6–12 тижнів
Backend зі складною бізнес-логікою + інтеграції 12–20 тижнів
Високонавантажений сервіс на Go 8–16 тижнів
Міграція legacy PHP на Laravel 16–32 тижні

Вартість розраховується індивідуально після аналізу вимог до навантаження, інтеграцій та бізнес-логіки. Зв'яжіться з нами для безкоштовного аудиту вашого поточного backend — отримайте план оптимізації за 2 дні. Замовте консультацію та дізнайтеся, як знизити витрати на інфраструктуру на 30% без втрати продуктивності.