Запрос таймсерии MongoDB не использует индекс и занимает слишком много времениJAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Запрос таймсерии MongoDB не использует индекс и занимает слишком много времени

Сообщение Anonymous »

У меня есть большая коллекция временных рядов, наполненная миллионами документов под названием «События», которые имеют следующую структуру

Код: Выделить всё

{
"_id": {"$oid": "123456"},
"class": "...",
"event_data": {...},
"ts": {"$date": "2025-01-01T01:00:00.000Z"}
}
С индексами в поле ts как по возрастанию, так и по убыванию. Я пытаюсь выполнить запрос, чтобы переместить документы между двумя метками времени, используя Java. Вот так:

Код: Выделить всё

ZonedDateTime lowerBound = ...;
ZonedDateTime upperBound = ...;

var query = Query.query(new Criteria().andOperator(
Criteria.where("ts").gte(lowerbound.toInstant().toEpochMilli()),
Criteria.where("ts").lt(upperbound.toInstant().toEpochMilli()),
)

var result = mongoTemplate.find(query, Events.class)
Этот запрос работает... примерно через 20 минут, поскольку он сканирует все коллекции без использования индексов. Когда я использую отладчик IntelliJ, я вижу, что запрос форматируется следующим образом:

Код: Выделить всё

Query: { "$and" : [{ "ts": { "$gte" : 1733852133000}}. { "ts" : { "$lt": 1733853933000}} ] }
Что я перевожу в код консоли MongoDB как таковой:

Код: Выделить всё

db.events.find({
"$and": [
{ "ts": { "$gte": 1733852133000}},
{ "ts": { "$lt": 1733853933000}},
]
})
Это в точности имитирует то, что происходит в коде Java, технически это работает, но сканирует всю коллекцию, что я могу дополнительно увидеть, если выполнить .explain() и увидеть только этап COLLSCAN.
Однако, если я напишу следующий фрагмент, код выполнится менее чем за пару секунд:

Код: Выделить всё

db.events.find({
"$and": [
{ "ts": { "$gte": new Date("2025-01-01T01:00:00Z)}},
{ "ts": { "$lt": new Date("2025-01-02T01:00:00Z)}},
]
})
Это также совпадает, когда я запускаю .explain(), поскольку я вижу этапы FETCH и IXSCAN.
В чем основная разница между этими двумя запросами? Как я могу перевести свой Java-код во вторую версию и фактически использовать имеющуюся у нас индексацию?
Некоторые вещи, которые я пробовал:
  • Используя Instant или Date вместо Long в качестве значений, они просто имеют ту же проблему, что и COLSCAN
  • Добавление подсказки("ts_1") к принудительное использование индекса. При запуске этого в консоли MongoDB выдается ошибка с предоставленной подсказкой, которая не соответствует существующему индексу, что совершенно неверно, поскольку я вижу индекс при запуске .getIndexes(). Запуск его в коде Java, похоже, вызывает ту же проблему, что и раньше, когда он выполняет COLLSCAN
Редактировать 21.03.25
Вот еще несколько подробностей о том, что происходит. Сначала просто чтобы показать, что индексы существуют:

Код: Выделить всё

db.events.getIndexes();

// returns
{
"key": {
"ts": 1
},
"name": "ts_1",
"v": 2
},
{
"key": {
"ts": -1
},
"name": "ts_-1",
"v": 2
}
Затем этапы из .explain() рабочего запроса (я удалил некоторые детали):

Код: Выделить всё

db.events.find({
"$and": [
{ "ts": { "$gte": new Date("2024-12-10T17:35:33Z")} },
{ "ts": { "$lt": new Date("2024-12-10T18:05:33Z")} }
],
}, {}, {}).explain();

// returns
{
...
"stages": [
{
"$cursor": {
"queryPlanner": {
"namespace": "database.system.buckets.events",
"indexFilterSet": false,
"parsedQuery": {
"$and": [
{
"_id": {
"$lt": {"$oid": "675882ed0000000000000000"}
}
},
{
"_id": {
"$gte": {"$oid": "67572a650000000000000000"}
}
},
{
"control.max.ts": {
"$_internalExprGte": {"$date": "2024-12-10T17:35:33.000Z"}
}
},
{
"control.min.ts": {
"$_internalExprGte": {"$date": "2024-12-09T17:35:33.000Z"}
}
},
{
"control.max.ts": {
"$_internalExprLt": {"$date": "2024-12-11T18:05:33.000Z"}
}
},
{
"control.min.ts": {
"$_internalExprLt": {"$date": "2024-12-10T18:05:33.000Z"}
}
}
]
},

...

"winningPlan": {
"stage": "FETCH",
"filter": {
"$and": [
{
"_id": {
"$lt": {"$oid": "675882ed0000000000000000"}
}
},
{
"_id": {
"$gte": {"$oid": "67572a650000000000000000"}
}
}
]
},
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"control.min.ts": 1,
"control.max.ts": 1
},
"indexName": "ts_1",
"isMultiKey": false,
"multiKeyPaths": {
"control.min.ts": [],
"control.max.ts": []
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 2,
"direction": "forward",
"indexBounds": {
"control.min.ts": ["[new Date(1733765733000), new Date(1733853933000))"],
"control.max.ts": ["[new Date(1733852133000), new Date(1733940333000))"]
}
}
}
}

И этапы из .explain() для нерабочего запроса (также удалены некоторые детали):

Код: Выделить всё

db.events.find({
$and: [
{ ts: { $gte: 1733852133000 } },
{ ts: { $lt: 1733853933000 } }
],
}, {}, {})

// returns
{
...
"stages": [
{
"$cursor":  {
"queryPlanner": {
"namespace": "db.system.buckets.events",
"indexFilterSet": false,
"parsedQuery": {
},
"queryHash": "5F5FC979",
"planCacheKey": "5F5FC979",
"maxIndexedOrSolutionsReached": false,
"maxIndexedAndSolutionsReached": false,
"maxScansToExplodeReached": false,
"winningPlan": {
"stage": "COLLSCAN",
"direction": "forward"
},
"rejectedPlans": []
}
}
},
{
"$_internalUnpackBucket": {
"exclude": [],
"timeField": "ts",
"metaField": "data",
"bucketMaxSpanSeconds": 86400,
"assumeNoMixedSchemaData": true,
"eventFilter": {
"$and": [
{
"ts": {
"$gte": 1733852133000
}
},
{
"ts": {
"$lt": 1733853933000
}
}
]
}
}
}
]
}
Наконец, вот значение объекта Java Query, из которого я получаю приведенный выше запрос MongoDB:

Код: Выделить всё

Query: { "$and" : [{ "ts" : { "$gte" : 1733852133000}}, { "ts" : { "$lt" : 1733853933000}}]}, Fields: {}, Sort: {}
Даже если я использовал $date вместо милли эпох для фильтров, результаты были бы такими же.
Из этих поясняющих комментариев мы можем видеть большую разницу между двумя, где рабочий с new Date() добавляет дополнительную фильтрацию в столбце _id. Я предполагаю, что это как-то связано с тем, как работают коллекции временных рядов, но я не уверен. В дополнение к этому фильтры разбиваются, и перед FETCH есть этап ввода IXSCAN.
Я нашел на форуме MongoDB еще один вопрос, который очень похож, но без реального ответа на то, что происходит.. ссылка

Подробнее здесь: https://stackoverflow.com/questions/795 ... ively-long
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»