"Мы два месяца долбались, а потом построили...

43

Upload: alexander-chistyakov

Post on 23-Dec-2014

1.574 views

Category:

Technology


2 download

DESCRIPTION

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

TRANSCRIPT

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

Привет!

● Меня зовут Саша● Я работаю главным инженером● В компании Git in Sky● Однажды я настроил одинMySQL-сервер

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

Какого цвета инсталлятор Oracle?

● Вы используете базы данных?● Умеете читать и пониматьплан запроса?

● Настраивали однаждыMySQL-сервер?

● Может, и не однажды?● Может, и не MySQL?

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

Какова цель операции?

● Однажды ко мне обратилсяодин человек

● Он предоставил следующиедокументы:

● http://slideshare.net/profyclub_ru/08-6● Действовать надо было быстрои осторожно

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

Кто мой заказчик?

● Конструктор сайтов, http://setup.ru● Пользовательские файлы хранятся в базе данных (PostgreSQL)

● Для больших файлов используется large objects API

● Приложение на Perl, под Apache + mod_perl● В 2012-м это работало хорошо, в 2014-м...

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

Перечень возникших сложностей

● Количество файлов: 6 млн => 207(85) млн● Размер индексов: 2Gb => десятки Gb● Скорость синхронизации: 100 f/s => ~30 f/s● Объем базы данных на дисках: 6Tb на тот момент

● ^ Сейчас уже 6.7Tb, и дальше будет только больше

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

Анализ ситуации

● Гражданский специалист: “Файлы в БД? АААА, куда я попал!”

● Советы взять какое-нибудь другое хранилище я уже слышал, можно их не повторять :)

● Наш специалист: “Ничего неломаем, валим всех аккуратно,отходим быстро”

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

Детальный анализ ситуации

● Бизнес-причины хранить файлы в СУБД:● Нужны транзакции при публикации сайта● Варианты:● Менять хранилище и делать транзакции на уровне приложения

● Найти транзакционное хранилище (а это СУБД :) )

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

Объекты предметной области● Таблица domains – имена доменов● Таблица content – метаинформация о файле (время последнего изменения и путь)

● Таблица stat – сами бинарные данные и их sha-1 хэш для дедупликации

● Таблица deleted – признак того, что файл удален

● Все четыре связаны между собой

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

Пользовательские сценарии

● Публикация и синхронизация файлов:● Публикуем всегда на одну и ту же ноду● Кастомный синхронизатор не очень быстро обновляет все остальные ноды

● Отдача статического контента:● Отдаем а) последнюю, б) неудаленную версию

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

Что плохо?

● Отдача файлов работает не очень быстро● Публикация и синхронизация – тоже● Существующее железо справляется не оченьхорошо

● Одним словом, Hetzner!

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

Я бы даже сказал, полный 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) Аксенов

Но как?

● Как обычно:● slow queries log, потом pgFouine или pgBadger, раз в сутки – смотреть отчеты, в них смотреть план запроса

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

Все еще проще

● Два самых популярных запроса при отдаче и синхронизации - “найти неудаленный файл” и “найти, что синхронизировать” - это запросы ко view

● Они и тормозят, их и надо оптимизировать

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

Начнем с отдачи файлов

● Этот план запроса мне не нравится● В нем слишком многабукв

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

Нам нужен новый план

● Материализовать view● В PostgreSQL 9.2 нет materialized view● Но в книге “Enterprise Rails” написано, как их эмулировать с помощью триггеров

● Enterprise WHAT, sorry?

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

Шаверма своими руками

● “Поверх” нематериализованного view делается таблица с такими же полями

● Она работает как кэш – записи в ней заводятся по запросу

● Сначала ищем в ней, потом в исходном view, если не нашлось в ней

● Записи инвалидируются триггерами на всех таблицах-участниках исходного view

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

Как измерить результат?

● pgFouine и pgBadger не подходят – долго ждать, много процессить, slow log нерепрезентативен

● Расширение pg_stat_statements● ^ Лучшее, что было со мной● Позволяет смотреть статистикув реальном времени

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

Как пользоваться?

● 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) Аксенов

Что покажет?

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

Близки ли мы к цели?

● Хорошо: кэширующая таблица кэширует● Плохо: примерно 30-40% запросов не попадают в кэш

● Может быть, надо подождать?● На третий день Зоркий Глаз заметил, что у тюрьмы нет одной стены

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

Know your weapon

● “Посмотреть в таблице, потом во view”● А что, если у нас 404, и файла нет вообще?● Зачем ходить за такими файлами во view?

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

Стало ли лучше?

● Ночью – 15мс в среднем● Днем – 40-50мс в среднем● Обычно я работаю по ночам● А результат нужно смотреть в середине дня на пике нагрузки

● Что очень неудобно

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

Я люблю графики!

● Главная метрика – время отдачи контента● Ее лучше измерять на эппсервере, а не в базе?

● Zabbix● Graphite/StatsD● http://goo.gl/x6If1S● ^ Ansible playbook для установки StatsD и Graphite

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

Я не люблю Zabbix!

● Это плохо написанная система “все-в-одном”, по качеству напоминающая китайскую видеодвойку из 90-х

● К тому же, там плохие планы запросов

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

UNIX-way не всегда вреден

● Graphite/StatsD stack:● Dashboard (сначала – стандартный от Graphite),

● Веб-сервис отдачи графиков (на Django)● Коллектор с RRD-like хранилищем (Carbon)● Агрегатор/препроцессор с UDP-интерфейсом (собственно, StatsD)

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

StatsD server

● Есть на Go, Node.JS, Python, Perl, C, Ruby, ...● Сперва я взял Python:

● Потом опомнился и взял Perl

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

Уже должен быть результат?

● 40-50мс никак не хотят превращаться в 0-1● Что делать?● Построить более лучшие индексы

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

Сказано – сделано

● Для самого частого запроса построен индекс на все три столбца, по которым идет поиск

● В этот момент все стало еще хуже! :)● Размер индекса – 18 гигабайт● Зоркий Глаз опять заметил, что у тюрьмы нет одной стены

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

Чрезвычайные меры

● Одно из полей, по которым индекс – varchar● Превращаем varchar в int:● http://stackoverflow.com/a/9812029/601572● Совсем забыл сказать: база данных уже полна хранимых процедур и триггеров, кроме того, я их совершенно не боюсь

● Просто не люблю

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

Что же было по ссылке?

● Я не помню, поэтому записал прямо сюда:● create function h_int(text) returns int as $$ select ('x'||substr(md5($1),1,8))::bit(32)::int;$$ language sql;

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

К чему приводит чтение*

● *плана запроса● 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) Аксенов

Счастливы ли мы?

● Размер индекса: 18G => 8G● Время запроса: 40-50мс => 20-25мс● 90% всех запросов обслуживаются за 100мс● В среднем запрос обслуживается приложением за 50мс

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

Как это выглядит в Graphite

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

Как это выглядит в Zabbix

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

Часть вторая, момент истины

● При проверке существования файла я получал id файла, если он есть, и решил этим воспользоваться, чтобы ходить во view по PK

● Оказалось, я ошибся ранее, и мне возвращался массив id – при оптимизации это стало явным

● Я исправил ошибку и этой оптимизацией добился ускорения еще в два раза

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

Что мешало

● PL/pgSQL – плохой, негодный язык● Я так и не понял, как в нем сконструировать программно множество из нуля строк, поэтому возвращал, при необходимости, такое множество, делая SQL-запрос в специально заготовленную пустую таблицу с нужным списком полей

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

Что еще удалось

● В качестве dashboard к Graphite я поставил Grafana

● Систему отдачи файлов я переписал на смешанную асинхронно/синхронную, используя для внутренних нужд HTTP status 418 I'm a teapot

● “Асинхронная” не значит “быстрая” (наоборот), но значит “экономичная”

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

Что было дальше

● Я пытался тюнить кастомный репликатор, но быстро понял, что это невозможно – он работает на пределе

● Я решил заменить его на какое-то общее средство репликации и выбрал Bucardo

● Bucardo в тестовом режиме работало отлично, но из 6+Tb базы среплицировало 1Tb

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

WTF?

● Know your weapon:● Large objects – это просто еще один key-value storage, по сути

● Репликация ими не занимается● При этом в нашей базе мы никогда их не перезаписываем после создания!

● Кроме того, у них уникальные номера

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

И вот тут мне карта пошла!● Object storages:● LeoFS● OpenStack Swift● Elliptics● Riak CS● Ceph Object Gateway● Но это другая история, и она еще не закончена

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

Выводы

● Иногда, прежде чем сказать “давайте перепишем всё”, стоит попробовать переписать не всё

● “Переписать всё” - тоже выход, надо только уметь писать и знать, где взять оружие

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

Спасибо за внимание!

● Пожалуйста, ваши вопросы!● С вами был Александр Чистяков,● Главный инженер Git in Sky● http://twitter.com/noatbaksap● [email protected]● http://gitinsky.com,http://meetup.com/DevOps-40