7 ошибок одного Black Friday. Основано на реальных событиях

Kate

Administrator
Команда форума
Всем привет! Меня зовут Влад Опухлый, я — Magento Tech Lead. Последние 5 лет я работаю в компании Magecom. В этой статье расскажу об истории одного из самых сложных кейсов за всё то время, которое я занимаюсь Magento-разработкой. Это были действительно безумные сутки с большим количеством людей на созвоне, когда мне пришлось работать около 30 часов подряд. С тех пор я не люблю проводить Black Friday в офисе.

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

Это статья основана на моём докладе с Magento Meetup Online #8, который я доработал и добавил больше конкретики и советов.

photo_2021-11-03_18-18-45.jpg
Иллюстрации Марии Рыбак

Ошибка #1: Вера в безупречность дорогого Payment Method​

Мы переезжали с Magento 1 на Magento 2, у нас было примерно 750–800 тыс. заказов и чуть меньше кастомеров. В целом, не мало, но и не так много для Magento.

После нашего переезда на Magento 2 нужно было перенести все payment methods, чтобы у пользователей была возможность повторно совершить ордер и использовать данные карт, внесенные ранее за прошлые 5–7 лет существования на Magento 1.

Проблема заключалась в том, что payment method, который существовал для Magento 1 не существовал для Magento 2. Нужно было искать аналоги, тестировать и применять их быстро, желательно в течение недели.

На хорошее тестирование времени, к сожалению, не хватало, особенно на тестирование продакшн credentials.

Клиент хотел сделать так, чтобы релиз произошел переключением с Magento 1 на Magento 2 в течение максимум 30 минут, что позже вылилось для нас самой неприятной ошибкой из всех перечисленных.

Уже в саму Black Friday мы заметили, что трафик, который пошёл на сайт, начал сильно нагружать базу данных, при чем мы ранее не замечали этой нагрузки.

Мы увидели странный SQL-запрос, который увеличивал нагрузку на базу данных в геометрической прогрессии.

Целью этого запроса была проверка fraud-detection system модуля Cyber Source. Это модуль платежной системы, который маркировал ордера как мошенник/не мошенник, нужна ли перепроверка или можно обрабатывать и списывать средства автоматически.

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

Поэтому, когда у нас пошёл большой трафик и нагрузка на чекаут, мы увидели, что этот запрос начал выполняться около 8 секунд. После этого нагрузка пошла по экспоненте. И это было самое начало того, когда сайт в первый раз ушёл в даунтайм. Нагрузка на базу увеличилась, сайт начал тормозить, клиенты начали звонить. Довольно неприятное начало длинного дня.

Мы начали быстро искать причину.

Первое, что пришло на помощь — AWS DB monitor, New Relic и SQL slow query.
С помощью просмотра данных статистики был найден самый тяжелый и медленный скрипт. Как оказалось, поле, по которому выполнялась выборка, было без индекса, и добавление индекса помогло убрать бешеную нагрузку от запросов.

Это быстро спасло ситуацию, но с этой ошибки я вынес для себя урок. Если мы покупаем дорогой и популярный third-party модуль, это ещё не значит, что он будет работать идеально и быстро.

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

Все делают ошибки, и не все вендоры имеют окружение, похожее на ваше. Не все проверяют большую нагрузку. В общем, чтобы спать спокойно, нельзя слепо верить решениям, которые вы купили у third-party вендоров.

Ошибка #2: «Стандартные» лоад-тесты​

60/30/10 — это примерная пропорция того, как распределяется трафик на сайте, где:

  • 60% трафика сёрфят по закэшированным страницам;
  • 30% трафика занимаются поиском;
  • 10% находятся сейчас на чекауте.
Когда мы только запустили первую версию тестового сайта, еще до миграции и переноса данных, мы заметили, что при нагрузочных тестах появились проблемы с Elasticsearch, именно с теми 30% трафика.

В случае, если не все запросы покрываются Elasticsearch, увеличивается количество запросов на базу данных, и это в свою очередь замедляет следующие 10%, которые находятся на чекауте.

Как вы знаете, страница чекаута (и корзины) не кэшируется. 60% трафика кэшируется при помощи Varnish и Full Page Cache, а 30% сохраняется в Elasticsearch — запросы возвращаются быстро и не сильно нагружают базу. А с теми 10%, которые должны идти на чекаут, дела обстоят хуже.

Как было сказано выше: самые сложные страницы — чекаут и корзина. Они обе не кэшируются и добавляют очень много нагрузки на базу данных.

При посещении этих страниц многими пользователями в базе данных создаются сессии, но не успевают закрыться. И как только эта волна из необработанных сессий скапливается, базы данных очень сильно нагружаются. Настолько сильно, что на самой дорогой базе данных в AWS, запрос на выборку одного поля с таблицы на 3 записи может выполнятся 15 секунд.

Почему универсальные нагрузочные тесты помогают не всегда​

Дело в том, что у нас никогда не бывает абсолютно такого же сервера и трафика в синтетических условиях, как в реальной жизни.

В нашем случае причиной обрушившегося чекаута и сайта был тот факт, что пропорция на самом деле составляла 30/20/50, где 50% трафика ушла сразу же на чекаут! Да-да, 50%.

Клиент забыл уточнить очевидное для них, но непонятное и неожиданное поведение аудитории сайта для нас.

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

Поэтому трафик ещё за несколько дней был низким, а именно в Black Friday все пользователи ринулись на чекаут, а многие даже заранее сложили в корзину акционные товары.

Пользователи сайта начали замечать, что чекаут работает медленнее, а самое простое решение — обновить страницу, а лучше сделать это несколько раз, что вызывало еще больше сложных PHP/SQL и AJAX-запросов.

В результате, сессии не закрывались и накапливались как снежный ком, пока база данных не начала отвечать на запрос SELECT * FROM Customers в течение 20 секунд.

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

Быстрым и временным решением проблемы, чтобы избежать потери заказов, было открыть второй канал продаж на какое-то время, чтобы избавить от огромного количества людей на чекауте и перенаправить поток. Спасало и то, что у клиента было больше 40 тыс. представителей в США и Канаде, и они позволили выжить кастомер-саппорту.

На уровне сервера, на 502 страницу была добавлена ссылка на страницу помощи, куда можно дозвониться, чтобы хоть немного уменьшить количество одновременных пользователей на чекауте, которые в пике переваливали за 1000!

Это большая нагрузка для Magento. Возможно, вы справлялись с нагрузкой, когда 1000+ кастомеров на чекауте хорошо обрабатывались, но это была не наша история. Мы тогда только мигрировали Magento и 2–2,5 месяца работали по плану, а все лоад-тесты успешно проходили.

Ошибка #3: Плохая координация команд​

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

Уже после релиза, когда мы думали, что всё прекрасно, команда клиента решила, что уже тесно взаимодействовать нам не нужно и без предупреждения отправила 700 000 емейлов одновременно. Это был очередной сюрприз, о которым нас не оповестили.

Как только мы вытащили сайт из даунтайма, и вроде нагрузка чуть уменьшилась, и сайт начал работать стабильнее, начали приходить заказы — 3–5 штук в секунду.

Мы начали замечать, что просадка уже шла не только по базе данных, а и по процессору. На то время была поднято два сервера по 48–50 ядер в AWS — громадные ресурсы, но даже их получилось убить, так как мы создавали второй сервер, и команда клиента решила, что они будут их администрировать.

Мы создали один сервер, настроили, протестировали — всё было неплохо.

После этого команда клиента решила, что нужно сделать ещё один сервер, не известив нас об изменениях в инфраструктуре. Они просто сразу же всё клонировали.

Проблема была в том, что в одном образе сервера был и Varnish, и Nginx, и PHP-FPM — всё в связке. То есть вместо того, чтобы был один Varnish, за Load Balancer и два сервера отдельные с PHP-FPM. У нас всё получилось так, что в каждом сервере по одному инстансу Varnish. Это привело к тому, что кэш на серверах не прогревался полностью, и запросы уходили на Magento, минуя Varnish и не будучи зашифрованными.

Добило это очередное промо, разосланное на 700 000 емейлов, в котором была ссылка с набором параметров, уникальных для каждого юзера. Из-за спешки никто не согласовывал вид емейла и ссылки, и все эти запросы ушли мимо Varnish, нагрузив базу и добавив большое количество людей на чекауте.

У нас уже не было проблем с платежной системой, которая выполняла медленные запросы, но мы увидели, что линейно начала нарастать нагрузка на базу и увеличилась нагрузка на процессора. В итоге, два сервера по 50 ядер были загружены почти на 100%.

Молниеносным решением клиентской команды DevOps было увеличить ресурсы. После того, когда два сервера по 96 ядер были нагружены уже на 70%, ситуация стала улучшаться. В целом, больше не терялось время на обработку критического количества запросов и ордеров.

Как только показалось, что мы всё разрулили, мы столкнулись с очередной проблемой. Заказы сформированы — пришло примерно 7 000 заказов за 2 часа. Сайт справился, и их нужно было обработать, запроцессить и начинать отправлять.

ERP клиента тоже не была безупречной, хорошо протестирована для работы в нагрузке и не смогла вытащить такое огромное количество запросов за раз и начала отказывать. Срочно пришлось писать SQL-запрос, изменяющий статус на временный, и потом обновлять по 300–500 ордеров за раз тоже при помощи SQL, чтобы их можно было вытащить во внешнюю систему, обработать и отправить клиентам. Это тоже заняло примерно полчаса-час и делалось в спешке, примерно на 20-й час от старта Black Friday.

photo_2021-11-20_21-21-42.jpg


Ошибка #4: Отличия в настройках серверов​

Следующее, с чем мы столкнулись — проблемы в конфигурации Load Balancer.

Мы заметили, что второй сервер начал перегружаться сильнее, и пошёл перекос в пропорции 30% на 70%. Было сложно быстро найти причину, но позже увидели, что одним и тем же пользователям постоянно прилетают одинаковые ответы от AWS, что означало, что юзеры фиксируются за серверами.

Это нормальная практика, если не используется Redis Cache, который расшарен для сессий между несколькими серверами. Если бы у нас было, например, файловое хранилище сессий, было бы неплохо. А мы получили перекос по серверам и нагрузке, что сделало работу части трафика нестабильной и не позволяло эффективно использовать ресурсы.

Ещё после Black Friday мы запросили архитектуру, состоящую из двух серверов, Load Balancer и Varnish, настроенную так же, как и на лайве, потому что первичные нагрузочные тесты шли на архитектуру с одним сервером, но с большим количеством ядер.

Это позволило позже найти проблемы в архитектуре и поправить их для того, чтобы не сталкиваться на продакшене. А ещё чтобы количество ошибок, которые есть на проде, было максимально похожим с тем, что на UAT, и чтобы приходилось писать меньше дополнительных логгеров и искать неприятные ошибки с прода.

Ошибка #5: Коробочные настройки Magento/AWS/MySQL​

В ходе работы над нашим кейсом мы столкнулись с тем, что база данных — это всегда боттлнек.

База данных всегда выдерживала меньше всего нагрузки, и из-за спешки протестировать репликацию мы не успели. Соответственно, Read Replica не была настроена, мы не использовали Magento Cloud, поэтому базе данных приходилось хуже всего.

В один из пиковых моментов нагрузки, когда трафик уже начал угасать, мы связались с DevOps на нашей стороне и спросили, почему база данных так сильно нагружается, и количество необработанных сессий не уменьшается.

Позже мы оптимизировали настройки базы и поняли, что большинство из них были коробочные. Когда база была не настроена, был большой инстанс AWS, 48 ядер. Для того, чтобы молниеносно улучшить performance, она была увеличена в 2 раза. Она обработала и начала работать чуть хуже и со временем тоже начала не справляться.

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

После прогонки, донастройки базы и перезагрузки на 48 ядрах, она начала работать в нагрузку 50%, хотя до этого 96 ядер заходили за 100%.

Соответственно, необходимость проверки настроек базы и настройки репликации огромная. Если вы планируете большой трафик, то без репликации, Read Replica и коробочных настроек базы данных всё может не сработать.

Ошибка #6: Медленный код​

Мне как программисту сложно сказать, что причина медленного сервера и медленно работающего сайта — медленный код, но такое тоже случается :)

Причины медленного кода и что повлияло на нашу историю:

Причина #0. Бюрократия и согласованность со срочными релизами.

Причина #1. Недостаточное время, заложенное на код ревью.

Когда мы гнались за релизами, спринтами, датами и успевали, а клиенту всё нравилось, то очевидно, у нас не хватало времени на то, чтобы всё посмотреть, отрефакторить, сделать лучше и эффективнее.

То есть выбирая между тем, чтобы успеть к юбилею бренда или сделать красивый, но ненужный код, приходилось жертвовать качеством.

После Black Friday необходимость в более тщательных тестах и подготовке к релизам появилась.

Причина #2. Отсутствие пост-релизных performance тестов.

В Magento Cloud с коробки есть New Relic и Blackfire — невероятно классная штука на продакшене!

На нашем проекте был только настроенный New Relic, который тоже помогал проверить состояние и скорость работы, задержки и прочее. Хоть мы и пытались ставить Blackfire, но из-за специфической архитектуры его не смогли установить быстро, поэтому мы полагались только на данные New Relic.

Ошибка #7: Всё вместе​

В целом, у нас был очень быстрый сервер, классное оборудование, одни из самых больших инстансов и огромная команда из 30 человек.

Все мы спешили и даже успели, что было здорово, но вот потом всё начало рушиться.

При том, что мы протестировали нашу ожидаемую нагрузку — примерно 10 000 параллельных юзеров, 3 000 параллельно ищущих юзеров и около 100 юзеров на чекауте, — всё пошло не по плану.

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

photo_2021-11-20_21-21-39.jpg


Выводы и советы​

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

Профилируйте код после каждого релиза, а лучше до. Когда вы релизите 20–30 задач, некоторые из них могут работать с той же скоростью, как раньше. Когда же это всё идёт вместе, вы можете столкнуться с тем, что они используют те же таблицы, похожие запросы и могут даже немного конфликтовать или замедлять скорость в сумме.

При покупке third-party модулей не думайте, что цена = качество. Если модуль стоит больше $1000, всё равно нужно его протестировать, посмотреть логику и желательно сделать это на окружении, максимально близком к продакшену. Некоторые ошибки никогда не появляются на стейджинге, UAT, а когда они появляются на продакшене, может быть слишком поздно.

Добавляя рисковый функционал в срочном порядке для клиента, всегда добавляйте выключатель. Так как redeploy может занять время и стоить больших денег и репутации. Когда есть очередь из юзеров на чекауте, всегда проще добавить один маленький конфиг в админке, который позже можно рефакторить и убрать.

Изучайте поведение кастомеров на сайте. Если у вас предвидится большое количество юзеров на чекауте, внимательно смотрите на настройки базы и тестируйте базу на нагрузку. База — самый тонкий боттлнек, и огромное количество AJAX-запросов, которые идут к базе на чекауте, могут стать серьезной проблемой.

Полезные инструменты​

Google Analytics — полезный инструмент, который позволяет понять среднестатистическое поведение трафика. Можно зайти и посмотреть, сколько человек на чекауте в режиме реального времени, за определенный период или после отправки рассылок, например, чтобы понимать, на что ориентироваться.

New Relic позволяет увидеть самые тяжелые запросы и боттлнеки по базе. Можно посмотреть, как сильно загружена база и сколько сессий открыто. Больше всего блокируют базу открытые сессии, которые не смогли закрыться. Лимит на очень больших серверах, примерно на 100 ядрах, для баз данных составляет чуть больше 1000. В нашем случае сайт начинал работать очень медленно примерно после 1000 коннекшенов. После того, как решались проблемы с базой, эти коннекшены моментально закрывались и падали до нормального количества — 10–20.

Blackfire. Очень хорош для работы локально и в Cloud, так как он там настроен в коробке и позволяет сразу же посмотреть главные страницы, их скорость загрузки после того, как сайт продеплоился.

xDebug Profiler + PHPStorm. Можно использовать эту связку локально, она тоже позволяет протестировать отдельные страницы. Хотя я считаю, что связка Blackfire и New Relic куда лучше, чтобы видеть максимально близкие результаты к тому, что будет на продакшене.

Varnishstat. Это то, что помогло нам выяснить причину некэшируемых запросов, которые гробили базу после новостной рассылки. Varnishstat — команда, которую можно запустить на инстансе, где у вас крутится Varnish и посмотреть hit rate (количество запросов, которые попадают/не попадают в Varnish), логи, какие запросы по каким урлам идут. Когда мы увидели на нашем проекте, что большинство запросов, их поведение, характер и формат ссылки очень похожи, мы связались с командой, которая делала рассылку, и девопсами. Поняли, что для таких ссылок у нас не настроены правила кэширования для Varnish. А после того, как правило добавили, перезагрузили, нагрузка ушла с 95–100% до 12% спустя 3 минуты. Вот такая простая, но очень полезная утилита.

MySQL Tuner. Утилита, которая позволяет проверить, какие есть параметры в MySQL или MariaDB, например. Можно настроить, чтобы он работал эффективнее и использовал ресурсы эффективнее.

Полезная книга которая могла уберечь от стресса и многих ошибок: «Идеальный программист».

 
Сверху