CouchDB: вернуть новейшие документы типа на основе метки времени

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

{
 "type": "status_update",
 "source_id": "truck1231",
 "timestamp": 13023123123,
 "location": "Boise, ID"
}

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

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

{
 "type": "status_update",
 "source_id": "truck1231",
 "timestamp": 13023126723,
 "location": "Madison, WI"
}

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

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

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

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;
}

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

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

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

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

3 ответа

Решение

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

"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,

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

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

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

// 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

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

?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
  }
}

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

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 

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

Другие вопросы по тегам