ArangoDB: Вставить как функцию запроса на примере

Часть моего графика построена с использованием гигантского соединения между двумя большими коллекциями, и я запускаю его каждый раз, когда добавляю документы в любую коллекцию. Запрос основан на более раннем сообщении.

FOR fromItem IN fromCollection
    FOR toItem IN toCollection
        FILTER fromItem.fromAttributeValue == toItem.toAttributeValue
        INSERT { _from: fromItem._id, _to: toItem._id, otherAttributes: {}} INTO edgeCollection

Это займет около 55 000 секунд для моего набора данных. Я был бы абсолютно рад предложениям сделать это быстрее.

Но у меня есть две связанные проблемы:

  1. Мне нужно подтверждение. Обычно upsert было бы хорошо, но в этом случае, поскольку у меня нет возможности узнать ключ заранее, это мне не поможет. Чтобы получить ключ заранее, мне нужно было бы запросить на примере, чтобы найти ключ идентичного в остальном существующего края. Это кажется разумным, если это не убивает мою производительность, но я не знаю, как в AQL построить мой запрос условно, чтобы он вставлял ребро, если эквивалентное ребро еще не существует, но ничего не делает, если эквивалентное ребро существует. Как я могу это сделать?
  2. Мне нужно запускать это каждый раз, когда данные добавляются в любую коллекцию. Мне нужен способ запустить это только с новейшими данными, чтобы он не пытался присоединиться ко всей коллекции. Как я могу написать AQL, который позволяет мне присоединяться только к вновь вставленным записям? Они добавляются с помощью Arangoimp, и у меня нет гарантий, в каком порядке они будут обновляться, поэтому я не могу создавать ребра одновременно с созданием узлов. Как я могу присоединиться только к новым данным? Я не хочу тратить 55 тысяч секунд каждый раз, когда добавляется запись.

person Nate Gardner    schedule 18.10.2016    source источник
comment
Я делал запросы в других базах данных с той же проблемой, как уменьшить размер набора данных при его повторном связывании. Решение, которое сработало для меня, - добавить поле с названием что-то вроде linked = false в коллекции fromCollection и toCollection.   -  person David Thomas    schedule 19.10.2016
comment
... Затем, когда вы вставляете новые документы в любую коллекцию, вы всегда устанавливаете linked на false. Когда вы связываете документы, вы также возвращаетесь назад и устанавливаете linked на true. Чтобы ускорить это, вы также можете поместить индекс на linked. Вы обнаружите, что это значительно ускоряет вашу обработку, хотя в ПЕРВЫЙ раз она все равно будет медленной, так как все будет иметь значение linked = false.   -  person David Thomas    schedule 19.10.2016
comment
Вы можете написать приложение Foxx, которое сделает это за вас, я задокументировал пример приложения Foxx для чьего-то вопроса, он доступен здесь в StackOverflow. Стоит потратить некоторое время на изучение Foxx, так как он может быть приятным и быстрым, а функция, подобная той, которую вы описываете, является идеальным вариантом использования. Функция даже не требует никаких параметров, она просто запускается и будет сканировать только эти записи с linked = false.   -  person David Thomas    schedule 19.10.2016


Ответы (1)


Если вы запустите свой запрос в том виде, в котором он написан, без каких-либо индексов, ему придется выполнить два вложенных сканирования полной коллекции, что можно увидеть, посмотрев на вывод

db._explain(<your query here>);

что показывает что-то вроде:

  1   SingletonNode                1   * ROOT
  2   EnumerateCollectionNode      3     - FOR fromItem IN fromCollection   /* full collection scan */
  3   EnumerateCollectionNode      9       - FOR toItem IN toCollection   /* full collection scan */
  4   CalculationNode              9         - LET #3 = (fromItem.`fromAttributeValue` == toItem.`toAttributeValue`)   /* simple expression */   /* collections used: fromItem : fromCollection, toItem : toCollection */
  5   FilterNode                   9         - FILTER #3
  ...

Если вы это сделаете

db.toCollection.ensureIndex({"type":"hash", fields ["toAttributeValue"], unique:false})`

Затем будет одно сканирование полного набора таблиц в fromCollection, и для каждого найденного элемента в toCollection будет выполняться поиск хэша, что будет намного быстрее. Все будет происходить пачками, так что это уже должно исправить ситуацию. db._explain() покажет это:

  1   SingletonNode                1   * ROOT
  2   EnumerateCollectionNode      3     - FOR fromItem IN fromCollection   /* full collection scan */
  8   IndexNode                    3       - FOR toItem IN toCollection   /* hash index scan */

Работать только с недавно вставленными элементами в fromCollection относительно просто: просто добавьте временную метку времени импорта для всех вершин и используйте:

FOR fromItem IN fromCollection
    FILTER fromItem.timeStamp > @lastRun
    FOR toItem IN toCollection
        FILTER fromItem.fromAttributeValue == toItem.toAttributeValue
        INSERT { _from: fromItem._id, _to: toItem._id, otherAttributes: {}} INTO edgeCollection

и, конечно, поместите индекс пропуска на атрибут timeStamp в fromCollection.

Это должно прекрасно работать для обнаружения новых вершин в fromCollection. Он будет "пропускать" новые вершины в toCollection, которые связаны со старыми вершинами в fromCollection.

Вы можете обнаружить их, поменяв ролями fromCollection и toCollection в вашем запросе (не забудьте индекс на fromAttributeValue в fromCollection) и не забывая вставлять ребра только в том случае, если исходная вершина устарела, как в:

FOR toItem IN toCollection
    FILTER toItem.timeStamp > @lastRun
    FOR fromItem IN fromCollection
        FILTER fromItem.fromAttributeValue == toItem.toAttributeValue
        FILTER fromItem.timeStamp <= @lastRun 
        INSERT { _from: fromItem._id, _to: toItem._id, otherAttributes: {}} INTO edgeCollection

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

person Max Neunhöffer    schedule 20.10.2016
comment
Спасибо, Макс! Одна потенциальная проблема с использованием метки времени заключается в том, что различные коллекции импортируются с разной скоростью, поэтому данные в fromCollection могли быть импортированы прошлой ночью, но данные в toCollection были импортированы час назад. Кроме того, иногда новые данные необходимо связать с данными, которые были импортированы давно. Это сработает, если и fromItem, и toItem были ранее импортированы, но не только для одного. Моя команда с тех пор придумала детерминированные ключевые правила для ребер, поэтому дублирование не является проблемой - теперь это чисто производительность вставки. - person Nate Gardner; 20.10.2016