Замедляют ли запросы Objection.js WHERE IN мое приложение Node.js?

Итак, это в основном ответ да/нет. У меня есть приложение узла, работающее на Heroku с планом postgres. Я использую Objection.js в качестве ORM. На большинстве конечных точек время отклика превышает 300 мс, и у меня есть теория, почему это происходит. Я хотел бы, чтобы это подтвердилось.

Большинство моих конечных точек API выполняют около 5-10 нетерпеливых загрузок. Objection.js обрабатывает нетерпеливую загрузку, выполняя дополнительные запросы WHERE IN, а не выполняя один большой запрос с большим количеством JOINS. Причина этого в том, что таким образом было проще строить, и это не должно сильно снижать производительность.

Но это заставило меня задуматься: Heroku Postgres не работает на том же dyno heroku, что и приложение узла, которое я предполагаю, так что это означает задержку для каждого запроса. Может ли быть так, что все эти задержки складываются, вызывая общую задержку в 300 мс?

Подводя итоги: не будет ли быстрее создавать собственные запросы с помощью Knex, а не с помощью Objection.js, если у вас есть отдельно размещенная база данных?


person Gersom    schedule 10.08.2018    source источник
comment
Вы можете попробовать включить slow_log_query в postgres, чтобы увидеть, медленный ли запрос или это просто из-за задержки в сети.   -  person Dat Tran    schedule 10.08.2018
comment
В Heroku есть специальная панель инструментов для запросов, отнимающих много времени. Я смотрю на это сейчас, и ни один запрос не занимает больше 1 мс   -  person Gersom    schedule 10.08.2018
comment
У меня нет большого опыта работы с heroku-postgres. Но это абсолютно верно, если между вашим приложением и вашей БД есть сетевая задержка, 1 большой запрос должен быть большой оптимизацией по сравнению с 1 основным запросом и 5-10 подзапросами для возврата данных. Кроме того, из-за сетевой задержки, если вы сможете уменьшить размер полезной нагрузки, отправляемой по сети (выберите, какой столбец запрашивать), это также будет значительной оптимизацией.   -  person Dat Tran    schedule 10.08.2018


Ответы (3)


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

Из базы данных вы должны иметь возможность видеть время запросов, чтобы определить, есть ли какие-либо медленные запросы, вызывающие проблемы.

Также knex выводит некоторую информацию о производительности на консоль, когда вы запускаете его с установленной переменной окружения DEBUG=knex:*.

В настоящее время node также имеет встроенную поддержку профилирования, которую вы можете включить, установив флаг --inspect при запуске node. Затем вы сможете связать свой процесс узла с инструментами разработчика Chrome и посмотреть, где узел использует свое время. Из этого профиля вы сможете увидеть, например, если синтаксический анализ результатов запроса к базе данных доминирует над временем выполнения.

Лучший способ выяснить медлительность — изолировать медленную часть приложения и тщательно изучить ее или даже опубликовать этот пример в stackoverflow, чтобы другие люди могли рассказать вам, почему оно может быть медленным. Общее объяснение, подобное этому, не дает другим достаточно инструментов, чтобы помочь решить реальную проблему.

Objection.js обрабатывает нетерпеливую загрузку, выполняя дополнительные запросы WHERE IN, а не выполняя один большой запрос с большим количеством JOINS. Причина этого в том, что таким образом было проще строить, и это не должно сильно снижать производительность.

С возражением вы можете выбрать, какой энергичный алгоритм вы хотите использовать. В большинстве случаев (при наличии отношений «один ко многим» или «многие ко многим») выполнение нескольких запросов на самом деле более эффективно по сравнению с использованием объединения, потому что при объединении объем данных увеличивается, а время передачи + анализ результатов на стороне узла. займет слишком много времени.

не будет ли быстрее создавать собственные запросы с помощью Knex, а не генерировать их с помощью Objection.js, если у вас есть отдельно размещенная база данных?

Обычно нет.

Спекуляционная часть:

  1. Вы упомянули, что Most of my api endpoints do around 5-10 eager loads.. В большинстве случаев, когда я сталкивался с такой медлительностью в запросах, причина заключалась в том, что приложение запрашивает слишком большие фрагменты данных из базы данных. Когда запрос возвращает, например, десятки тысяч строк, это будет несколько мегабайт данных JSON. Только анализ этого количества данных из базы данных в объекты JavaScript занимает сотни миллисекунд. Если ваши запросы также вызывают высокую нагрузку на ЦП в течение этих 300 мс, это может быть вашей проблемой.

  2. Медленные запросы. База данных иногда не имеет правильно установленных индексов, поэтому запросам просто нужно будет линейно сканировать все таблицы, чтобы получить результаты. Проверка медленных запросов из журналов БД поможет найти их. Также, если получение ответа занимает много времени, но нагрузка ЦП на процесс узла низкая, это может иметь место.

person Mikael Lepistö    schedule 10.08.2018
comment
Спасибо за развернутый ответ!! Но я уже много оптимизировал запросы, и даже мой полнотекстовый поиск занимает менее 1 миллисекунды. Выборка данных разбита на страницы, поэтому количество строк ограничено менее чем 100. Так что я думаю, что это исключает оба ваших предположения;) - person Gersom; 10.08.2018
comment
@gersom хорошо, тогда кажется, что вам нужно изолировать эту медленную часть запроса и профилировать код, чтобы выяснить, в чем настоящая проблема. Кстати. если вы извлекаете 100 строк, где вы стремитесь 5-10 раз по 100 строк, всего будет уже легко 50000 строк. Удачи в поиске первопричины :) - person Mikael Lepistö; 10.08.2018
comment
Сейчас я пытаюсь написать один чистый запрос Knex, который достигает эквивалента, но с подзапросами и json_agg. Я уже добавил 1 соединение «многие-один» и 1 соединение «многие-многие», и время отклика API сократилось до 39 мс :) - person Gersom; 10.08.2018

Настоящим я могу подтвердить, что каждый запрос занимает ок. 30 мс, независимо от сложности запроса. Objection.js действительно выполняет около 10 отдельных запросов из-за нетерпеливой загрузки, что объясняет суммарное время в 300 мс.


Просто к вашему сведению; Я иду по этой дороге сейчас ⬇

Я начал углубляться в написание своих собственных более сложных SQL-запросов. Кажется, что вы можете делать довольно продвинутые вещи, достигая результатов, аналогичных нетерпеливой загрузке Objection.js.

select 
  "product".*,
  json_agg(distinct brand) as brand,
  case when count(shop) = 0 then '[]' else json_agg(distinct shop) end as shops,
  case when count(category) = 0 then '[]' else json_agg(distinct category) end as categories,
  case when count(barcode) = 0 then '[]' else json_agg(distinct barcode.code) end as barcodes
from "product"
inner join "brand" on "product"."brand_id" = "brand"."id"
left join "product_shop" on "product"."id" = "product_shop"."product_id"
left join "shop" on "product_shop"."shop_code" = "shop"."code"
left join "product_category" on "product"."id" = "product_category"."product_id"
left join "category" on "product_category"."category_id" = "category"."id"
left join "barcode" on "product"."id" = "barcode"."product_id"
group by "product"."id"

Это занимает 19 мс для 1000 продуктов, но обычно ограничение составляет 25 продуктов, что очень эффективно.

person Gersom    schedule 10.08.2018
comment
кстати. Вы пробовали нетерпеливый алгоритм возражения, который использует объединение вместо нескольких запросов? - person Mikael Lepistö; 24.08.2018
comment
@MikaelLepistö Да, но тогда невозможно иметь нумерацию страниц (ограничение/смещение) - person Gersom; 05.03.2020

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

Вы можете использовать алгоритм на основе соединения, просто вызвав метод joinEager вместо метода eager. joinEager выполняет один единственный запрос.

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

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

person Sami Koskimäki    schedule 18.11.2018
comment
Спасибо за внимание! joinEager не был вариантом из-за неработающей нумерации страниц. Проблема действительно заключалась в последовательных запросах, поэтому, если большинство запросов теперь будут выполняться параллельно, я думаю, что смогу выделить свой собственный необработанный запрос :) Вы знаете, с какой версии и на что это изменилось? - person Gersom; 19.11.2018