"Мы два месяца долбались, а потом построили...
DESCRIPTION
Мой доклад про оптимизацию PostgreSQL хранилища с DevConf 2014TRANSCRIPT
![Page 1: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/1.jpg)
![Page 2: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/2.jpg)
Привет!
● Меня зовут Саша● Я работаю главным инженером● В компании Git in Sky● Однажды я настроил одинMySQL-сервер
![Page 3: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/3.jpg)
Какого цвета инсталлятор Oracle?
● Вы используете базы данных?● Умеете читать и пониматьплан запроса?
● Настраивали однаждыMySQL-сервер?
● Может, и не однажды?● Может, и не MySQL?
![Page 4: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/4.jpg)
Какова цель операции?
● Однажды ко мне обратилсяодин человек
● Он предоставил следующиедокументы:
● http://slideshare.net/profyclub_ru/08-6● Действовать надо было быстрои осторожно
![Page 5: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/5.jpg)
Кто мой заказчик?
● Конструктор сайтов, http://setup.ru● Пользовательские файлы хранятся в базе данных (PostgreSQL)
● Для больших файлов используется large objects API
● Приложение на Perl, под Apache + mod_perl● В 2012-м это работало хорошо, в 2014-м...
![Page 6: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/6.jpg)
Перечень возникших сложностей
● Количество файлов: 6 млн => 207(85) млн● Размер индексов: 2Gb => десятки Gb● Скорость синхронизации: 100 f/s => ~30 f/s● Объем базы данных на дисках: 6Tb на тот момент
● ^ Сейчас уже 6.7Tb, и дальше будет только больше
![Page 7: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/7.jpg)
Анализ ситуации
● Гражданский специалист: “Файлы в БД? АААА, куда я попал!”
● Советы взять какое-нибудь другое хранилище я уже слышал, можно их не повторять :)
● Наш специалист: “Ничего неломаем, валим всех аккуратно,отходим быстро”
![Page 8: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/8.jpg)
Детальный анализ ситуации
● Бизнес-причины хранить файлы в СУБД:● Нужны транзакции при публикации сайта● Варианты:● Менять хранилище и делать транзакции на уровне приложения
● Найти транзакционное хранилище (а это СУБД :) )
![Page 9: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/9.jpg)
Объекты предметной области● Таблица domains – имена доменов● Таблица content – метаинформация о файле (время последнего изменения и путь)
● Таблица stat – сами бинарные данные и их sha-1 хэш для дедупликации
● Таблица deleted – признак того, что файл удален
● Все четыре связаны между собой
![Page 10: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/10.jpg)
Пользовательские сценарии
● Публикация и синхронизация файлов:● Публикуем всегда на одну и ту же ноду● Кастомный синхронизатор не очень быстро обновляет все остальные ноды
● Отдача статического контента:● Отдаем а) последнюю, б) неудаленную версию
![Page 11: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/11.jpg)
Что плохо?
● Отдача файлов работает не очень быстро● Публикация и синхронизация – тоже● Существующее железо справляется не оченьхорошо
● Одним словом, Hetzner!
![Page 12: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/12.jpg)
Я бы даже сказал, полный Hetzner
● Было: RAID0 2*3Tb SATA, 16G RAM, 128G SSD – для pg_temp и nginx, сортировка в PostgreSQL и буферизация в nginx – быстро
● Стало: RAID10 4*4Tb SATA, 48G RAM без SSD● SSD не дают, хотя место в корпусе еще есть – Hetzner!
● Надо жить с этим
![Page 13: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/13.jpg)
Но как?
● Как обычно:● slow queries log, потом pgFouine или pgBadger, раз в сутки – смотреть отчеты, в них смотреть план запроса
![Page 14: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/14.jpg)
Все еще проще
● Два самых популярных запроса при отдаче и синхронизации - “найти неудаленный файл” и “найти, что синхронизировать” - это запросы ко view
● Они и тормозят, их и надо оптимизировать
![Page 15: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/15.jpg)
Начнем с отдачи файлов
● Этот план запроса мне не нравится● В нем слишком многабукв
![Page 16: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/16.jpg)
Нам нужен новый план
● Материализовать view● В PostgreSQL 9.2 нет materialized view● Но в книге “Enterprise Rails” написано, как их эмулировать с помощью триггеров
● Enterprise WHAT, sorry?
![Page 17: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/17.jpg)
Шаверма своими руками
● “Поверх” нематериализованного view делается таблица с такими же полями
● Она работает как кэш – записи в ней заводятся по запросу
● Сначала ищем в ней, потом в исходном view, если не нашлось в ней
● Записи инвалидируются триггерами на всех таблицах-участниках исходного view
![Page 18: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/18.jpg)
Как измерить результат?
● pgFouine и pgBadger не подходят – долго ждать, много процессить, slow log нерепрезентативен
● Расширение pg_stat_statements● ^ Лучшее, что было со мной● Позволяет смотреть статистикув реальном времени
![Page 19: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/19.jpg)
Как пользоваться?
● SELECT (total_time / 1000 / 60) as total_minutes, (total_time/calls) as average_time, calls, query FROM pg_stat_statements ORDER BY total_minutes/average_time desc;
![Page 20: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/20.jpg)
Что покажет?
![Page 21: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/21.jpg)
Близки ли мы к цели?
● Хорошо: кэширующая таблица кэширует● Плохо: примерно 30-40% запросов не попадают в кэш
● Может быть, надо подождать?● На третий день Зоркий Глаз заметил, что у тюрьмы нет одной стены
![Page 22: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/22.jpg)
Know your weapon
● “Посмотреть в таблице, потом во view”● А что, если у нас 404, и файла нет вообще?● Зачем ходить за такими файлами во view?
![Page 23: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/23.jpg)
Стало ли лучше?
● Ночью – 15мс в среднем● Днем – 40-50мс в среднем● Обычно я работаю по ночам● А результат нужно смотреть в середине дня на пике нагрузки
● Что очень неудобно
![Page 24: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/24.jpg)
Я люблю графики!
● Главная метрика – время отдачи контента● Ее лучше измерять на эппсервере, а не в базе?
● Zabbix● Graphite/StatsD● http://goo.gl/x6If1S● ^ Ansible playbook для установки StatsD и Graphite
![Page 25: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/25.jpg)
Я не люблю Zabbix!
● Это плохо написанная система “все-в-одном”, по качеству напоминающая китайскую видеодвойку из 90-х
● К тому же, там плохие планы запросов
![Page 26: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/26.jpg)
UNIX-way не всегда вреден
● Graphite/StatsD stack:● Dashboard (сначала – стандартный от Graphite),
● Веб-сервис отдачи графиков (на Django)● Коллектор с RRD-like хранилищем (Carbon)● Агрегатор/препроцессор с UDP-интерфейсом (собственно, StatsD)
![Page 27: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/27.jpg)
StatsD server
● Есть на Go, Node.JS, Python, Perl, C, Ruby, ...● Сперва я взял Python:
● Потом опомнился и взял Perl
![Page 28: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/28.jpg)
Уже должен быть результат?
● 40-50мс никак не хотят превращаться в 0-1● Что делать?● Построить более лучшие индексы
![Page 29: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/29.jpg)
Сказано – сделано
● Для самого частого запроса построен индекс на все три столбца, по которым идет поиск
● В этот момент все стало еще хуже! :)● Размер индекса – 18 гигабайт● Зоркий Глаз опять заметил, что у тюрьмы нет одной стены
![Page 30: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/30.jpg)
Чрезвычайные меры
● Одно из полей, по которым индекс – varchar● Превращаем varchar в int:● http://stackoverflow.com/a/9812029/601572● Совсем забыл сказать: база данных уже полна хранимых процедур и триггеров, кроме того, я их совершенно не боюсь
● Просто не люблю
![Page 31: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/31.jpg)
Что же было по ссылке?
● Я не помню, поэтому записал прямо сюда:● create function h_int(text) returns int as $$ select ('x'||substr(md5($1),1,8))::bit(32)::int;$$ language sql;
![Page 32: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/32.jpg)
К чему приводит чтение*
● *плана запроса● SET enable_bitmapscan=false; <= nested loops
SELECT something FROM stat s JOIN domains d ON d.id = s.domain JOIN content c ON c.id = s.content LEFT JOIN deleted e ON e.id = s.id WHERE d.name = domname AND h_int(s.name) = h_int(filename) <= новый индекс AND s.name = filename AND date_part('epoch'::text, s.ptime) = filerev
![Page 33: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/33.jpg)
Счастливы ли мы?
● Размер индекса: 18G => 8G● Время запроса: 40-50мс => 20-25мс● 90% всех запросов обслуживаются за 100мс● В среднем запрос обслуживается приложением за 50мс
![Page 34: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/34.jpg)
Как это выглядит в Graphite
![Page 35: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/35.jpg)
Как это выглядит в Zabbix
![Page 36: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/36.jpg)
Часть вторая, момент истины
● При проверке существования файла я получал id файла, если он есть, и решил этим воспользоваться, чтобы ходить во view по PK
● Оказалось, я ошибся ранее, и мне возвращался массив id – при оптимизации это стало явным
● Я исправил ошибку и этой оптимизацией добился ускорения еще в два раза
![Page 37: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/37.jpg)
Что мешало
● PL/pgSQL – плохой, негодный язык● Я так и не понял, как в нем сконструировать программно множество из нуля строк, поэтому возвращал, при необходимости, такое множество, делая SQL-запрос в специально заготовленную пустую таблицу с нужным списком полей
![Page 38: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/38.jpg)
Что еще удалось
● В качестве dashboard к Graphite я поставил Grafana
● Систему отдачи файлов я переписал на смешанную асинхронно/синхронную, используя для внутренних нужд HTTP status 418 I'm a teapot
● “Асинхронная” не значит “быстрая” (наоборот), но значит “экономичная”
![Page 39: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/39.jpg)
Что было дальше
● Я пытался тюнить кастомный репликатор, но быстро понял, что это невозможно – он работает на пределе
● Я решил заменить его на какое-то общее средство репликации и выбрал Bucardo
● Bucardo в тестовом режиме работало отлично, но из 6+Tb базы среплицировало 1Tb
![Page 40: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/40.jpg)
WTF?
● Know your weapon:● Large objects – это просто еще один key-value storage, по сути
● Репликация ими не занимается● При этом в нашей базе мы никогда их не перезаписываем после создания!
● Кроме того, у них уникальные номера
![Page 41: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/41.jpg)
И вот тут мне карта пошла!● Object storages:● LeoFS● OpenStack Swift● Elliptics● Riak CS● Ceph Object Gateway● Но это другая история, и она еще не закончена
![Page 42: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/42.jpg)
Выводы
● Иногда, прежде чем сказать “давайте перепишем всё”, стоит попробовать переписать не всё
● “Переписать всё” - тоже выход, надо только уметь писать и знать, где взять оружие
![Page 43: "Мы два месяца долбались, а потом построили индекс" (c) Аксенов](https://reader033.vdocuments.mx/reader033/viewer/2022051818/5499348ab479590d7a8b4590/html5/thumbnails/43.jpg)
Спасибо за внимание!
● Пожалуйста, ваши вопросы!● С вами был Александр Чистяков,● Главный инженер Git in Sky● http://twitter.com/noatbaksap● [email protected]● http://gitinsky.com,http://meetup.com/DevOps-40