Школа
системного анализа
и проектирования
Инна Александрова по материалам вебинара Ани КРХ

Приемы масштабирования баз данных и повышения производительности систем

Введение
В этой статье мы рассмотрим несколько основных приёмов, покрывающих 90% задач масштабирования высоконагруженных (highload) систем. При разработке высоконагруженных систем необходимо продумывать много нюансов: архитектура системы, структура хранилища, актуальность используемых технологий, возможности аппаратных ресурсов и многое другое. В статье сделаем упор на базы данных — рассмотрим методы оптимизации и повышения производительности работы с данными.

Материал будет полезен тем, кто:

  • хочет проектировать системы, пригодные к масштабированию без существенных изменений в архитектуре;
  • столкнулся с проблемами роста системы и хочет узнать основные способы решения;
  • ищет способы оптимизации производительности в своих проектах.
Время на чтение статьи: 20 минут
Не любите читать? Посмотрите видео.

Что такое масштабирование

Представим, что мы выпустили на рынок MVP (Minimum Viable Product — Минимально жизнеспособный продукт), который условно имеет такую архитектуру:
Управление требованиями и управление архитектурой ИТ
Рис.1 — Исходная архитектура системы
В реальности архитектура (даже у MVP) будет несколько сложнее, но для примера упростим ее до двух частей:
  • backend-приложение, в котором хранится логика системы;
  • база данных.
Предположим, что наш MVP стал успешен на рынке и привлёк много пользователей. Это привело к нестабильной работе системы: страницы загружаются медленно, возникают ошибки. В таком случае необходимо позаботиться о масштабировании системы.

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

Основные методы масштабирования систем

В нашем примере система состоит из двух частей, соответственно проблема в одной из них. Определить, где она кроется можно разными способами: по ошибкам в логах или по графикам утилизации ресурсов сервера (CPU и память).

Масштабирование бэкенда

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


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


Зачастую горизонтальное масштабирование бэкенда становится довольно сложной задачей, требующей тщательного анализа и изменения бизнес-логики, разнесения различных кусков функциональности по разным узлам.


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

Оптимизация базы данных

Мы выделили для backend дополнительные сервера, но с вероятностью 90% не получили необходимый результат, потому что в большинстве случаев низкая производительность бывает вызвана проблемами в хранилище, а не в приложении, поэтому нужно попытаться оптимизировать ее.
Настройки СУБД
Первый пункт, требующий внимания — настройки СУБД (системы управления базой данных).

Настройки СУБД — это набор параметров в конфигурационном файле. Часто они по умолчанию выставлены неоптимально.

Например, в PostgreSQL дефолтные значения предназначены для работы с минимальными ресурсами, чтобы СУБД можно было развернуть даже на серверах с маленькой мощностью. Изменить их довольно просто.

На примере PostgreSQL рассмотрим три настройки, которые сильно влияют на производительность БД. Их значения зависят от:
  • типа носителя, на котором хранятся данные (жёсткий диск HDD или твердотельный накопитель SSD);
  • количества оперативной памяти, выделенной под сервер хранилища.
Полезные ссылки

How to tune PostgreSQL for memory
Индексирование
Ещё один шаг — применение индексов. Индексирование — один из основных способов ускорения запросов к базе данных.
Работая с хранилищем, не забывайте индексировать данные в таблицах. При выборе способа и ключа индексации учитывайте распределение данных и типовые запросы, которые планируете выполнять к БД.

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

Примерно так работает индексирование. При запросе к реляционной БД, проиндексированной методом B-tree, механизм спускается по бинарному дереву, как показано на иллюстрации. Бинарный поиск имеет алгоритмическую сложность O(log(n)), то есть на 1000 записей нужно выполнить в среднем всего 10 операций, чтобы дойти по дереву индекса до искомого значения и это эффективнее перебора всех значений подряд.
Управление требованиями и управление архитектурой ИТ
Рис.2 — Пример индекса B-tree для набора численных значений
Оптимизация запросов
Под оптимизацией запросов подразумевается изменение структуры SQL-запросов без изменения структуры хранения данных.

Пример запроса с простым синтаксисом:
SELECT *
FROM items
WHERE category IN (1, 3, 15)
ORDER BY priority DESC
LIMIT 3
Этот запрос можно заменить на приведённый ниже, упростив логику обработки данных для БД.
SELECT *
FROM (SELECT *
      FROM (SELECT *
            FROM items
            WHERE category = 1
            ORDER BY priority DESC
            LIMIT 3) x
      UNION ALL
      SELECT *
      FROM (SELECT *
            FROM items
            WHERE category = 5
            ORDER BY priority DESC
            LIMIT 3) x
      UNION ALL
      SELECT *
      FROM (SELECT *
            FROM items
            WHERE category = 13
            ORDER BY priority DESC
            LIMIT 3) x
      ) y
ORDER BY priority DESC
LIMIT 3
Такая замена позволит снизить время выполнения запроса с 6208 мс до 0.140 мс.

Для освоения искусства оптимизации запросов вам понадобится научиться:
  • читать план запроса, подготовленный планировщиком запросов по команде EXPLAIN (ANALYZE);
  • запрашивать и интерпретировать статистику БД;
  • разбираться в том, как СУБД читает и обрабатывает данные.
Денормализация данных
Под денормализацией понимается приведение структуры данных к удобному для чтения виду. При этом допускается нарушение правил нормализации данных, они могут быть избыточными или задублированными.

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

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

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

Так возникает необходимость поддерживать консистентность данных — их согласованность в двух таблицах: таблице с заказами и в таблице с суммой заказов пользователей. Это решается несколькими способами:

  • записывать данные в обе таблицы транзакционно (одновременно);

  • обновлять данные во второй таблице в асинхронном режиме (фоново), пересчитывая суммы заказов на основе первой таблицы. Такой вариант подходит, если допустима небольшая задержка обновления данных по суммам заказов.
Необходимость поддержания консистентности данных усложняет логику системы и такой подход должен быть оправдан. Использовать денормализацию нужно когда вы точно уверены, что правильно настроенные индексы и оптимизированные запросы уже не обеспечивают необходимую скорость запросов.



Кэширование

Кэширование представляет собой технологию временного хранения данных и подходит для часто запрашиваемых и редко меняющихся данных (в маркетплейсе — список брендов или категории товаров). Для их отображения не обязательно каждый раз делать запрос в базу.

Можно использовать In-Memory базы данных, например Redis или Memcached. Эти базы хранят данные прямо в оперативной памяти, что обеспечивает очень быстрое чтение информации.

С внедрением кэш-хранилища архитектура нашего маркетплейса из примера изменится следующим образом:

Управление требованиями и управление архитектурой ИТ
Рис.3 — Архитектура системы после внедрения кэш-хранилища

Репликация

Другой механизм оптимизации — репликация хранилища. Репликация заключается в разворачивании дополнительных баз (реплик) и постоянном копировании (репликации) на них данных с главной (мастер) базы.

Архитектура системы будет выглядеть следующим образом:
Управление требованиями и управление архитектурой ИТ
Рис.4 — Архитектура системы при применении репликации
При этом запись данных производится только в мастер-базу, а реплики используются исключительно для чтения.

Шардирование

Если вам недостаточно масштабирования базы на чтение и необходимо масштабироваться на запись, единственный способ это сделать — шардирование. Это самый сложный механизм из всех перечисленных, он требует изменения структуры данных и логики приложения.

Шардирование можно применять не только для увеличения производительности, но и в случаях, когда объем данных настолько большой, что они физически не помещаются на сервере из-за аппаратных ограничений.
Шардирование — деление данных на сегменты с хранением их на разных серверах. При шардировании необходимо определить ключ (признак), по которому данные будут делиться. В нашем примере с маркетплейсом можно поделить товары по бренду. Необходимо выделить дополнительную базу для хранения метаданных — информации о том, на каком шарде хранятся данные по каждому бренду.
Управление требованиями и управление архитектурой ИТ
Рис.5 — Архитектура системы при применении шардирования

Резюме

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

  2. 90% проблем с производительностью информационной системы заключаются в БД.

  3. Начинать оптимизацию базы нужно с наиболее простых действий: подбор оптимальных настроек СУБД, оптимизация SQL-запросов, внедрение индексов. Это может дать значительный прирост производительности при грамотном подходе.

Воркшоп Дарьи Колесовой из Яндекса


«От проектирования до эксплуатации:

реляционные БД и SQL-запросы для аналитика

(на примере PostgreSQL)»

8 часов в выходные 17-18 февраля

Ждём начинающих ИТ-специалистов, которые хотят:
  • научиться проектировать физическую модель реляционной БД, наполнять её данными и
  • делать базовые SQL-запросы к ней (операторы SELECT, WHERE, LIKE, ORDER BY, GROUP BY, HAVING, JOIN)
Ваш промокод: SQL50 даст скидку 50%

Ответы на вопросы

  • Вопрос:
    Всегда ли нужна база с метаданными при шардировании?
    Ответ:
    Использовать метабазу необязательно. Данные могут разноситься по шардам не только по какому-то смысловому ключу, как в примере с брендами в статье. Например, письма в Яндекс.Почта хранятся более чем на 20 шардах без использования метабазы. В каждом письме есть хэш пользователя, указывающий на нужный шард.

    В PostgreSQL существует расширение PL/Proxy — механизм, упрощающий работу с базами данных при масштабировании; промежуточный слой между базой данных и приложением. Приложению не обязательно знать в каком узле находятся нужные данные, оно направит запрос к proxy, а proxy самостоятельно определит, к какому узлу нужно сделать запрос данных.
  • Вопрос:
    Какие есть советы для геораспределения? Есть ли смысл располагать дата-центры ближе к пользователю?
    Ответ:
    Желательно, чтобы ping (время реакции на запрос) между приложением и БД был как можно меньше. Расположение дата-центров ближе к пользователю не имеет особого смысла, пользователь не заметит миллисекунды задержки.

    Размещение данных в разных дата-центрах может также применяться для отказоустойчивости: если произойдет авария в одном из дата-центров, его заменят расположенные в других местах.
  • Вопрос:
    Почему на ваших проектах в основном используется PostgreSQL?
    Ответ:
    В основном по двум причинам:
    1. PostgreSQL — зрелое решение, в котором решены многие проблемы и задачи.
    2. Меньшая стоимость по сравнению с другими СУБД.
  • Вопрос:
    Допустим, что в БД реализовано шардирование, но запросы затрагивают все данные. Будет ли в таком случае прирост производительности?
    Ответ:
    Зависит от того, как вы отправляете запросы к шардам. Если запросы отправляются параллельно, то прирост производительности будет, даже когда запросы затрагивают данные на всех шардах.
  • Вопрос:
    Можно ли использовать репликацию совместно с денормализацией?
    Ответ:
    Все перечисленные в статье методики можно использовать совместно.

■ Другие статьи по теме Базы данных

Показать еще