#javascript #gremlin #amazon-neptune #gremlinjs #aws-neptune
Вопрос:
Я пытаюсь проверить и вставить 1000 вершин в кусок с помощью promise.all()
. Код выглядит следующим образом:
public async createManyByKey(label: string, key: string, properties: object[]): Promiselt;T[]gt; { const promises = []; const allVertices = __.addV(label); const propKeys: Arraylt;stringgt; = Object.keys(properties[0]); for(const propKey of propKeys){ allVertices.property(propKey, __.select(propKey)); } const chunkedProperties = chunk(properties, 5); // [["demo-1", "demo-2", "demo-3", "demo-4", "demo-5"], [...], ...] for(const property of chunkedProperties){ const singleQuery = this.g.withSideEffect('User', property) .inject(property) .unfold().as('data') .coalesce(__.V().hasLabel(label).where(eq('data')).by(key).by(__.select(key)), allVertices).iterate(); promises.push(singleQuery); } const result = await Promise.all(promises); return result; }
Этот код вызывает исключение ConcurrentModificationException. Нужна помощь, чтобы исправить/улучшить эту проблему.
Ответ №1:
Я не совсем уверен в данных и параметрах, которые вы используете, но мне нужно было немного изменить ваш запрос, чтобы он работал с набором данных, который у меня есть под рукой (воздушные маршруты), как показано ниже. Я сделал это, чтобы помочь мне разобраться в том, что делает ваш запрос. Мне пришлось изменить второй by
шаг. Я не уверен, как это работало в противном случае.
gremlingt; g.inject(['AUS','ATL','XXX']).unfold().as('d'). ......1gt; coalesce(__.V().hasLabel('airport').limit(10). ......2gt; where(eq('d')). ......3gt; by('code'). ......4gt; by(), ......5gt; constant('X')) ==gt;v['3'] ==gt;v['1'] ==gt;X
Хотя подобный запрос прекрасно работает изолированно, как только вы начнете выполнять несколько асинхронных обещаний (которые содержат изменяющиеся шаги, как в вашем запросе), может случиться так, что одно обещание попытается получить доступ к части графика, заблокированной другим. Даже несмотря на то, что я считаю, что выполнение более «параллельное», чем по-настоящему «параллельное», если одно обещание выполняется из-за ожидания ввода-вывода, позволяющего выполнить другое, следующее может завершиться неудачно, если в предыдущем обещании уже есть блокировки в базе данных, в которых также нуждается следующее обещание. В вашем случае, поскольку у вас есть coalesce
это ссылается на все вершины с заданной меткой и свойствами, что потенциально может привести к возникновению конфликтующих блокировок. Возможно, это будет работать лучше, если вы await
будете работать после каждой for
итерации цикла, а не делать все это в конце в одном большом Promise.all
.
Еще следует иметь в виду, что этот запрос все равно будет несколько дорогостоящим, так как промежуточный обход V
будет выполняться пять раз (в случае вашего примера) для каждой for
итерации цикла. Это связано с тем, что unfold
введенные данные берутся из фрагментов размером 5 и, следовательно, порождают пять обходчиков, каждый из которых начинается с просмотра V
.
ОТРЕДАКТИРОВАНО 2021-11-17
Как немного обсуждалось в комментариях, я подозреваю, что наиболее оптимальный путь на самом деле заключается в использовании нескольких запросов. Первый запрос просто выполняет g.V(id1,id2,...)
проверку всех идентификаторов, которые вы потенциально собираетесь добавить. Пусть он вернет список найденных идентификаторов. Удалите их из набора для добавления. Затем разбейте добавляемую часть на пакеты и сделайте это без coalesce
, так как теперь вы знаете, что этих элементов не существует. Это, скорее всего, лучший способ уменьшить блокировку и избежать CME (исключений). Если только кто-то еще не пытается добавить их параллельно, я думаю, что я бы выбрал именно такой подход.
Комментарии:
1. если
promise.all()
не будет работать так, как ожидалось, то как я могу улучшить производительность. Я попытался вставить 1000 вершин в один запрос, это заняло более 2 минут. Производительность была примерно следующей:- — 100 вершин — 1,2 секунды — 300 вершин — 20 секунд — 500 вершин — 45 секунд — 900 вершин — 1 минута 30 секунд Есть ли способ улучшить эту производительность2. Прежде чем изучать варианты, смогли ли вы убедиться, что выполнение
await
после каждой итерации цикла for удаляет исключения?3. Я попробовал, чтобы он выполнялся синхронно/последовательно. Поэтому он ждет, пока каждый цикл не будет завершен, и это происходит еще медленнее, когда речь заходит о больших числах.
4. Обычно лучший способ добиться хорошей производительности записи — это многопоточный подход, при котором каждый запрос добавляет от 50 до 100 вершин за раз, что не отличается от того, что вы пытаетесь сделать. Что усложняет ситуацию в вашем случае, так это то, что ваш запрос, скорее всего, блокирует большие части графика из-за
coalesce
шага и, в частностиhasLabel(label)
, и поэтому полностью асинхронный/многопоточный подход будет иметь проблемы с исключениями. Средний обходV
, как вы увидите, если профилируете запрос, также вызывает большое количество посещенных вершин.5. В этом конкретном случае, возможно, лучше всего выполнить 2 запроса, в которых первый находит все отсутствующие вершины, а второй добавляет их без необходимости
coalesce
.