3

WebTech

Личный кабинет

Проекты, коммерческие предложения, документы и отчёты — в одном месте.

Войти Зарегистрироваться Забыли пароль?
Схема, которая выдержит рост данных

Схема базы данных, которая выдержит рост без переписывания проекта

Проектирование и оптимизация баз данных: PostgreSQL и MySQL, индексы и планы запросов, репликация, кэш на Redis и проверенное резервное копирование.

Схема под ваши запросы
Проектируем от реальных выборок, а не «на будущее»
Проверенное восстановление
Бэкап считается рабочим после теста на восстановление
План запроса до и после
Оптимизация подтверждается EXPLAIN, а не на глаз
Схема и миграции в репозитории
Изменения структуры проходят ревью и откат

Ключевые моменты

  • Проектируем схему под реальные запросы, а не «на будущее»: нормализация там, где она помогает
  • Индексы и планы запросов: находим и убираем медленные выборки до того, как они станут проблемой
  • Репликация для чтения и разделение нагрузки между мастером и репликами
  • Redis для кэша, очередей и сессий — снижаем давление на основную базу
  • Партиционирование и архивация больших таблиц вместо бесконечного роста
  • Резервное копирование с проверкой восстановления, а не «бэкап, который никто не тестировал»
Задачи

Что происходит с базой, когда проект растёт

Почти все проблемы с базами данных проявляются одинаково: сначала «стало медленнее», потом «непонятно, что с этим делать». Ниже — типовые ситуации, с которыми приходят на аудит, и к чему они приводят.

База выросла — списки начали тормозить

На тестовых данных всё летало, а в продакшене появились сотни тысяч строк, и отчёты стали занимать секунды. Часто причина не в «слабом сервере», а в выборке, которая читает таблицу целиком, и в отсутствии нужного индекса.

Сначала это замечают пользователи, потом — руководство

Индексы наставлены наугад

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

Запись медленнее, база больше, эффекта нет

Резервная копия есть, но не проверялась

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

Риск реализуется ровно тогда, когда дороже всего

Изменения схемы «на живую»

Структуру правят прямо в продакшене, потому что «надо быстро». Никто не помнит, какие изменения уже применены на каждом стенде, локальная база разработчика расходится с боевой, а откатить релиз нечем.

Разные окружения живут по разным правилам

Схема не защищает данные

Дубли строк, отрицательные остатки, ссылки на несуществующие записи — всё это ловится уже в отчётах или у клиента. Ограничения на уровне базы не заведены, поэтому приложение остаётся единственным «контролёром», а он не всегда прав.

Данные, которым нельзя доверять

Непонятно, что происходит с базой

Нет ответа на простые вопросы: сколько живёт самый долгий запрос, растут ли таблицы, успевает ли реплика, когда последний раз обновлялась статистика. Без метрик любое решение о масштабировании принимается наугад.

Реагируем на аварию вместо того, чтобы её видеть

Выбор СУБД

PostgreSQL или MySQL: решение под задачу, а не по привычке

Обе системы зрелые и справятся с большинством проектов. Разница проявляется на сложной доменной логике, аналитике и требованиях к целостности данных. Ниже — честные развилки, по которым мы выбираем.

Сравнение PostgreSQL и MySQL по ключевым критериям
Критерий PostgreSQL MySQL
Тип нагрузки Сложные аналитические запросы, отчёты, агрегации рядом с транзакционной нагрузкой Преимущественно короткие транзакционные запросы, простые выборки по ключу
Целостность данных Строгая типизация, богатый набор ограничений, отложенные проверки внешних ключей Ограничения есть, но исторически часть проверок выполнялась неявно — зависит от режима SQL
Транзакции и блокировки MVCC: читатели не блокируют писателей; уровень изоляции настраивается под задачу В InnoDB чтение тоже не блокирует запись, но поведение и уровни изоляции отличаются от PostgreSQL
Расширения и типы Расширения (например, для индексов по JSONB или геоданных), массивы, диапазоны, собственные типы Ядро проще, расширяемость ограничена; для сложных типов чаще выносят логику в приложение
Изменение схемы DDL транзакционен: изменение структуры можно откатить вместе с транзакцией DDL не транзакционен так же, как в PostgreSQL: часть операций применяется необратимо
Команда и хостинг Часто уже есть у команды; поддержка во всех крупных облаках и managed-сервисах Огромная база специалистов, самый распространённый вариант в веб-хостинге и легаси-проектах
Когда выбираем Сложная доменная логика, отчётность и аналитика, требования к целостности и типам Простая модель данных, важна привычная инфраструктура и максимальная распространённость стека

Если у вас уже есть база и команда, которая её знает, — это весомый аргумент остаться на ней. Смена СУБД сама по себе редко решает проблему производительности.

Индексы и планы

Что именно меняется, когда индекс подобран верно

Разница между «тормозит» и «работает быстро» видна в плане выполнения запроса. Переключите режим, чтобы сравнить чтение таблицы целиком и поиск по составному индексу.

СУБД читает таблицу целиком и отбрасывает почти всё. Время растёт линейно вместе с объёмом таблицы — на тестовой базе этого не видно.

  • Seq Scan

    Полный перебор таблицы вместо поиска по индексу

  • rows

    Оценка числа строк расходится с фактом — статистика устарела

  • 142 ms

    При 10-кратном росте данных запрос станет пропорционально дольше

Составной индекс покрывает условие фильтрации целиком. Планировщик перестал читать лишние строки, и время перестало зависеть от размера таблицы.

  • Index Scan

    Поиск по индексу вместо полного перебора

  • rows=12

    Оценка совпала с фактом — статистика актуальна

  • 0.038 ms

    Время определяется глубиной индекса, а не объёмом таблицы

EXPLAIN ANALYZE
Seq Scan on orders  (cost≈0.00..18334.00 rows≈1 width=64) (actual time=0.015..142.318 rows=12 loops=1)
Index Scan using orders_customer_status_created_idx on orders  (cost≈0.29..8.31 rows=12 width=64) (actual time=0.021..0.038 rows=12 loops=1)

Составной индекс: (customer_id, status, created_at)

  • customer_id Первым — столбец равенства: по нему идёт точное совпадение
  • status Вторым — следующий по селективности фильтр из запроса
  • created_at Последним — сортировка и диапазон, чтобы не строить отдельную сортировку

Тот же индекс с обратным порядком столбцов для этого запроса уже не подойдёт: СУБД сможет использовать только его начало.

Разделы работ

Что входит в работу с базой данных

Объём подбирается под ситуацию: для нового проекта это проектирование схемы, для работающей системы чаще начинается с аудита и оптимизации запросов.

Схема, в которой нельзя записать противоречивые данные

Структуру проектируем от запросов, которые приложение действительно выполняет: где нужна нормализация, где оправдана денормализация, какие ограничения должна держать сама база, а не только код приложения.

Что дальше

Покажем на вашей базе, что именно упирается в предел, и предложим порядок изменений с оценкой риска.

Заказать аудит

Нормализация по делу

Убираем дублирование там, где оно порождает расхождения, и не дробим структуру там, где это только усложняет запросы.

Ограничения в базе

Внешние ключи, уникальность, проверки значений: база не даёт записать то, что противоречит модели, даже при ошибке в коде.

Типы и точность

Деньги — точный числовой тип, не float. Даты — с часовым поясом. Строки — с явной длиной и кодировкой.

Миграции в репозитории

Каждое изменение структуры — файл миграции под контролем версий, который проходит ревью и может быть откачен.

Оптимизация, подтверждённая планом запроса

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

Что дальше

Покажем на вашей базе, что именно упирается в предел, и предложим порядок изменений с оценкой риска.

Заказать аудит

Составные индексы

Порядок столбцов определяется тем, как фильтрует и сортирует конкретный запрос, а не алфавитом.

Устранение N+1

Жадная загрузка связей в ORM вместо сотни отдельных запросов из цикла — типовая причина «медленной страницы».

EXPLAIN до и после

Любое изменение подкреплено планом: видно, что перебор таблицы превратился в поиск по индексу.

Актуальная статистика

Без своевременного обновления статистики планировщик выбирает плохой план даже при верных индексах.

Транзакции, блокировки и целостность

Конкурентный доступ — источник самых неприятных дефектов: списание дважды, потерянное обновление, зависшая блокировка. Эти сценарии разбираются на этапе проектирования.

Что дальше

Покажем на вашей базе, что именно упирается в предел, и предложим порядок изменений с оценкой риска.

Заказать аудит

Границы транзакций

Операция, меняющая несколько таблиц, выполняется целиком или не выполняется вовсе.

Осторожно с блокировками

Читаем без блокировки там, где это допустимо; держим транзакции короткими, чтобы не собирать очередь ожиданий.

Идемпотентность

Повторная обработка платежа или вебхука не создаёт вторую операцию — защита на уровне ограничений базы.

Уровни изоляции

Выбираем осознанно: где нужен строгий уровень, а где достаточно чтения зафиксированных данных.

Порядок действий, когда данных становится больше

Масштабирование начинается не с покупки более мощного сервера, а с вопроса «что именно упирается в предел». Кэш, реплики и партиционирование решают разные задачи.

Что дальше

Покажем на вашей базе, что именно упирается в предел, и предложим порядок изменений с оценкой риска.

Заказать аудит

Кэш на Redis

Повторяющиеся выборки и счётчики уходят из базы в кэш — основной источник снижения нагрузки на чтение.

Репликация для чтения

Отчёты и тяжёлые выборки переносятся на реплику, чтобы не мешать транзакционной нагрузке.

Партиционирование

Большие таблицы делятся по периоду, и запросы читают нужную часть вместо всей истории.

Архивация

Устаревшие данные переносятся в архив по регламенту, а не удаляются навсегда «чтобы стало быстрее».

Бэкапы, которые действительно восстанавливаются

Резервная копия имеет ценность только тогда, когда из неё восстановились на реальном стенде и уложились в допустимое время. Это проверяется, а не предполагается.

Что дальше

Покажем на вашей базе, что именно упирается в предел, и предложим порядок изменений с оценкой риска.

Заказать аудит

Автоматические бэкапы

Полные и инкрементальные копии по расписанию, с хранением в отдельном месте от самой базы.

Проверка восстановления

Регулярный тест: развернуть копию на отдельном стенде и убедиться, что она читается и полна.

Замер времени восстановления

Заранее известно, сколько занимает возврат в работу, — иначе в аварии решения принимаются вслепую.

Мониторинг и алерты

Долгие запросы, блокировки, рост таблиц, отставание реплики — с оповещением до того, как это заметит клиент.

Доступ по минимуму и защита на уровне строк

Учётная запись приложения не должна уметь удалять таблицы, а сотрудник не должен видеть чужие данные. Это настраивается в базе, а не только в приложении.

Что дальше

Покажем на вашей базе, что именно упирается в предел, и предложим порядок изменений с оценкой риска.

Заказать аудит

Отдельные роли

У приложения, у миграций и у разработчика — разные учётные записи с разным объёмом прав.

Доступ по минимуму

Приложению выдаются только те операции, которые ему действительно нужны.

Персональные данные

Чувствительные поля шифруются, доступ к ним фиксируется в журнале, выгрузки ограничены.

Сетевая изоляция

База не смотрит в интернет: доступ только из защищённого контура или через туннель.

Этапы

Как проходит работа

Начинаем с замеров, а не с изменений: пока не видно планов запросов и размеров таблиц, любая оптимизация — предположение.

  1. Аудит и замеры

    Снимаем реальную картину: размеры таблиц, самые тяжёлые запросы, их планы, состояние индексов, статистика, настройки бэкапов. Смотрим код доступа к данным в приложении, включая связи ORM.

    Отчёт с узкими местами и приоритетами

  2. Проектирование схемы

    Описываем структуру, ограничения, индексы и связи под фактические сценарии работы. Отдельно фиксируем, какие данные растут быстро и что с ними будет через год.

    Схема, миграции, состав индексов

  3. Внедрение и проверка

    Применяем изменения через миграции, переносим данные без остановки работы, сравниваем планы запросов до и после. Каждое изменение обратимо.

    Изменения в продакшене с подтверждением

  4. Эксплуатация

    Настраиваем мониторинг, бэкапы и проверку восстановления. Передаём документацию по схеме и регламент действий при аварии.

    Мониторинг, регламент, обученная команда

Защита от двойного списания на уровне схемы

Вместо проверки «а не обработали ли мы уже этот платёж» в коде — уникальное ограничение в базе. Повторный запрос с тем же ключом просто не сможет создать вторую операцию, даже если придёт дважды одновременно.

ALTER TABLE payments
  ADD CONSTRAINT payments_external_id_unique
  UNIQUE (external_id);

Блокировка против гонки при записи

Когда два процесса меняют одну строку, второй должен дождаться первого, а не перезаписать результат. Advisory-блокировка берётся на время операции и снимается автоматически вместе с транзакцией.

-- PostgreSQL
SELECT pg_advisory_xact_lock(hashtext('order:42'));

-- MySQL: именованная блокировка на время транзакции
SELECT GET_LOCK('order:42', 5);
Результат

Что вы получаете

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

Как начинается работа

Аудит базы — самостоятельный результат, а не «первый этап продажи разработки». Отчёт можно передать своей команде или другому подрядчику: мы не привязываем смету аудита к последующим работам.

  • Схема данных с ограничениями целостности и внешними ключами
  • Набор индексов, обоснованный планами конкретных запросов
  • Миграции под контролем версий и порядок их отката
  • Оптимизированные тяжёлые запросы с планами «до и после»
  • Кэширование на Redis и перенос отчётности на реплику, где нужно
  • Партиционирование и архивация растущих таблиц
  • Резервное копирование с проверенным восстановлением
  • Мониторинг, алерты и регламент действий при аварии

Коротко о терминах

Чтобы разговор о базе не превращался в обмен непонятными словами.

Индекс
Структура, позволяющая найти строки без чтения всей таблицы. Ускоряет чтение, но замедляет запись и занимает место.
План запроса
Порядок действий, который СУБД выбрала для выполнения запроса. Основной источник фактов при оптимизации.
Транзакция
Набор изменений, который применяется целиком или не применяется вовсе.
MVCC
Механизм многоверсионности: читатели видят согласованный снимок данных и не блокируют писателей.
Реплика
Копия базы, которую можно использовать для чтения, чтобы разгрузить основной сервер.
Партиционирование
Разделение большой таблицы на части, чтобы запрос читал только нужный фрагмент.
Вопросы

Частые вопросы о базах данных

PostgreSQL или MySQL — что выбрать для нового проекта?

По умолчанию для новой разработки мы чаще предлагаем PostgreSQL: строгая типизация, транзакционный DDL и расширения упрощают жизнь на сложной доменной логике. MySQL остаётся разумным выбором, когда модель данных простая, команда уже работает с ним, а инфраструктура и хостинг заточены под него. Решение принимается по вашей нагрузке и опыту команды, а не по общему правилу.

Можно ли ускорить существующую базу, не переписывая приложение?

Часто да. Индексы, устранение запросов из циклов (N+1), актуализация статистики и кэширование повторяющихся выборок дают эффект без изменения бизнес-логики. Если узкое место в самой модели данных — это выяснится на аудите, и тогда мы покажем, какие изменения нужны и чем они обойдутся.

Чем опасна денормализация?

Тем, что одни и те же данные начинают храниться в нескольких местах и рано или поздно расходятся. Денормализация оправдана, когда она решает конкретную проблему производительности, а согласованность копий поддерживается осознанно — например, триггером или фоновой задачей. Мы не используем её «для скорости» без такой поддержки.

Что делать, если база уже большая и тормозит?

Начинаем с замеров, а не с изменений. Смотрим планы тяжёлых запросов и размеры таблиц, находим, что именно упирается в предел: чтение с диска, блокировки, нехватка памяти под кэш страниц или неудачные запросы. Дальше порядок зависит от причины: индексы и запросы, затем кэш и реплики, затем партиционирование и архивация.

Как часто нужно проверять резервные копии?

Регулярно и по регламенту, а не «когда будет время». Настроенный бэкап сам по себе ничего не гарантирует: важно хотя бы периодически разворачивать копию на отдельном стенде, проверять полноту данных и замерять время восстановления. Мы передаём этот регламент вместе с проектом.

Вы работаете с нашей существующей базой или создаёте новую?

И с тем, и с другим. Для действующей системы начинаем с аудита и точечных изменений, чтобы не останавливать работу. Для нового проекта проектируем схему до первой строки кода: переделывать структуру, когда в ней уже есть данные, заметно дороже.

Кто отвечает за базу после сдачи проекта?

Вы. Передаём схему с описанием, миграции, регламент бэкапов и мониторинг, объясняем, почему приняты те или иные решения. Можем остаться на сопровождении, но не строим систему так, чтобы без нас её нельзя было эксплуатировать.

Проверим вашу базу данных?

Пришлите доступ к тестовой копии или опишите, что именно тормозит. На аудите покажем планы тяжёлых запросов, состояние индексов и бэкапов — и назовём порядок изменений с оценкой риска.