Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

"Мы два месяца долбались, а потом построили индекс" (c) Аксенов

2,959 views

Published on

Мой доклад про оптимизацию PostgreSQL хранилища с DevConf 2014

Published in: Technology
  • Be the first to comment

"Мы два месяца долбались, а потом построили индекс" (c) Аксенов

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

×