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

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

Обсуждая потребность в такого рода приложениях, я всегда в первую очередь обращаю внимание на следующее:

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

«Мы не сможем использовать традиционную базу данных или даже NoSQL. Он должен быть в памяти ».

«Если мы не сосредоточимся на производительности, наше приложение никогда не будет соответствовать нашему SLA».

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

Однако я умоляю вас начать с простого.

Все элементы, которые я только что перечислил выше, могут быстро привести вас в место, где вы изо всех сил пытаетесь угадать шаблоны доступа и угадать узкие места в производительности. Вы потратите много времени на то, чтобы научиться решать свои предполагаемые проблемы, и, скорее всего, придумаете что-то сложное, чтобы улучшить свою работу. Затем, когда придет время воплощать в жизнь, вы должны все время держать свой дизайн в голове. Добавьте к этому еще 3–4 разработчика (типичный размер команды), и вдруг всем вам придется напоминать друг другу и регулярно «попадать на одну страницу», потому что ваш дизайн слишком сложен.

Сложность убивает

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

Почему? Потому что сложность часто снижает удобочитаемость и ремонтопригодность. Тесты должны стать более сложными. Устранение неполадок становится трудным и утомительным.

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

Начать с простого

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

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

Почему?

Потому что, когда программное обеспечение доступно для чтения, сопровождения и тестирования, оно также изменяемое. Его легко изменить, если у вас появятся дополнительные требования или возникнут дополнительные оптимизации, которые вы могли бы сделать для повышения производительности. Каждое изменение может (обычно) быть небольшим и лаконичным, а не 1000 строк кода, которые на самом деле просто делают вызов базы данных, завернутый в будущее, или что-то, что также пытается реализовать кеширование, которое также пытается оптимизировать каждую итерацию цикла из 5 элементы…. вы поняли суть.

Измерение, измерение, измерение

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

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

В качестве примера рассмотрим, что у вас есть процесс A, который занимает в среднем ~ 1 мс, и другой процесс, B, который занимает в среднем ~ 4 мс. Оба должны быть выполнены для вашей службы, прежде чем она сможет вернуть ответ, и одно должно произойти раньше другого (вам нужны данные из A для B).

Вы можете легко увидеть, что в то время как вы можете сэкономить 70% времени, сосредоточив внимание на A и сэкономив 0,7 мс, или вы можете ускорить другое узкое место на 50% и сэкономить 2 мс. Вы также можете оценить время разработки, необходимое для каждого из них, и обнаружить, что вы можете потратить в 2 раза больше времени на B и сэкономить, возможно, даже 75% или 3 мс.

Это большая разница в общем времени обработки, если вы сосредоточитесь только на B!

Время историй

В Bandwidth (мы, кстати, нанимаем!) Мы работали над сервисом, подобным тем, которые я описал выше. У него было агрессивное соглашение об уровне обслуживания, и ему, как и большинству сервисов, требовалось выполнять поиск некоторых данных. Естественно, разговор перешел на Как мы можем хранить данные с максимальной эффективностью? Стоит ли использовать кеширование? А как насчет ‹вставить сюда модное хранилище данных в памяти›? »

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

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

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

В качестве краткого контекста позвольте мне немного подробнее описать нашу структуру данных. Наши запросы к базе данных вернули строку JSON (ничего особенного) по уникальному идентификатору. Этот JSON достаточно сложен, поэтому мы решили написать для него собственный десериализатор. Отметим, что наша команда не владела этими данными, поскольку мы получали их из API партнера по интеграции.

А наш десериализатор принимал НАВСЕГДА!

Для десериализации объекта требовалось несколько миллисекунд, что было слишком долго. Мы увидели несколько способов довольно легко его оптимизировать. Мы попробовали и сэкономили примерно 60% времени. Мы также поняли, что общее количество десериализуемых объектов было достаточно небольшим (~ 300 000 элементов). Это достаточно мало, чтобы мы пришли к выводу, что, скорее всего, мы могли бы кэшировать весь набор, используя хранилище данных в памяти. Мы еще не реализовали это позже, но полагаем, что сможем сделать это с помощью целенаправленных изменений.

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

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

Не пытайтесь быть «слишком умным» и гадать о том, чего вы не знаете. Пишите код таким образом, чтобы он был простым и понятным, чтобы его можно было легко изменить, когда вы найдете место для оптимизации. Оцените свое приложение, чтобы выявить узкие места и узнать, какие из них стоит улучшить. Ваше будущее будет вам благодарно.

Удачного кодирования!