Кардинальность по гистограмме даты - PullRequest
4 голосов
/ 11 марта 2019

Каков наилучший способ запроса Elasticsearch для реализации гистограммы даты, представляющей общее число уникальных показателей посетителей?

Учитывая следующие данные:

PUT /events
{
"mappings" : {
        "_doc" : {
            "properties" : {
                "userId" : { "type" : "keyword" },
                "eventDate" : { "type" : "date" }
            }
        }
    }
}

POST /events/_bulk
{ "index" : { "_index" : "events", "_type" : "_doc", "_id" : "1" } }
{"userId": "1","eventDate": "2019-03-04T13:40:18.514Z"}
{ "index" : { "_index" : "events", "_type" : "_doc", "_id" : "2" } }
{"userId": "2","eventDate": "2019-03-04T13:46:18.514Z"}
{ "index" : { "_index" : "events", "_type" : "_doc", "_id" : "3" } }
{"userId": "3","eventDate": "2019-03-04T13:50:18.514Z"}
{ "index" : { "_index" : "events", "_type" : "_doc", "_id" : "4" } }
{"userId": "1","eventDate": "2019-03-05T13:46:18.514Z"}
{ "index" : { "_index" : "events", "_type" : "_doc", "_id" : "5" } }
{"userId": "4","eventDate": "2019-03-05T13:46:18.514Z"}

Теперь,если я запрашиваю количество элементов в поле userId, я получаю 4 различных посетителя.

POST /events/_search
{
    "size": 0,
    "aggs": {
        "visitors": {
            "cardinality": {
                "field": "userId"
            }
        }
    }
}

Однако, распределяя документы по гистограмме даты, я получаю общую сумму 5, потому что в обоих сегментах есть повторяющийся userId.

POST /events/_search
{
    "size": 0,
    "aggs": {
        "visits_over_time": {
            "date_histogram": {
                "field": "eventDate",
                "interval": "1d"
            },
            "aggs": {
                "visitors": {
                    "cardinality": {
                        "field": "userId"
                    }
                }
            }
        }
    }
}

Есть ли способ отфильтровать эти повторяющиеся значения?Как лучше всего это сделать?

Ответы [ 3 ]

4 голосов
/ 15 марта 2019

Мы столкнулись с той же проблемой в нашем коде, и мы решили использовать агрегирование терминов в поле UserId с вложенным минимальным агрегированием в поле datetime. Это предоставляет вам корзину для каждого идентификатора пользователя, содержащего корзину с первым посещением. Мы выполняем эту агрегацию за пределами гистограммы даты и впоследствии отображаем ее вручную.

"aggs": {
    "UniqueUsers": {
      "terms": {
        "field": "userId",
        "size": 1000,
      }, "aggs": {
        "FirstSeen": {
          "min": {
            "field": "date"
          }
        }
      }
    }
  }

Это работает для нас, но я уверен, что должна быть лучшая реализация.

1 голос
/ 13 марта 2019

Идентификаторы пользователей повторяются, но они происходят в разные дни, поэтому их распределение по дням приведет к тому, что это произойдет несколько раз, если вы не смотрите на определенный день. Даже в этом случае, если один и тот же идентификатор встречается в один и тот же день более одного раза, у вас все равно могут быть повторяющиеся идентификаторы в зависимости от того, насколько точны временные рамки, на которые вы смотрите. Поскольку вы просматриваете интервалы в один день, верно, что он возвращает 5 записей и должен сказать, что 4-го числа было 3 идентификатора, один из которых является дубликатом, а на следующий день показаны две записи с двумя разными идентификаторами, один из которых это дубликат. Если вы увеличите интервал до недели или месяца, эти дубликаты будут считаться одним.

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

По сути, он возвращает всех уникальных посетителей в данный день. Если вы не заботитесь об отдельных пользователях, а просто хотите узнать, сколько их, вам нужен другой подход. Возможно, группа по запросу

0 голосов
/ 31 марта 2019

Несмотря на то, что я хотел бы избежать сценариев, Агрегирование метрик по сценариям кажется единственным способом выполнить то, что было запрошено:

{
    "size": 0,
    "aggs": {
        "visitors": {
            "scripted_metric": {
                "init_script": "params._agg.dateMap = new HashMap();",
                "map_script": "params._agg.dateMap.merge(doc.userId[0].toString(), doc.eventDate.value, (e1, e2) -> e1.isBefore(e2) ? e1 : e2);",
                "combine_script": "return params._agg.dateMap;",
                "reduce_script": "def dateMap = new HashMap(); for (map in params._aggs) { if (map == null) continue; for (entry in map.entrySet()) dateMap.merge(entry.key, entry.value, (e1, e2) -> e1.isBefore(e2) ? e1 : e2); } def hist = new TreeMap(); for (entry in dateMap.entrySet()) hist.merge(entry.value.toString(), 1, (a, b) -> a + 1); return hist;"
            }
        }
    }
}

Init просто создает пустой HashMap, Mapзаполняет эту карту с помощью userId в качестве ключа и устанавливает в качестве значения самую старую eventDate, а Combine просто разворачивает карту, которая должна быть передана в Reduce:

def dateMap = new HashMap();
for (map in params._aggs) {
    if (map == null) continue;
    for (entry in map.entrySet())
        dateMap.merge(entry.key, entry.value, (e1, e2) -> e1.isBefore(e2) ? e1 : e2);
}

def hist = new TreeMap();
for (entry in dateMap.entrySet())
    hist.merge(entry.value.toString(), 1, (a, b) -> a + 1);
return hist;

Up для объединения код был выполнен для каждого узла кластера,Reduce объединяет все карты в одну (т.е. dateMap), сохраняя самую старую eventDate для userId.Затем он подсчитывает вхождения каждой eventDate.

Результат:

"aggregations": {
    "visitors": {
        "value": {
            "2019-03-04T13:40:18.514Z": 1,
            "2019-03-04T13:46:18.514Z": 1,
            "2019-03-04T13:50:18.514Z": 1,
            "2019-03-05T13:46:18.514Z": 1
        }
    }
}

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

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

Примечание²: запускиз Elasticsearch 6.4 state и states следует использовать вместо params._agg и params._aggs.

...