С 2010 года мы разрабатываем сервис для организации совместной работы и управления процессами. Сейчас в нашей системе Pyrus работают тысячи организаций и десятки тысяч пользователей. За 4 года мы наработали неплохой опыт обеспечения надежности и хотим поделиться им с вами.
1. Сломаться может все
Мы стараемся подстелить соломку везде, где только можно. Все сервера базы данных 100% зеркалируются. В дата-центрах бывают регламентные работы по 2-3 часа, в это время наш сервис не должен прерывать работу. Поэтому зеркала расположены в разных дата-центрах, и даже в разных странах. Кроме того, на серверах необходимо регулярно устанавливать обновления безопасности, а они временами требуют перезагрузки. В таких случаях горячее переключение на резервный сервер оказывается как нельзя кстати.
В серверах стоят RAID, мы делаем ежедневное резервное копирование. У нас несколько серверов приложений, это обеспечивает масштабирование и позволяет обновлять их по очереди, не прерывая обслуживания. Для балансировки нагрузки мы используем механизм round robin DNS. Мы предполагали, что DNS – это самая надежная система Интернета, ведь без нее ни один сайт не откроется. Однако тут нас поджидал сюрприз.
Мы хостили доменную зону у крупного регистратора register.com, он обслуживает более 3 млн доменов. Как полагается, у нас 2 независимых сервера доменных имен (nameserver), что защищает от сбоя одного из них. Однажды утром отказали оба. Консоль управления register.com была недоступна. В Twitter начали появляться робкие жалобы пользователей, которые через час сменились лавинообразным потоком воплей, плача, стенаний и обещаний немедленно покинуть этого провайдера. Как только он включит сервера обратно.
С тех пор мы перенесли нашу доменную зону в Amazon, который предоставляет 4 сервера доменных имен, расположенных в разных корневых зонах Интернета: .com, .net, .org, .uk. Это дает дополнительный уровень надежности: даже если вся доменная зона .com будет по каким-то причинам недоступна в DNS, клиенты все равно смогут работать с нашим сервисом.
Вывод: проектируйте систему зная, что рано или поздно любой компонент откажет. Помните Мерфи: если есть вероятность того, что какая-нибудь неприятность может случиться, то она обязательно произойдёт
2. Вы не знаете, где узкое место вашего приложения
По мере роста нагрузки мы постоянно делаем 2 вещи: покупаем память (RAM) и оптимизируем приложение. Но как понять, какая функция в приложении работает недостаточно быстро? По синтетическим замерам в тестах на машине разработчика судить сложно. Запускать профайлер на боевом сервере практически невозможно – он добавляет слишком много накладных расходов и сервис начинает тормозить.
Приходится вставлять в код контрольные точки и оценивать скорость приложения по времени исполнения программы между ними.
Так мы выяснили, что 1/3 процессорного времени уходит на… сериализацию: упаковку структур данных в JSON-строки. Изучив альтернативные библиотеки сериализации, мы приняли непопулярное решение: написать свою. Реализация для конкретно наших задач работала в 2 раза быстрее самого быстрого доступного на рынке альтернативного решения.
Кстати, многие ошибочно полагают, что шифрование затрачивает много ресурсов процессора. Ранее действительно этот процесс мог «съедать» до 20% ресурсов CPU. Однако начиная с архитектуры Westmere, запущенной в январе 2010 года, команды алгоритма шифрования AES включены в набор команд процессоров Intel. Поэтому переключение с HTTP на HTTPS практически не изменяет нагрузку на процессор.
Вывод: не оптимизируйте преждевременно. Не проведя точных замеров, ваши предположения, что нужно ускорять, вероятно окажутся ошибочными.
3. Тестируйте все
Однажды нам понадобилось изменить структуру таблицы в базе данных. Эта процедура требует остановки сервиса, поэтому мы запланировали ее в наименее нагруженное время – ночью в выходные. Наши тесты показали время исполнения менее одной минуты. Мы остановили сервис и запустили процедуру на боевом сервере, однако она не окончила работу ни через одну, ни через десять минут.
Оказалось, процедура в некоторых случаях начинает перестраивать кластерный индекс в таблице, размер которой на тот момент был около 1TB. Мы не заметили этого, потому что проводили тесты на маленькой таблице. Пришлось, не дожидаясь окончания работы процедуры, запустить сервис. На нашу удачу все основные функции работали корректно, хотя и несколько медленнее обычного, за исключением прикладывания файлов к задачам. Процедура окончила работу через пару часов и 100% работоспособность была восстановлена.
Вывод: тестируйте все изменения на объемах данных, приближенных к боевым. Мы запускаем около 500 автоматических тестов при каждой сборке приложения, чтобы гарантировать отсутствие фатальных ошибок.
4. Скорость тестирования должна быть высокой
Мы выпускаем обновления приложения каждую неделю. Пару раз в год, несмотря на тестирование, в релиз вкрадывается ошибка, небольшая, но неприятная. Обычно такие ошибки обнаруживаются в течение 10 минут после релиза. В таких случаях мы выпускаем исправление (hotfix).
Никому не нравится откатывать релизы, но иногда это необходимо. Исправление нужно делать быстро, часто мы обнаруживаем причину ошибки в течение получаса. Но чтобы релиз попал на боевые сервера, исходный код должен пройти автосборку и автоматические тесты. Наши 500 тестов выполняются более 20 минут, это достаточно быстро, но мы планируем еще снизить это время путем большего распараллеливания.
При медленном тестировании мы не могли бы исправлять ошибки так быстро, а совсем без тестов количество ошибок было бы выше.
Вывод: не жалейте денег на ресурсы для разработчиков. Покупайте производительные сервера для автоматических тестов, количество которых будет постоянно расти.
5. Каждая функция продукта должна использоваться
Хорошие продукты требуют многих итераций. В продукт постоянно добавляются новые возможности, но часто необходимо вырезать редко используемые функции. Они не несут ценности: тратят время разработчиков на их поддержку и занимают лишнее место на экране.
Хороший садовник обрезает молодые побеги каждую весну и формирует правильную, здоровую и красивую крону дерева.
Есть ли в вашем продукте функции, которые не использует никто? В Pyrus мы таких не знаем.
Эмпирически мы выработали правило: каждую фичу использует минимум 2% пользователей. Это означает, когда мы отключаем функцию, десяткам или сотням людей это не нравится. Мы всегда предоставляем другой способ сделать то же самое, но привычка оказывается сильнее.
Вывод: развитие требует некоторых жертв. Представьте себе, скольким людям не нравится каждое изменение в продуктах Google и Microsoft.
Автор: Pyrus