Проблема списка покупок
Стефан Фаро
1
описывает ситуацию, называемую «проблемой списка по-
купок».
Предположим, у вас есть список покупок для продуктового магазина. В ре-
альной жизни вы садитесь в машину, едете в магазин, покупаете все продук-
ты из своего списка, кладете их в багажник, едете домой, заносите их в дом
и кладете в холодильник. А теперь представьте, что вместо этого вы едете
в магазин, заходите и берете только первый продукт из своего списка, едете
домой, кладете его в холодильник и снова отправляетесь в магазин! И про-
должаете повторять ту же самую последовательность действий для каждого
пункта из вашего списка.
Звучит нелепо? Да, но именно этим и занимаются многие приложения,
взаимодействуя с базами данных.
А теперь представьте, что для повышения скорости совершения покупок
эксперты предложили бы увеличить ширину проходов в магазине, постро-
ить более качественные дороги или оснастить автомобиль более мощным
двигателем.
Некоторые из этих предложений действительно могут улучшить ситуацию.
Но даже если бы можно было сократить время покупок на 30 %, это улучше-
ние нельзя сравнивать с успехом, которого можно достичь с помощью одного
простого улучшения процесса: купить все продукты за одну поездку.
Как перенести проблему со списком покупок на поведение приложения?
Большинство проблем с производительностью вызваны
слишком
большим
количеством
слишком
мелких
запросов
. И так же, как более качественные
дороги не помогут, если за каждым продуктом ездить отдельно, так и следую-
щие популярные предложения не улучшат производительность приложения:
более
мощные
компьютеры
практически не помогают, так как и прило-
жение, и база данных находятся в состоянии ожидания 99 % времени;
более
высокая
пропускная
способность
сети
тоже не помогает. Сети с вы-
сокой пропускной способностью эффективны для передачи больших
объемов данных, но не могут значительно сократить время, необходи-
1
Stephane Faroult and Peter Robson,
The Art of SQL
.
Дорога, вымощенная благими намерениями
187
мое для отправки сообщения серверу и получения результата. Время
зависит от количества пересылок и количества сообщений, но почти не
зависит от размера сообщений. Кроме того, размер заголовка пакета
фиксирован; следовательно, для очень коротких сообщений полезная
нагрузка использует только небольшую долю пропускной способности;
распределенные
серверы
могут улучшить пропускную способность, но
не время отклика, поскольку приложение отправляет запросы после-
довательно.
Антипаттерн «слишком много слишком мелких запросов» наблюдается
уже несколько десятилетий. Примерно 20 лет назад одному из авторов при-
шлось проанализировать приложение, которому требовалось 5–7 минут для
создания HTML-формы, содержащей около 100 полей. Код приложения был
идеально структурирован, небольшие методы сопровождались коммента-
риями и были отлично отформатированы. Однако трассировка базы дан-
ных показала, что для создания этой формы приложение выдавало около
16 000 запросов – больше, чем символов в самой форме. Дальнейший анализ
показал, что несколько тысяч запросов исходили от метода
GetObjectIdByName
.
За каждым из этих вызовов следовал запрос от метода
GetNameByObjectId
, ко-
торый вызывался из другой части приложения, вероятно написанной другим
разработчиком. Значения
name
были уникальными; следовательно, второй
вызов всегда возвращал параметр первого. Один запрос, извлекающий все
данные, необходимые для построения формы, вернул результат менее чем
за 200 миллисекунд.
Несмотря на эти известные недостатки, многие компании упорно продол-
жают применять одни и те же средства снова и снова, каждый раз с одним
и тем же результатом. Даже если изначально им удается добиться некоторого
улучшения, это длится недолго. В течение ряда лет мы наблюдали за усилия-
ми по оптимизации в одной компании.
Поскольку оптимизатор PostgreSQL пользуется преимуществами доступ-
ной оперативной памяти, эта компания увеличивала аппаратные ресурсы,
чтобы вся (или почти вся) база данных помещалась в память. Мы наблюда-
ли за миграцией с машин с 512 ГБ ОЗУ на машины с 1 ГБ, 2 ГБ, а затем 4 ГБ
оперативной памяти, как только соответствующая конфигурация появлялась
в наличии. Каждый раз после короткого периода относительного удовлетво-
рения возникала та же проблема: база данных увеличивалась и выходила за
пределы оперативной памяти.
Еще одно средство, которое часто применяется, – использование храни-
лища «ключ–значение» вместо полноценной базы данных. Аргументируется
это тем, что «в приложении не используется ничего, кроме доступа по пер-
вичному ключу, поэтому механизм запросов нам не нужен». Действительно,
такой подход может улучшить время отклика для любого отдельного доступа
к данным, но не сократит время, необходимое для выполнения бизнес-функ-
ции. В одном из крайних случаев, который мы наблюдали, поиск записи
по первичному ключу в среднем занимал около 10 миллисекунд. При этом
количество обращений к базе данных, выполняемых за одно действие конт-
роллера приложения, составило почти тысячу, с предсказуемым влиянием
на общую производительность.
Do'stlaringiz bilan baham: |