Странная проблема производительности с UPDATE в ArangoDB

Я создаю приложение Node.js, которое работает с ArangoDB в качестве хранилища данных. По сути, в качестве структуры данных у меня есть две таблицы, одна для управления так называемым instances, другая для entities. Я делаю следующее:

  • В коллекции instances есть документ на каждый instance, который у меня есть.
  • Всякий раз, когда я добавляю объект в коллекцию entities, я также хочу отслеживать объекты, принадлежащие конкретному экземпляру.
  • Таким образом, в каждом документе instance есть поле массива для entities, и я помещаю идентификатор объекта в этот массив.

Следующий код показывает общую схему:

// Connect to ArangoDB.
db = new Database(...);
db.useBasicAuth(user, password);

// Use the database.
await db.createDatabase(database);
db.useDatabase(database);

// Create the instance collection.
instanceCollection = db.collection(`instances-${uuid()}`);
await instanceCollection.create();

// Create the entities collection.
entityCollection = db.collection(`entities-${uuid()}`);
await entityCollection.create();

// Setup an instance.
instance = {
  id: uuid(),
  entities: []
};

// Create the instance in the database.
await db.query(aql`
  INSERT ${instance} INTO ${instanceCollection}
`);

// Add lots of entities.
for (let i = 0; i < scale; i++) {
  // Setup an entity.
  const entity = {
    id: uuid()
  };

  // Update the instance.
  instance.entities.push(entity);

  // Insert the entity in the database.
  await db.query(aql`
    INSERT ${entity} INTO ${entityCollection}
  `);

  // Update the instance in the database.
  await db.query(aql`
    FOR i IN ${instanceCollection}
      FILTER i.id == ${instance.id}
      UPDATE i WITH ${instance} IN ${instanceCollection} OPTIONS { mergeObjects: false }
  `);
}

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

Running benchmark 'add and update'
  100 Entities:   348.80ms [+0.00%]
 1000 Entities:  3113.55ms [-10.74%]
10000 Entities: 90180.18ms [+158.54%]

Добавление индекса имеет эффект, но ничего не меняет в общей проблеме:

Running benchmark 'add and update with index'
  100 Entities:   194.30ms [+0.00%]
 1000 Entities:  2090.15ms [+7.57%]
10000 Entities: 89673.52ms [+361.52%]

Проблема может быть отслежена до оператора UPDATE. Если вы опустите его и будете использовать только оператор базы данных INSERT, масштабирование будет линейным. Значит, что-то не так с самим обновлением. Однако я не понимаю, где проблема.

Вот что я хотел бы понять: почему оператор UPDATE со временем становится значительно медленнее? Я использую это неправильно? Это известная проблема в ArangoDB? …?

Что меня не интересует, так это обсуждение этого подхода: Пожалуйста, принимайте то, что дано. Давайте сосредоточимся на производительности оператора UPDATE. Любые идеи?

ОБНОВИТЬ

Как просили в комментариях, вот некоторая информация о настройке системы:

  • ArangoDB 3.4.6, 3.6.2.1 и 3.7.0-alpha.2 (все работают в Docker, на macOS и Linux)
  • Настройка одного сервера
  • ArangoJS 6.14.0 (у нас также было это с более ранними версиями, хотя я не могу сказать точную версию)

person Golo Roden    schedule 25.03.2020    source источник
comment
Голо, можешь дать больше информации о своей среде? Какую версию ArangoDB вы используете? Это кластер или один сервер? Какую версию arangojs вы используете? С этой информацией я могу уточнить у инженеров. Мы проводим длительные грубые тесты с Java-клиентами, которые не демонстрируют такого поведения. Поэтому было бы хорошо получить как можно больше информации о вашей среде.   -  person fceller    schedule 25.03.2020
comment
@fceller Спасибо за ответ ????. Я добавил информацию, которую вы просили в вопросе.   -  person Golo Roden    schedule 25.03.2020
comment
Что вы имеете в виду под use the database's ADD statement? Насколько мне известно, нет операции или функции ADD. Вы имеете в виду что-то вроде этого? LET scale = 10000 RETURN (FOR i IN 1..scale RETURN { id: i })   -  person CodeManX    schedule 25.03.2020
comment
Извините, это опечатка, должно было быть INSERT вместо ADD. Я починил это.   -  person Golo Roden    schedule 25.03.2020


Ответы (1)


Поиск проблемы

Пробовали ли вы объяснение или профилирование запрос?

Описания плана объяснения Аранго превосходны. Вы можете получить доступ к explain с помощью встроенного веб-интерфейса администратора Aardvark или с помощью db._explain(query). Вот как выглядит ваш:

Execution plan:
 Id   NodeType                  Est.   Comment
  1   SingletonNode                1   * ROOT
  5   CalculationNode              1     - LET #5 = { "_key" : "123", "_id" : "collection/123", "_rev" : "_aQcjewq---", ...instance }   /* json expression */   /* const assignment */
  2   EnumerateCollectionNode      2     - FOR i IN collection   /* full collection scan, projections: `_key`, `id` */   FILTER (i.`id` == "1")   /* early pruning */
  6   UpdateNode                   0       - UPDATE i WITH #5 IN pickups 

Indexes used:
 By   Name      Type      Collection   Unique   Sparse   Selectivity   Fields       Ranges
  6   primary   primary   pickups      true     false       100.00 %   [ `_key` ]   i

Эта проблема

Ключевой частью плана является - FOR i IN collection /*full collection scan Полное сканирование коллекции будет ...медленным. Он должен расти линейно с размером вашей коллекции. Итак, с вашим for циклом из scale итераций это определенно означает экспоненциальный рост с размером коллекции.

Решение

Индексация id должна помочь, но я думаю, это зависит от того, как вы создали индекс.

Использование _key вместо индекса изменяет план показа primary

- FOR i IN pickups   /* primary index scan, index only, projections: `_key` */    

Это должно быть постоянное время, поэтому с вашим циклом for из scale итераций это должно означать линейное время.

person Codebling    schedule 29.03.2020
comment
Есть ли способ использовать функцию объяснения также в транзакциях? - person Golo Roden; 30.03.2020
comment
Я не думаю, что план, созданный 'объясняющими' факторами в каких-либо блокировках или дополнительных накладных расходах от транзакций. Он показывает стратегию, которую БД будет использовать для обработки запроса. При этом транзакции не повлияют на эту стратегию. - person Codebling; 30.03.2020