Pull to refresh

Comments 53

Возникли вопросы:
1. Приложение работает только с одним мастером или с несколькими сразу?
2. Кто определяет с каким мастером должно взаимодействовать приложение? Есть ли балансировка?

P.S. Было бы интересно почитать про federated таблицы.
Одна нода приложения работает с одним мастером, но все ноды поделены на 4 «зоны», это просто 4 разных окружения, с немного отличающимися параметрами и своими инстансами кешей, например. Каждая зона работает со своим мастером, поэтому можно сказать что приложение в целом работает со всеми 4мя одновременно, т.к. все активные и работают параллельно. Выбор мастера — вопрос балансировки по этим зонам, тут конечно мы имеем возможность распределять веса и отправлять на любую из них нужную долю пользователей. и со стороны приложения, конечно можем в любой момент сказать в какой зоне какой мастер использовать.

Про federated — часть некритичных для логики данных (логи заказов, например) — вынесены в отдельный кластер mysql и под них делается отдельный коннект. чтобы иметь возможность делать джоины тех же данных заказов между разными БД, например, основную таблицу заказов сджоинить с логами, можно извращаться, а можно использовать federated таблицы (хотя это тоже извращение :)). Этот federated надо использовать очень осторожно, потому что любой запрос требующий поиска по таблице без первичного ключа приведет к тому, что вся таблица будет полностью копироваться по сети. точно также и в обратную сторону на отдельной реплике для аналитиков мы сделали и обратный проброс таблицы с логами в основную БД.

И для некоторых критичных данных, типа данных заказа, приложение может по оффсету сходить в любой "соседний" нужный мастер :)

Фасеты/фильтрация тоже только Mysql или есть elasticsearch?

Тот же вопрос про поиск.

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

Как вы решаете проблему пересчёта эластика, когда приходит обновление наличия/цен? Он же пожиратель всего в такие моменты.

Или такой проблемы нет?..

И вдогонку про поиск: вот такие запросы (без пробелов в середине) — это что за магия? Это не полнотекст (из-за отсутствия пробела).
(вангую, что перед полнотекстом стоит какой-нибудь яндексовский спеллер на основе Catboost или поднятый, или дёргается по API)
Про МВ не скажу, там своя кухня на фронте. Про эльдорадо — нет никакой магии, есть список алиасов и автоматических преобразований слов. вся история поисков и статистика по словам естественно ведется. +еще во многих регионах работает внешний сервис anyquery, про его магию не в курсе, но думаю что примерно в эту же сторону работает, т.к. судя по отзывам тех кто работал в админке там много как раз такой ручной настройки. сам руками внутренности не щупал.
Про пересчет — есть несколько моментов. реалтайм обновление остатков и цен для нас не сильно больно сейчас (раньше на древнем железе с шпиндельными дисками было хуже), намного больнее для нас — полный пересчет индексов (и он архитектурно пока тоже нужен, т.к. далеко не все данные уходят в реалтайме).

И Вам даже ребята из Percona не помогли? Или не писали им? Или денег не хватило на их услуги? Только честно.


Почему сидите на древнем mysql? Я так понял что 5.6 и местами 5.5, по тексту про 5.7 вроде не было упоминаний.

Им тоже писали и они входят в список тех кто либо не ответил, либо отказался, но мы специально в статье не указываем имен :)
Сейчас везде 5.7. До этого сначала было везде 5.5, потом слейвы 5.7+мастера 5.5, и самая боль про обновление мастеров (хотели сначала до 5.6 мягко, а получилось в итоге сразу на 5.7) — как раз в статье.

5.7 это хорошо, просто из статьи немного не очевидно, что Вы убили 2-х зайцев.

А почему не перешли на 8.0?
Мы посчитали такой переход менее рискованным, чтобы не наткнуться на новые грабли 8й версии, и про переход на 8ку можно будет еще статью написать. Просто чтобы некоторые баги поймать надо неделями гонять нагрузочные тесты :)
Почему вы не используете gtid?
gtid-mode = ON
binlog-transaction-dependency-tracking = WRITESET

Позволяет накатывать binlog реплики в несколько потоков и значительно уменьшить отставание (при должной настройке)?

Тут не претендуем на объективность, он нам не понравился в ситуациях потери части транзакций или скипа части бинлога. проигрывание и восстановление может превратиться в боль, списки проигранных сетов тоже. В идеальном мире наверное да, но при нашем потоке изменений — пока не стали менять. Может быть когда-нибудь в будущем ещё потестим и включим, но пока не так уж хочется.

У нас на 40 000 QPS жило достаточно долго и не было таких проблем, потом нагрузку снизили изменением архитектуры. Как понимаю тут боль совсем не в базе и не в монолите как таковом, а в архитектуре монолита, которая не позволяет шардировать данные. Чтобы не быть голословным:

У нас тоже нет таких проблем, но мы всегда рассчитываем на худшее в действиях пользователей и готовимся заранее ) Можно несколькими костылями пошардить часть данных, но лучше и проще сделать нормальное решение рядом и вынести. Основная боль — это как раз из сильносвязанного монолита вычленить то, что можно будет сделать слабосвязанным. Сервис новый написать это где-то 5% трудозатрат имхо)

Статейки неплохие, спасибо! можем вам рассказать как делать большие альтеры без боли и влияния на пользователей в любое время :)
Очень интересно! Особенно если это не про pt-online-schema-change, не сливает всю таблицу в репликацию, не аффектит триггеры и совместимо с foreign keys.
с такими вводными наверное не будет нормальных решений, кроме как не использовать триггеры и внешние ключи :) а насчет инструмента — мы юзаем gh-ost. всю таблицу он, конечно, сливает, но делает это умно — меедленно с контролем отставания по другим слейвам перетаскивает данные из основной таблицы. при этом он цепляется как еще один слейв к мастеру и читает поток изменений из бинлога, и эти изменения проигрываются и накатываются на таблицу-копию. Когда данные синхронизированы, вешает небольшую блокировку на несколько секунд и в этот момент ренеймит таблицы. Внешние ключи конечно не дадут такие махинации делать. по принципу очень похоже на pt-schema-change, но чуток поумнее.
Спасибо, интересное решение. У нас есть триггеры на огромных таблицах и foreign keys на некоторых. Триггеры не позволяют использовать pt-schema-change. И отказаться от них нельзя.
Вы пишите в статье о binlog_transaction_dependency_tracking, но совершенно не говорите о transaction_write_set_extraction, а эти 2 параметра очень сильно связаны друг с другом и изменить binlog_transaction_dependency_tracking с COMMIT_ORDER на WRITESET как указано в статье без изменения transaction_write_set_extraction невозможно!

И второе это баг в MySQL связанный с binlog_transaction_dependency_tracking который исправили только в 5.7.33, у нас например при попытке включить WRITESET реплика через некоторое время просто развалилась и перестала работать. Вот такой милый параметр.

P.S. У нас 45 kQPS, местами 50 kQPS и в принципе все гуд.
Спасибо, полезное замечание, подправил. Естественно привел не все настройки, только те которые показались интересными. У нас transaction_write_set_extraction = XXHASH64
40к rps мы привели просто как текущее значение, для общего понимания масштаба.
в высокий сезон это 1.5-2х от этих значений, но это скорее повод задуматься об оптимизации кода, чем об оптимизации кластера.

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

О, спасибо. Я похоже не внимательно читал доку. Не знал что эта опция может улучшить параллельность выполнения.
Получается binlog_transaction_dependency_tracking=WRITESET, требует transaction_write_set_extraction=XXHASH64 (или MURMUR32). А transaction_write_set_extraction в свою очередь требует binlog_format=ROW.
Итого:


binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64
binlog_format = ROW

Эх, не хотел на ROW формат переходить. Уж очень много места сьедает, сейчас итак за ден, бинлоги на 250GiB скапливаются.

получилось в статье совсем неочевидно, хотя картинку старался делать максимально детальной :)
вот тут подробнее. в нашем кейсе потоки данных физически идут от разных мастеров, каждый поток стартует как обычная репликация, но работает с добавлением ко всем командам FOR CHANNEL='masterN'. На каждом слейве у нас сразу 4 мастера. И если транзакции с них при этом не имеют общих блокировок, то выполняются параллельно в рамках одного таймстемпа. ключевое для повышения параллельности обработки — slave_parallel_type и binlog_transaction_dependency_tracking, но как уже тут писали — не совсем безопасно и не стоит просто так крутить из дефолтов особенно в некоторых минорных версиях 5.7.

У меня один мастер. Я пробовал slave_parallel_type=LOGICAL_CLOCK и разное количество воркеров. Это никак не помогает. Параллельность выполнения проверял как в этой статье. Но теперь я знаю что эту ситуацию можно улучшить с помощью binlog_transaction_dependency_tracking.


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

В первую очередь это нужно чтобы любой мастер можно было выключить в любой момент и легко пережить аварию/обслуживание. Но это точно далеко не самый типичный сетап. Я про крайней мере не знаю пока никого, кто делал бы так же. а так да, у каждого треда свой поток applier'a. И немного это разгружает, т.к. часть запросов по бинлогу приходит как раз в row, но конечно не по общему объему записи.

В первую очередь это нужно чтобы любой мастер можно было выключить в любой момент и легко пережить аварию/обслуживание

Тогда мне непонятно как вы этого достигли. Ранее вы писали:


Одна нода приложения работает с одним мастером, но все ноды поделены на 4 «зоны», это просто 4 разных окружения

У вас бэкенды поделены на 4 типа, и каждый пишет в свой мастер. В такой конфигурации непонятно, как можно в любой момент отключить хоть один мастер. Тут только наверное если использовать какой-то proxy, например proxysql, который видит что мастер упал/отключен и в этом случае направляет трафик бэкенда на другой мастер.

Приложению всегда можно сказать "иди в другой". Это можно и как фоллбэк при невозможности выполнения запроса юзать, и просто переключая настройки енва по срабатыванию мониторинга.

Сам же монолит сейчас находится в процессе распила на некоторое количество отдельных и независимых сервисов, часть из которых мы будем шардировать и рассказывать вам о полученном опыте — следите за обновлениями

Можно приоткрыть завесу тайны — что останется от кода битрикса после распила?
у нас тайн нет. Стратегически — ничего не останется, уже существенная часть работает без него, да и последние апдейты php только повышают вероятность такого сценария. Остались самые тяжелые места для переноса. но на их распил до конца легко может уйти еще года 2-3.
Напишу, вдруг кому полезно будет.
Уже достаточно давно использую решение такой проблемы (на Mysql5.5, Percona5.6, Percona5.7) в случаях, когда мастер и слейв заранее известны и автоматически меняться местами не будут.

Минусы такой схемы:

Слейв до завершения выполнения транзакции еще не знает, что он отстает в slave_status по seconds_behind_master.


Делается таблица max_delay_sec int, date_last_update datetime и 2 евента: для реплики и для мастера.
На мастере поле date_last_update обновляется евентом раз в 2 секунды.
На слейве сравнивается date_last_update с now() и выясняется приемлимость задержки.
После того, как в бд есть реальная задержка репликации можно уже что угодно делать.

Например, у меня закрывается возможность подключения для хапрокси:
Update mysql.user  
    set Host= (select if(abs(timestampdiff(second, date_last,now()))<=Setup.max_delay_sec ,'%','127.0.0.2')  
                     from Setup 
                     Where id_Setup=1
                  ) 
    Where user='robot_haproxy';
  If row_Count() is not null and row_count()>0 Then
      flush privileges;


Как бы коряво этот костыль не выглядел, но он успешно живет 6 лет и перенаправляет/разделяет поток чтения с базы в условиях, когда железо виртуальное, исключительно нестабильное.
Сейчас изменение пользователя заменено на скрипт типа такого github.com/olafz/percona-clustercheck
выглядит немного костыльно и, конечно, есть вероятность блокировать весь кластер при неудачном тяжелом запросе на мастере.
Тут больше о том, что таким евентом можно точно знать отставание. А что делать с отставанием — другой вопрос.

в этой части — да, сами примерно также когда-то мониторили. но сейчас от этого ушли, больше нет такой острой необходимости. сейчас мы мониторим все блокирующие запросы на мастерах (также как и долгие селекты), это получилось полезнее и позволяет еще до попадания в бинлог все найти и отреагировать.
Я помню facebook писал об этой проблеме давно-давно (не знаю что у них с базой сейчас). У них было 2 ДЦ, основной и вторичный. Вторичный брал трафик, который к нему ближе, но обновления посылал в основной. Проблему они решили тем, что при изменениях ставили браузеру временную cookie, которая load-balancer-у говорила: «проксируй все запросы в основной ДЦ». Я подумал, что интересное решение.
а проблема с отказом одного и мастеров и потерей данных не решилась никак я понимаю?
с MySQL очень давно работал с 5.1 еще. Потом все на MS SQL.
потом пришлось переехать на версию MySQL повыше и таких детских болячек (раньше null в поле not null вставлялось, а в новой версии перестало) я в MS SQL не встречал.
Эльдорадо не может себе позволить СУБД с поддержкой?

Такой проблемы с потерей данных, к счастью не было никогда. Худшее что происходило — меняли материнскую плату на тогда ещё железном сервере с мастером. К нашему счастью отказ материнки позволял нормально работать до поставки вендором.
А насчёт БД с поддержкой, у нас полно оракла и есть несколько саповских монстров типа ханы (не конкретно в ИМ eldorado, а в целом в мвидео-эльдорадо), но наличие поддержки не гарантирует отсутствие проблем ни в одной из них.

Почему стали переделывать — понял. Но не понял, почему не посмотрели в сторону альтернатив, типа галеры. А если смотрели — что помешало?

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

Не думали перейти на облачные технологии? Типа Amazon Web Services.

Вот как сделано у нас:
Для отказоустойчивости hot-cold кластер на RHEL. Подключен SAN storage к активному ноду а так же VIP (virtual IP адрес указывает на активный нод). Между нодами есть хардбит. И если один нод упал(чего небыло никогда) то SAN storage и VIP адрес подключаются ко второму ноду. Все, что увидит клиент это то, что нет ответа от сервера пока mysql не поднимется на втором холодном ноде.

Для того чтоб в базу можно было быстро писать и так же быстро читать и не делать master-slave кластеры то сделано что-то типа materialized view. Это две базы. Основная, куда можно быстро и эфективно записывать и вторая View в которую посредством триггеров переносятся изменения из основной базы. Это дает возможность сделать разные способы чтения и записи(к примеру индексы сделаны по разному в таблицах куда будем писать и откуда будем читать).

Регулярно думаем об облаках, но есть нюансы и ограничения: облака только российские (яндекс/mail/крок), а ещё лучше собственное где твои ресурсы точно не будут конкурировать с кем-нибудь ещё. С зарубежными намного больше рисков, какими бы удобными они ни были, хотя и их тоже рассматривали.
Насчёт вашего примера — одна большая нода с пассивным стендбаем — это скорее про оракл, для мускуля (даже если она будет очень толстая) — это плохо по целому ряду причин. Такую толстую ноду мускуля легко уронить в сегфолт простым сбросом кверикеша, или как она будет долго висеть в блокировках при большом рпс?.. Главный вопрос — как ее будут дальше скейлить на чтение при росте нагрузки x10? Из вашей схемы пока мне неочевиден 100% выигрыш, т.к. при больших объемах записи вам надо строить больше индексов для нескольких представлений, т.е. это некий tradeoff для случаев большого чтения, ценой удорожания записи. Но в целом интересный подход, для каких-то кейсов точно подойдёт.

Если возрастет нагрузка на чтение\запись в х10 то это не проблема. И блокировки тоже не проблема. Потому как есть два сервиса. Один в базу пишет а второй читает(раз в день из базы читает все данные и держит в памяти). И когда происходит запись то сервисы посылают друг другу сообщения, что данные в базе изменились. Сервис который из базы читает рассылает подписчикам изменившиеся данные(дельты) через low latency протокол работающий по UDP. Все сервисы работают в режиме hot-hot кластера и тоже общаются по low latency протоколу. Единственная неприятность в hot-hot схеме это возможный Split-brain но это крайне редкие события.

Это уже не совсем бд) это сервис, который ещё и в память может вместить все. При таких вводных тут совсем другой подход. (Для подписчиков видимо интеграция через grpc?)
Но чем толще данные, тем больнее падение такой системы и время восстановления будет.

grpc как я понимаю сделан на основе google protobuf. Я говорил о протоколе для быстрой доставки данных IBM WebSphere Low Latency Messaging.
Такую толстую ноду мускуля легко уронить в сегфолт простым сбросом кверикеша

ЕМНИП кверикеш давно не рекомендуется к использованию, он блокирующийся внутри и плохо масштабируется с ростом числа ядер.

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

мне привычнее видеть кэширование вынесенным в memcached/redis. конечно, больше ручной работы, но результат более стабильный.

Интересно, сколько еще компаний не проапгрейдились на более новые mysql/mariadb? Уж 5.7 вышел давным-давно, и вот спустя все эти годы — такие героические решения.

Я думаю многие. Кто-то ещё на фортране пишет до сих пор :) и в нашем зоопарке софт на delphi вполне активно используется.
Конкретно в нашем случае, не будь кольца, обновление бы прошло, конечно, намного раньше.

Sign up to leave a comment.