10 способов достижения HighLoad'а и BigData на ровном месте
1. Масштабирование
Типичный случай: расчитываем на миллион пользователей, поставили 100 инстансов PostgreSQL, шардирование по created_at.
Последствия:
- любое взаимодействие, затрагивающее пользователей, лежащих на разных нодах (например, чат) - боль, логика сильно усложняется
- оказывается, что пользователи, созданные давно - малоактивны, поэтому 90 машин кластера простаивают, а оставшиеся 10 утилизованы на 100%. Надо было шардить по user_id.
- при обращении к незанятой машине кэш у нее холодный и запарос выполняется в разы дольше
Как надо было:
- нечего было сразу масштабироваться, нужно было сначала вырасти до ресурсов одной машины, потом посчитать стоимость ее апгрейда и только после этого принимать решение о масштабировании
2. Бизнес хочет хранить данные за все время
Нужны отчеты за все время, поэтому мы в одну огромную базу сохраняем все данные и никогда не удаляем
Последствия:
- через несколько лет получаем big data на пустом месте
Как надо было:
- ретроспективные данные можно архивировать
- не хранить сырые данные, а хранить агрегаты по ним (но это если бизнес согласится, что им не понадобятся новые агрегаты)
- можно партиционировать и на одном сервере хранить архивные данные, на другом - горячие + агрегаты
3. EAV упрощает проектирование
EAV (Entity-attribute-value) - это подход, используемый когда у нас есть сущности, у которых есть много атрибутов, но используется лишь малая их часть. Тогда создается таблица Attributes
с тремя колонками:
- Entity: идентификатор сущности
- Attribute: название атрибута
- Value: значение атрибута
Применяется, чтобы упростить проектирование
Последствия:
- все данные лежат в 3-4 гигантских таблицах, которые все время джойнятся. Типы полей
Attribute
иValue
будут, скорее всего, текстовые, а это значит что эффективность индексирования таких данных будет крайне мала. В результате наши джойны будут очень долгими. - через некоторое время EAV гордо переименовывается в ядро и обрастает витринами и представлениями с денормализованными данными в реляционном виде. Работает медленно и плохо. Любое изменение в схеме ведет кучу изменений в этих разрозненных представлениях. Чтобы упростить, приходится выкидывать ядро.
Как надо было:
- не лениться и делать отдельные реляционные таблицы
4. ORM упрощает разработку
- универсальный способ убить производительность любой базы
Как надо было:
- использовать только в прототипах
5. Главное зло в PostgreSQL - autovacuum
Постоянно работает и всему мешает, в результате чего его выключают.
Последствия:
- фрагментированная таблица на 100К строк занимает 100 ГБ
Как надо было:
- не отключать и смотреть здесь как с ним жить
6. JOIN это зло - они медленные
Последствия:
Чтобы не использовать джойн, в контроллер вытягиваются 2 таблицы из базы, они джойнятся средствами ЯП. Затем, чтобы оптимизировать этот велосипедный джойн, добавляется выбор алгоритма - nested loop, hash или merge. В результате получается самодельная БД, только плохая.
Как надо было:
- Надо было джойнить и не выпендриваться. Джойны на самом деле быстрые, реляционная база данных оптимизирована для работы с джойнами.
7. Давайте изобретем Slony
(Slony - система репликации, используемая в PostgreSQL)
Последствия:
- велосипед всегда работает как-то не так, потому что репликация - это обработка распределенных транзакций, а это тяжело
- велосипед скорее всего будет работать на уровне SQL, таблиц, триггеров и хранимых процедур. Это медленно и чревато конфликтами.
Как надо было:
- использовать готовые проверенные решения. Если в них чего-то нет, значит тому есть причина. Возможно желаемый функционал просто невозможно реализовать с учетом всех сложностей репликации.
- в готовых решениях используется репликация лога транзакций на низком уровне
8. У меня в тесте все работает
Разработчики используют EXPLAIN, но только на своей разработческой машине. А на продакшне данных в 1000 раз больше и все сразу тормозит.
Как надо было:
- у разработчиков должен быть какой-то ограниченный доступ к продакшну. В идеале на чтение. Если нельзя, то, например, доступ к мониторингу базы, чтобы после деплоя видеть, сколько ресурсов потребляет каждый запрос. Либо же должны быть отлаженные процедуры работы с DBA, чтобы можно было попросить их прогнать какой-то запрос.
9. Be smart, as a java-developer
Разработчик хочет быстрее закачать данные в базу, поэтому делает это в 500 тредов. А на стороне БД есть только 10 воркеров (по числу ядер) и в результате 500 тредов, пытающиеся пролезть в 10 воркеров, начинают драться между собой на стороне приложения.
SQL-запрос через ко-рутины питона может быть в 10 раз медленнее, чем без них.
10. Приятные мелочи
Если запрос от веба возвращает миллион строк, то:
- он никогда не будет работать достаточно быстро для веба
- стоит подумать, а зачем он нужен? никто не будет читать миллион строк в браузере
20 счетчиков с count(*) на главной странице:
- будут работать медленно
- точные обытно не нужны
- в постгресе можно написать процедуру, которая будет запрашивать данные от анализатора статистики планировщика. Они будут быстрые, но приблизительные.