Дорога, вымощенная благими намерениями
187
мое для отправки сообщения серверу и получения результата. Время
зависит от количества пересылок и количества сообщений, но почти не
зависит от размера сообщений. Кроме того, размер заголовка пакета
фиксирован;
следовательно, для очень коротких сообщений полезная
нагрузка использует только небольшую долю пропускной способности;
распределенные
серверы
могут улучшить пропускную способность, но
не время отклика, поскольку приложение отправляет запросы после-
довательно.
Антипаттерн «слишком много слишком мелких запросов» наблюдается
уже несколько десятилетий. Примерно 20 лет назад одному из авторов при-
шлось проанализировать приложение, которому требовалось 5–7 минут для
создания HTML-формы, содержащей около 100 полей. Код приложения был
идеально
структурирован, небольшие методы сопровождались коммента-
риями и были отлично отформатированы. Однако трассировка базы дан-
ных показала, что для создания этой формы
приложение выдавало около
16 000 запросов – больше, чем символов в самой форме. Дальнейший анализ
показал, что несколько тысяч запросов исходили от метода
GetObjectIdByName
.
За каждым из этих вызовов следовал запрос от метода
GetNameByObjectId
, ко-
торый вызывался из другой части приложения, вероятно написанной другим
разработчиком. Значения
name
были уникальными; следовательно, второй
вызов всегда возвращал параметр первого. Один запрос, извлекающий все
данные, необходимые для построения формы, вернул результат менее чем
за 200 миллисекунд.
Несмотря на эти известные недостатки, многие компании упорно продол-
жают применять одни и те же средства снова и снова, каждый раз с одним
и тем же результатом. Даже если изначально им удается добиться некоторого
улучшения, это длится недолго. В течение ряда лет мы наблюдали за усилия-
ми по оптимизации в одной компании.
Поскольку оптимизатор PostgreSQL пользуется преимуществами доступ-
ной оперативной памяти, эта компания увеличивала аппаратные ресурсы,
чтобы вся (или почти вся) база данных помещалась в память. Мы наблюда-
ли за миграцией с машин с 512 ГБ ОЗУ на машины с 1 ГБ, 2 ГБ, а затем 4 ГБ
оперативной памяти, как только соответствующая конфигурация появлялась
в наличии. Каждый раз после короткого периода относительного удовлетво-
рения возникала та же проблема: база данных увеличивалась и выходила за
пределы оперативной памяти.
Еще одно средство, которое часто применяется, – использование храни-
лища «ключ–значение» вместо полноценной базы данных. Аргументируется
это тем, что «в приложении не используется ничего, кроме доступа по пер-
вичному ключу, поэтому механизм запросов нам не нужен». Действительно,
такой подход может улучшить время отклика для любого отдельного доступа
к данным, но не сократит время, необходимое для выполнения бизнес-функ-
ции. В одном из крайних случаев,
который мы наблюдали, поиск записи
по первичному ключу в среднем занимал около 10 миллисекунд. При этом
количество обращений к базе данных, выполняемых за одно действие конт-
роллера приложения, составило почти тысячу, с предсказуемым влиянием
на общую производительность.