Разработка
В защиту простых архитектур
Наша архитектура настолько проста, что я даже не буду приводить архитектурную схему. Вместо этого я расскажу о нескольких скучных вещах, которые помогают нам поддерживать скуку.
Wave — это компания с оценкой в 1.7 млрд долларов и 70 инженерами (если вы хотите рассчитать соотношение, то при последнем привлечении финансирования и оценке в 1.7 млрд долл. у нас было около 40 инженеров), продуктом которой является CRUD-приложение для сложения и вычитания чисел. В соответствии с этим наша архитектура представляет собой стандартную архитектуру CRUD-приложений — монолит Python поверх Postgres. Начало работы с простой архитектуры и решение проблем простыми способами, где это возможно, позволило нам масштабироваться до таких размеров, пока инженеры в основном были сосредоточены на работе, приносящей пользу пользователям.
Stackoverflow успешно масштабировался на монолите (архитектура 2013/архитектура 2016), и в итоге был продан за 1.8 млрд долл. Если рассматривать трафик, а не рыночную стоимость, то Stackoverflow входит в Топ-100 самых посещаемых сайтов в Интернете (многие другие примеры дорогих компаний, построенных на основе монолитов, см. в ответах на эту тему в Twitter. У нас не так много веб-трафика, поскольку мы являемся мобильным приложением, но Alexa все равно помещает наш сайт в Топ-75, хотя наш сайт — это, по сути, просто способ найти приложение, и большинство людей находят приложения даже не через наш сайт).
Существуют некоторые виды приложений, требования к которым делают простой монолит поверх скучной базы данных нецелесообразным, но для большинства видов приложений, даже при уровне посещаемости сайтов из Топ-100, компьютеры достаточно быстры, чтобы приложения с высоким трафиком могли обслуживаться простыми архитектурами, которые, как правило, могут быть созданы дешевле и проще, чем сложные.
Несмотря на непостижимую эффективность простых архитектур, множество авторов уделяет внимание сложным архитектурам. Например, на недавней конференции по общим технологиям было шесть докладов о том, как строить или бороться с побочными эффектами сложных архитектур, основанных на микросервисах, и ноль — о том, как можно построить простой монолит. О квантовых вычислениях было больше докладов (один), чем о монолитах (ноль). На более крупных конференциях ситуация аналогичная: на недавней конференции по корпоративным технологиям в Сан-Францисок было двузначное число докладов о том, как справиться со сложностью сложной архитектуры, и ноль — о том, как построить простой монолит. На последней конференции меня поразило то, что многие участники, работавшие в компаниях над небольшими приложениями, которые могли бы быть построены на простых архитектурах, скопировали последние и самые сложные методы, популярные на конференциях и в HN.
Наша архитектура настолько проста, что я даже не буду приводить архитектурную схему. Вместо этого я расскажу о нескольких скучных вещах, которые помогают нам поддерживать скуку.
В настоящее время мы используем скучный, синхронный Python, что означает, что наши серверные процессы блокируются в ожидании ввода-вывода, например, сетевых запросов. Ранее мы пробовали использовать Eventlet, асинхронный фреймворк, который теоретически позволил бы нам добиться большей эффективности от Python, но столкнулись с таким количеством ошибок, что решили, что затраты на процессор и задержки при ожидании событий не стоят той оперативной боли, которую мы должны были испытывать при решении проблем с Eventlet. Существуют и другие известные асинхронные фреймворки для Python, но пользователи этих фреймворков при масштабировании часто также сообщают о значительных проблемах, связанных с их использованием в масштабе. Использование синхронного Python дорого, в том смысле, что мы платим за процессор, который не делает ничего, кроме ожидания во время сетевых запросов. Но поскольку мы обрабатываем только миллиарды запросов в месяц (пока), стоимость этого невелика даже при использовании такого медленного языка, как Python, даже при оплате розничных цен публичного облака. Стоимость нашей инженерной команды полностью доминирует над стоимостью систем, которыми мы управляем.
Вместо того чтобы брать на себя сложность создания асинхронного монолита, мы передаем долго выполняющиеся задачи (на которые мы не хотим блокировать ответы) в очередь.
Где мы не можем быть такими скучными, как хотелось бы, так это в наших локальных центрах обработки данных. Когда мы работали только в Сенегале и Кот-д’Ивуаре, мы полностью работали в облаке, но по мере расширения в Уганде (и в других странах в будущем) нам приходится разделять бэкэнд и размещать его на локальных центрах, чтобы соответствовать местным законам и нормам, регулирующим хранение данных. Это не совсем простая операция, но, как знает тот, кто делал то же самое со сложной сервис-ориентированной архитектурой, эта операция намного проще, чем если бы у нас была сложная сервис-ориентированная архитектура.
Еще одна область — это программное обеспечение, которое нам пришлось создавать (а не покупать). Когда мы начинали, мы предпочитали покупать программное обеспечение, а не создавать его, поскольку команда из нескольких инженеров не может позволить себе временные затраты на создание всего. В то время это был правильный выбор, даже несмотря на то, что при покупке обычно получаются неработающие инструменты. В тех случаях, когда поставщиков не удается убедить исправить ошибки, которые для нас являются критическими, имеет смысл создавать больше собственных инструментов и поддерживать собственную экспертизу в большем количестве областей, что противоречит стандартному совету, согласно которому компания должна выбирать «создание» только в своей основной компетенции. В некоторых категориях продуктов даже после достаточно обширных исследований мы не нашли ни одного поставщика, который мог бы предложить нам подходящий продукт. Справедливости ради следует отметить, что проблема, которую должны решить поставщики, чтобы предоставить нам работающее решение, гораздо сложнее, чем та, которую должны решить мы, поскольку поставщики берут на себя сложность решения проблемы для каждого клиента, в то время как нам нужно решить проблему только для одного клиента — для себя.
Ошибка, которую мы допустили в первые месяцы работы и которая сегодня приносит свои плоды, заключалась в том, что мы недостаточно тщательно разграничивали границы транзакций в базе данных. В кодовой базе Wave сессия базы данных SQLAlchemy является глобальной переменной запроса; она неявно начинает новую транзакцию базы данных каждый раз, когда происходит обращение к атрибуту объекта БД, и любая функция в кодовой базе Wave может вызвать commit для сессии, что приведет к фиксации всех ожидающих обновлений. Это затрудняет контроль за временем обновления базы данных, что увеличивает вероятность возникновения мелких ошибок в целостности данных, а также затрудняет использование базы данных для создания таких вещей, как ключи идемпотентности или транзакционно-стадийный слив задач. Это также повышает риск случайного удержания открытых транзакций в базе данных, что может затруднить миграцию схемы в оперативном режиме.
Некоторые варианты, в которых мы не уверены (в том смысле, что это вещи, которые мы либо думаем изменить, либо рекомендовали бы другим командам, начинающим с нуля, рассмотреть другой подход):
- использование RabbitMQ (для наших целей Redis, вероятно, будет работать также хорошо как очередь задач, и в целом использование Redis снизит операционную нагрузку)
- использование Celery (который слишком сложен для нашего случая использования и дал несколько отказов, например, из-за проблем с обратной совместимостью при обновлении версий)
- использование SQLAlchemy (затрудняет понимание разработчиками того, какие запросы к базе данных будет выполнять их код, что приводит к различным трудноотлаживаемым ситуациям и лишним операционным страданиям, особенно в связи с вышеуказанным вопросом о границах транзакций базы данных)
- использование Python (который был правильным первоначальным выбором из-за технического опыта нашего технического директора, но его поддержка параллелизма, производительность и большая динамичность заставляют нас сомневаться в том, что это правильный выбор для крупной кодовой базы бэкенда)
Ни один из этих примеров не является серьезной ошибкой, а для некоторых (например, Python) недостатки настолько минимальны, что нам дешевле продолжать нести повышенную нагрузку по сопровождению, чем инвестировать в переход на что-то теоретически лучшее. Но если бы мы сегодня начинали аналогичную кодовую базу с нуля, мы бы крепко задумались о том, был ли это правильный выбор.
Некоторые области, в которых мы довольны своим выбором, даже несмотря на то, что он может показаться не самым простым решением, — это API, где мы используем GraphQL, транспортные протоколы, где мы некоторое время использовали собственный протокол, и управление хостами, где мы используем Kubernetes. Для транспортных протоколов мы использовали собственный протокол, работающий на базе UDP, с резервным копированием SMS и USSD, по причинам, описанным в этом докладе. С внедрением HTTP/3 мы смогли заменить наш пользовательский протокол на HTTP/3, а USSD нам обычно требуется только для таких событий, как недавнее отключение Интернета в Мали.
Что касается использования GraphQL, то мы считаем, что плюсы для нас перевешивают минусы.
Плюсы:
- Самодокументирование возвращаемого типа
- Генерация кода с точным возвращаемым типом приводит к созданию более безопасных клиентов
- Интерактивный проводник GraphiQL — это выигрыш в производительности
- Наши различные приложения (пользовательское приложение, приложение поддержки, приложение Wave-агента и т.д.) могут в основном использовать один API, что снижает сложность.
- Композитный язык запросов позволяет клиентам получать именно те данные, которые им нужны, за один пакетный обход без необходимости создания большого количества специализированных конечных точек
- Устранение разногласий по поводу того, что считать RESTful API.
Минусы:
- Библиотеки GraphQL были не очень хороши на момент принятия GraphQL (базовая библиотека Python была портом Javascript-библиотеки, так что Pythonic и Graphene требовала много шаблонного кода, а Apollo-Android производила очень плохо оптимизированный код)
- Кодировка GQL по умолчанию избыточна, и мы очень заботимся об ограничении размера, поскольку у многих наших клиентов низкая пропускная способность каналов связи
Что касается Kubernetes, то мы используем Kubernetes, потому что знали, что если бизнес будет успешным (а так оно и было) и мы продолжим расширяться, то в конечном итоге мы распространимся на страны, которые требуют, чтобы наши сервисы работали в стране. Точные правила зависят от страны, но мы уже выходим на один крупный африканский рынок, который требует, чтобы наш «основной ЦОД» находился в стране, а также на другие, где есть правила, которые, например, требуют, чтобы мы имели возможность аварийного переключения на ЦОД в стране.
Неизбежные сложности для нас связаны с телекоммуникационными интеграциями. Теоретически мы могли бы использовать SaaS SMS-провайдера для всего, но основной SaaS SMS-провайдер работает не везде в Африке, и стоимость его использования везде была бы непомерно высокой. Предыдущее замечание о том, что расходы на оплату труда инженеров доминируют над стоимостью наших систем, не было бы верным, если бы мы использовали SaaS SMS-провайдера для всех наших потребностей в SMS; команда, обеспечивающая телекоммуникационные интеграции, многократно окупает себя.
Максимально упростив архитектуру приложений, мы можем тратить бюджет на сложность (и численность персонала) там, где сложность выгодна нашему бизнесу. Идея делать все как можно проще, если нет веских причин для усложнения, позволила нам построить довольно крупный бизнес с небольшим количеством инженеров, несмотря на то, что мы занимаемся африканским финансовым бизнесом, который, по общему мнению, является сложным бизнесом, о чем мы расскажем в одной из следующих статей (один из наших первых и наиболее полезных консультантов, который дал нам советы, сыгравшие решающую роль в успехе Wave, сначала сказал, что Wave — плохая бизнес-идея и основателям следует выбрать другую, поскольку он предвидел очень много потенциальных трудностей).
-
Интегрированные среды разработки2 недели назад
Лучшая работа с Android Studio: 5 советов
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2024.43
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2024.44
-
Исследования2 недели назад
Поможет ли новая архитектура React Native отобрать лидерство у Flutter в кроссплатформенной разработке?