Проектування схеми бази даних веб-застосунку

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

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

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

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

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

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

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

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

  • 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

Проектування схеми бази даних веб-застосунку

Схема бази даних — фундамент, який найважче змінювати після запуску. Неправильно нормалізовані таблиці, відсутні FK або невірні типи даних перетворюються на технічний борг, який накопичується роками. Ми стикалися з цим десятки разів: коли проект зростає, а база тріщить по швах — перепроектування обходиться в рази дорожче. На одному проекті клієнт втратив два місяці через відсутність індексів — запити виконувалися по 30 секунд. Після впровадження правильної схеми все полетіло за 50 мілісекунд. У середньому, рефакторинг схеми займає 2-3 дні, але запобігає місяцям переробок. Ми гарантуємо, що після нашої роботи запити стануть у десятки разів швидше.

Основні принципи та типові проблеми

У 80% проектів ми знаходимо одні й ті ж помилки: N+1 запити через відсутність зовнішніх ключів, фрагментація індексів при використанні випадкових UUID, втрата точності у фінансових розрахунках через FLOAT і магічні числа замість enum-ів. Для більшості застосунків достатньо третьої нормальної форми (3NF): кожне поле залежить тільки від первинного ключа, без транзитивних залежностей. Нормалізація допомагає уникнути надлишковості та аномалій оновлення.

Денормалізація виправдана для лічильників (comments_count, likes_count) — замість COUNT JOIN при кожному запиті. Також для кешованих агрегатів (суми замовлень за місяць) і flatten ієрархічних даних для пошуку. Шкідлива, коли дублюються персональні дані або статуси, які часто змінюються.

Чому вибір типу даних — критичне рішення?

Помилки в типах даних — одна з найчастіших причин проблем у продакшені. Ось типові антипатерни:

-- Погано
user_id   INT              -- переповниться на 2.1 млрд записів
price     FLOAT            -- втрати точності при фінансових обчисленнях
status    INT              -- магічні числа, немає domain constraint
created   VARCHAR(30)      -- сортування рядків замість дат
settings  TEXT             -- немає структури, немає індексу

-- Добре
user_id   BIGINT           -- або UUID
price     DECIMAL(12, 2)   -- точна арифметика
status    VARCHAR(20) CHECK (status IN ('draft', 'published', 'archived'))
created   TIMESTAMPTZ      -- з timezone
settings  JSONB            -- структурований, індексований

TIMESTAMPTZ зберігає час в UTC і конвертує при читанні згідно TimeZone сесії. TIMESTAMP зберігає "як є" — при зміні timezone сервера дані втрачають сенс.

Тип даних Рекомендація
INT BIGINT для PK
FLOAT DECIMAL
VARCHAR без CHECK VARCHAR з CHECK

Як вибрати первинний ключ: BIGSERIAL чи UUID?

-- SERIAL (автоінкремент): просто, компактно (8 байт), передбачувано
id BIGSERIAL PRIMARY KEY

-- UUID v4: унікально глобально, але 16 байт, випадковий порядок = фрагментація індексу
id UUID PRIMARY KEY DEFAULT gen_random_uuid()

-- ULID через pg_ulid або генерацію на стороні застосунку:
-- лексикографічно сортується за часом, 16 байт
id UUID PRIMARY KEY DEFAULT uuid_generate_v7()  -- PostgreSQL 17+

Для більшості веб-застосунків BIGSERIAL — оптимальний вибір. UUID потрібен, коли ID генеруються на стороні клієнта або потрібно приховати передбачуваність.

Тип Розмір Продуктивність запису Глобальна унікальність
SERIAL (8 байт) 8 байт Висока Ні
UUID v4 16 байт Середня (фрагментація) Так
ULID 16 байт Висока (впорядкований) Так

Як уникнути типових помилок при проектуванні схеми?

Ключ до успіху — заздалегідь продумати патерни доступу. Якщо застосунок часто читає кошик з товарами, використовуйте агрегатні поля та уникайте глибоких JOIN. Для історичних даних (замовлення) навмисно денормалізуйте unit_price, щоб зберегти знімок ціни. Завжди перевіряйте, чи підтримує вибраний PK діапазон зростання — BIGSERIAL покриває 9.2 квінтильйона записів, чого вистачить на десятиліття.

Приклад: схема інтернет-магазину

CREATE TABLE categories (
    id         BIGSERIAL PRIMARY KEY,
    name       VARCHAR(200)  NOT NULL,
    slug       VARCHAR(220)  NOT NULL UNIQUE,
    parent_id  BIGINT        REFERENCES categories(id) ON DELETE SET NULL,
    sort_order INT           NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

CREATE TABLE products (
    id              BIGSERIAL PRIMARY KEY,
    title           VARCHAR(500)   NOT NULL,
    slug            VARCHAR(520)   NOT NULL UNIQUE,
    category_id     BIGINT         NOT NULL REFERENCES categories(id) ON DELETE RESTRICT,
    price           DECIMAL(12, 2) NOT NULL CHECK (price > 0),
    status          VARCHAR(20)    NOT NULL DEFAULT 'draft'
                    CHECK (status IN ('draft', 'published', 'archived')),
    stock           INT            NOT NULL DEFAULT 0 CHECK (stock >= 0),
    specs           JSONB,
    search_vector   TSVECTOR,                -- для full-text search
    created_at      TIMESTAMPTZ    NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ    NOT NULL DEFAULT NOW()
);

CREATE TABLE orders (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT       NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
    status      VARCHAR(20)  NOT NULL DEFAULT 'pending'
                CHECK (status IN ('pending', 'paid', 'shipped', 'completed', 'cancelled')),
    total       DECIMAL(12, 2) NOT NULL,
    currency    CHAR(3)      NOT NULL DEFAULT 'USD',
    meta        JSONB,                       -- delivery address тощо
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE TABLE order_items (
    id          BIGSERIAL PRIMARY KEY,
    order_id    BIGINT         NOT NULL REFERENCES orders(id)   ON DELETE CASCADE,
    product_id  BIGINT         NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
    quantity    INT            NOT NULL CHECK (quantity > 0),
    unit_price  DECIMAL(12, 2) NOT NULL,     -- ціна на момент покупки
    UNIQUE (order_id, product_id)
);

unit_price — навмисна денормалізація: ціна продукту зміниться з часом, а історична ціна в замовленні повинна залишитися незмінною.

ON DELETE RESTRICT vs CASCADE — правило: CASCADE тільки коли дочірні записи не мають сенсу без батька (order_items без order). RESTRICT, коли видалення батька має бути явно запобіжено (не можна видалити категорію з товарами).

Які індекси створювати одразу та soft delete

Додаємо одразу при створенні схеми:

-- FK columns — завжди, інакше DELETE батька = seq scan дочірньої таблиці
CREATE INDEX idx_products_category_id  ON products (category_id);
CREATE INDEX idx_order_items_order_id  ON order_items (order_id);
CREATE INDEX idx_order_items_product_id ON order_items (product_id);

-- Часті фільтри
CREATE INDEX idx_products_status_created ON products (status, created_at DESC);
CREATE INDEX idx_orders_user_created     ON orders (user_id, created_at DESC);

-- Partial index для активних записів
CREATE INDEX idx_products_published ON products (category_id, created_at DESC)
    WHERE status = 'published';

-- GIN для JSONB
CREATE INDEX idx_products_specs ON products USING GIN (specs);

Патерн soft delete:

-- Soft delete
ALTER TABLE products ADD COLUMN deleted_at TIMESTAMPTZ;
CREATE INDEX idx_products_deleted_at ON products (deleted_at) WHERE deleted_at IS NULL;

-- Audit table
CREATE TABLE audit_log (
    id          BIGSERIAL PRIMARY KEY,
    table_name  VARCHAR(100) NOT NULL,
    row_id      BIGINT       NOT NULL,
    operation   CHAR(1)      NOT NULL CHECK (operation IN ('I', 'U', 'D')),
    old_data    JSONB,
    new_data    JSONB,
    changed_by  BIGINT       REFERENCES users(id),
    changed_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

Partial index по WHERE deleted_at IS NULL — активні записи індексуються окремо. Видалені записи не потрапляють в індекс і не сповільнюють запити.

Процес роботи та що входить

  1. Аналітика — виявлення бізнес-сутностей, зв'язків, частих запитів.
  2. Проектування — побудова ER-діаграми, нормалізація, вибір типів.
  3. Створення DDL — SQL-скрипти з індексами, FK, constraints.
  4. Документування — опис схеми, коментарі, guide для розробників.
  5. Аудит — рев'ю існуючої схеми, виявлення проблем, рекомендації.

У результат роботи входить: ER-діаграма (до 15 таблиць) у форматі PlantUML або Draw.io, SQL DDL з індексами та констрейнтами, документація по схемі (README з описом таблиць і полів), рекомендації по міграціях і консультація протягом тижня після здачі.

Строки

Проектування схеми для нового проекту (до 15 таблиць): 1–2 дні. Рев'ю та рефакторинг існуючої схеми: 1–3 дні. Вартість розраховується індивідуально — напишіть нам, і ми оцінимо ваш проект. Замовте проектування схеми — ми врахуємо всі нюанси. Отримайте професійну консультацію, навіть якщо не впевнені в обсязі робіт.

Послуги бекенд-розробки: 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% без втрати продуктивності.