Вопрос по couchdb, mapreduce, couchapp – CouchDB: вернуть новейшие документы типа на основе метки времени

2

У меня есть система, которая принимает обновления статуса из разных уникальных источников, и каждое обновление статуса создает новый документ в следующей структуре:

<code>{
 "type": "status_update",
 "source_id": "truck1231",
 "timestamp": 13023123123,
 "location": "Boise, ID"
}
</code>

Данные чисто примерные, но донесут идею до конца.

Теперь эти документы создаются с интервалом, один раз в час или около того. Через час мы могли бы вставить:

<code>{
 "type": "status_update",
 "source_id": "truck1231",
 "timestamp": 13023126723,
 "location": "Madison, WI"
}
</code>

Все, что мне интересно, - это видеть последние обновления из каждого уникального источника. Сейчас я делаю это, взяв карту:

<code>function(doc) {
  if (doc.type == "status_update") {
    emit(doc.source_id, doc);
  }
}
</code>

И сокращение:

<code>function(keys, values, rereduce) {
  var winner = values[0];
  var i = values.length;
  while (i--) {
    var val = values[i];
    if (val.timestamp > winner.timestamp) winner = val;
  }
  return winner;
}
</code>

И запрос данных как сокращение сgroup=true, Это работает, как ожидалось, и обеспечивает ключевой результат только последних обновлений.

Проблема в том, что он очень медленный и требует от меняreduce_limit=false в конфиге CouchDB.

Такое ощущение, что должен быть более эффективный способ сделать это. Обновление одного и того же документа не вариант - история важна, хотя в этом случае я не требую ее. Обработка данных на стороне клиента не является вариантом, так как это CouchApp, а количество документов в системе на самом деле довольно велико, и отправлять их по сети практически невозможно.

Заранее спасибо.

Как насчет обновления самого документа и добавления & quot; старого & quot; версия как приложение к рассматриваемому документу? (и повторять этот процесс для каждого нового изменения статуса) Dominic Barnes
Я ждал там остроумного комментария;) radicand
Вау, truck1231 проезжает 1700 миль в час! Неплохо. JasonSmith

Ваш Ответ

3   ответа
3

incremental что в основном означает, что результаты всегда кэшируются, поэтому последующие запросы для одного и того же представления (даже с разными параметрами поиска) запускаются "бесплатно". (или в логарифмическом времени).

Однако это не совсем верно для редуцирующих групп. Иногда частичные результаты должны быть уменьшены на лету. Может быть, это то, что вы бьете.

Вместо этого, как насчет представления карты (то есть без функции уменьшения), которое генерирует строки, подобные этой, с массивом в качестве ключа:

// Row diagram (pseudo-code, just to show the concept).
// Key                    , Value
// [source_id, timestamp] , null // value is not very important in this example
["truck1231", 13023123123], null
["truck1231", 13023126723], null
["truck5555", 13023126123], null
["truck6666", 13023000000], null

Обратите внимание, что все временные метки для источника "clump" все вместе. (На самом деле, онисличать.) Чтобы найти последнюю временную метку для"truck1231"просто запрашивает последнюю строку в этом "сгустке". Чтобы сделать это, сделайте нисходящий запрос, с конца, сlimit=1 аргумент. Чтобы указать «конец», используйте{} & quot; высокий ключ & quot; значение в качестве второго элемента в ключе (подробности см. в ссылке на параметры сортировки).

?descending=true&limit=1&startkey=["truck1231",{}]

(На самом деле, поскольку ваши временные метки являются целыми числами, вы можете испустить их отрицание, например,-13023123123, Это немного упростит ваш запрос, но «я не знаю», это похоже на игру с огнем.)

Чтобы создать такие строки, мы используем функцию карты, например:

function(doc) {
  // Emit rows sorted first by source id, and second by timestamp
  if (doc.type == "status_update" && doc.timestamp) {
    emit([doc.source_id, doc.timestamp], null) // Using `doc` as the value would be fine too
  }
}
Это имеет место - с решением, которое у меня есть в настоящее время, оно означает, что у меня есть только один запрос, но я в порядке с двумя запросами, как в решении _stats, и другими, если он более производительный. radicand
Повторно читая ваш вопрос, я подумал, что вы, возможно, захотите «отчет» введите результат, который дает вам последнее обновление дляall источники в одном большом результате. Если так, дайте мне знать. Я думаю, что есть еще возможность улучшить вашу функцию сокращения. Это не должно требовать отключения redu_limit, поэтому, возможно, мы сможем отладить это немного вместо этого ответа.
3

используя_stats built-in reduce functionЗатем выполните другой запрос, чтобы получить документы. Вот мнения:

"views": {
  "latest_update": {
    "map": "function(doc) { if (doc.type == 'status_update') emit(doc.source_id, doc.timestamp); }",
    "reduce": "_stats"
  },
  "status_update": {
    "map": "function(doc) { if (doc.type == 'status_update') emit([doc.source_id, doc.timestamp], 1); }"
  }
}

Первый запросlatest_update сgroup=true, затемstatus_update с чем-то вроде (правильно URL-кодированный):

keys=[["truck123",TS123],["truck234",TS234],...]&include_docs=true

где TS123 и TS234 являются значениямиmax вернулсяlatest_update.

+1 Да, я забыл о _stats. Я верю твоему первому взгляду,latest_update отвечает на вопрос ОП, который состоит в том, чтобы получить тот же результат, но с лучшей производительностью.
Я написал рагу, чтобы решить именно эту проблему; однако из-за ошибки CouchDB (require не поддерживается в функциях Reduce) она еще не работает.github.com/iriscouch/stew
Это прекрасно - я не знал о функции _stats, и она, кажется, работает довольно хорошо. Спасибо! radicand
_max встроенная функция была бы очень полезна здесь, чтобы минимизировать передачу данных ...
1

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

function(doc) {
  if (doc.type == "status_update") {
    emit(doc.source_id, [doc._id,doc.timestamp]);
  }
}

function(keys, values, rereduce) {
  var winner = values[0];
  var i = values.length;
  while (i--) {
    var val = values[i];
    if (val[1] > winner[1]) winner = val;
  }
  return winner;
}

Это должно заставить вас[id,timestamp] пара для каждого ключа, не будучи слишком медленной или сохраняя слишком много данных в представлениях.

Как только у вас будет список идентификаторов на клиенте, отправьте второй запрос, используя массовый GET API:

_all_docs?keys=[id1,id2,id3,...,idn]&include_docs=true 

Это позволит получить все документы за один запрос.

Похожие вопросы