Катастрофа «Производственная авария», вызванная составным индексом MongoDB

база данных
Катастрофа «Производственная авария», вызванная составным индексом MongoDB

Резюме

  1. конец ноября我司商品服务изMongoDB主库Были случаи сильного джиттера и частых блокировок библиотек.
  2. Из-за наличия вставки во многих предприятияхMongoDB, а потом сразу запрос и прочая логика, чтобы проект не открывал разделение чтения-записи.
  3. Окончательная проблема с позиционированием связана с: собственным диском сервера + большим количеством慢查询Привести к
  4. Основываясь на вышеизложенной ситуации, студенты, изучающие эксплуатацию и техническое обслуживание, сосредоточатся на повышенииMongoDB慢查询мониторинг и оповещение

немного удачи: обновление срока действия кэша было завершено непосредственно перед аварией, и срок действия составлял один месяц.C端查询все ложится на кеш, так что ни к чемуP0级Авария, только частично заблокированB端逻辑


повтор аварии

В нашей компании работают различные мониторинги, и я внезапно получил тревожное уведомление о том, что сервер базы данных в этот день имеет высокую нагрузку, поэтому мы с коллегами поспешно вошли в систему.Zabbix监控, как показано на рисунке ниже, скриншот был в нормальном состоянии, и я забыл сохранить изображение во время аварии.Возможно, что кривая данных в то время была максимумом и минимумом, а минимум был очень высоким.

Официальный сайт распределенной системы мониторинга Zabbix:www.zabbix.com/


начать анализ

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

---------------------------------------------------------------------------------------------------------------------------+
Op          | Duration | Query                                                                                                                   ---------------------------------------------------------------------------------------------------------------------------+
query       | 5 s      | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}               
query       | 5 s      | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}               query       | 4 s      | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}               query       | 4 s      | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}              query       | 4 s      | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}
...

Если запрос очень медленный, первое, о чем должны подумать все R&D, это索引, поэтому я сразу проверил индекс следующим образом:

### 当时的索引

db.sku_main.ensureIndex({"_id": -1},{background:true});
db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true});
db.sku_main.ensureIndex({"orgCode": 1, "upcCode": 1},{background:true});
....

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

Является ли медленный запрос первым в приведенном выше запросе и указывает на источник проблемы?

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

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

### 当时场景日志

query: { $query: { shopCategories.0: { $exists: false }, orgCode: 337451, fixedStatus: { $in: [ 1, 2 ] }, _id: { $lt: 2038092587 } }, $orderby: { _id: -1 } } planSummary: IXSCAN { _id: 1 } ntoreturn:1000 ntoskip:0 keysExamined:37567133 docsExamined:37567133 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:293501 nreturned:659 reslen:2469894 locks:{ Global: { acquireCount: { r: 587004 } }, Database: { acquireCount: { r: 293502 } }, Collection: { acquireCount: { r: 293502 } } } 

# 耗时
179530ms

Занимает 180 секунд и основано на запросе执行计划видно что идет_id_Index было выполнено полное сканирование таблицы, и общее количество просканированных данных составило: 37567133, что неудивительно.


решить быстро

Обнаружив проблему, ее невозможно сразу изменить.止损

В сочетании со временем в то время было относительно поздно, поэтому мы выпустили объявление о запрете вышеуказанной функции запроса и временной приостановке некоторых предприятий.Через некоторое время мы выполнили主从切换, посмотри сноваZabbix监控Все хорошо.


Проанализируйте первопричину

Давайте рассмотрим оператор запроса и наш ожидаемый индекс следующим образом:

### 原始Query
db.getCollection("sku_main").find({ 
        "orgCode" : NumberLong(337451), 
        "fixedStatus" : { 
            "$in" : [
                1.0, 
                2.0
            ]
        }, 
        "shopCategories" : { 
            "$exists" : false
        }, 
        "_id" : { 
            "$lt" : NumberLong(2038092587)
        }
    }
).sort(
    { 
        "_id" : -1.0
    }
).skip(1000).limit(1000);

### 期望的索引
db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true});

На первый взгляд вроде все хорошо, поляorgCodeзапрос эквивалентности, поле_idПочему так медленно выполняется сортировка в обратном порядке в соответствии с направлением создания индекса?

Однако ключевым моментом является то, что$ltначальство

Точка знаний 1: индекс, направление и сортировка

В MongoDB операции сортировки могут обеспечить упорядочение результатов путем извлечения документов из индекса в порядке индекса.

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

Уведомление: при сортировке памяти максимальный предел по умолчанию составляет 32 МБ, и если он превышается, будет выдана ошибка.

Пункт знаний 2: индекс с одним столбцом не заботится о направлении

И MongoDB, и MySQL используют древовидные структуры в качестве индексов, если排序方向и索引方向Вместо этого просто пройдите с другого конца, вот так:

# 索引
db.records.createIndex({a:1}); 

# 查询
db.records.find().sort({a:-1});

# 索引为升序,但是我查询要按降序,我只需要从右端开始遍历即可满足需求,反之亦然
MIN 0 1 2 3 4 5 6 7 MAX

Составная индексная структура MongoDB

Официальное введение: MongoDB поддерживаетcompound indexes, где одна структура индекса содержит ссылки на несколько полей в документах коллекции.

Схематическая диаграмма структуры составного индекса выглядит следующим образом:

Индекс оказывается таким же, как мы обсуждали,userid顺序,score倒序, предполагая, что текущая таблица имеет индекс с одним столбцом:{"score": -1}

Нам нужно ответить на второй вопрос:Нужно ли мне заботиться о направлении при использовании составного индекса?

Предположим два условия запроса:

# 查询 一
db.getCollection("records").find({ 
  "userid" : "ca2"
}).sort({"score" : -1.0});

# 使用索引
{"userid":1, "score":-1}


# 查询 二
db.getCollection("records").find({ 
  "userid" : "ca2"
}).sort({"score" : 1.0});

# 使用索引
{"userid":1, "score":-1}

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

# 错误示范
db.getCollection("records").find({ 
  "userid" : "ca2",
  "score" : { 
    "$lt" : NumberLong(2038092587)
  }
}).sort({"score" : -1.0});

# 使用索引
{"score":-1}

Причина ошибки следующая:

  • Так как поле счета сортируется в обратном порядке, для использования этого индекса необходимо пройти слева
  • Поиск данных меньше определенного значения в обратном порядке неизбежно приведет к сканированию большого количества бесполезных данных, а затем их отбрасыванию.В текущем сценарии поиск значения, превышающего определенное значение, является лучшим решением.
  • Поэтому MongoDB рассматривает больше сценариев, в этом случае он отказывается от составного индекса и выбирает другие индексы, такие как одностолбцовый индекс оценки.

целевая модификация

Внимательно прочитав первопричину, просмотрите оператор онлайн-запроса, целенаправленно измените его и поместите$ltсостояние изменилось на$gtНаблюдайте за результатами оптимизации:

# 原始查询
[TEMP INDEX] => lt: {"limit":1000,"queryObject":{"_id":{"$lt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}}

# 原始耗时
[TEMP LT] => 超时 (超时时间10s)

# 优化后查询
[TEMP INDEX] => gt: {"limit":1000,"queryObject":{"_id":{"$gt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}}

# 优化后耗时
[TEMP GT] => 耗时: 383ms , List Size: 999

поправка

  1. Условия обратной сортировки

    # 上文提到了索引可以从左或者从右开始遍历,因此调整文档扫描方向即可
    # 注: 需要主动申明首位(orgCode)字段查询方向,否则会按默认方向查找
    sort({ "orgCode" : -1.0},{ "_id" : 1.0})
    
  2. Изменить бизнес-код

    1. Предварительно найти условие запроса_idМинимум (полное использование индексации, очень быстро)
    2. будетltЗамените запрос наgtПросто запрос

Расширенный сценарий: сценарий, в котором нет другого вмешательства в индекс.

Чтобы смоделировать онлайн-аварии выше, мы приняли составной индекс и индекс с одним столбцом, а именно:

{"userid": 1, "score": -1}
{"score": -1}

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

# 仅剩复合索引时
db.getCollection("records").find({ 
  "userid" : "ca2",
  "score" : { 
    "$lt" : NumberLong(2038092587)
  }
}).sort({"score" : -1.0});

# 使用索引
{"userid":1, "score":-1}

Суммировать

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

  • При изучении базы данных вы можете использовать аналогию, но вам нужно обратить особое внимание на ее различия (MySQL, индекс MongoDB, направление индекса).
  • Одностолбцовый индекс MongoDB не может заботиться о направлении
  • Когда MongoDB не может быть отсортирован по индексу, он будет сортироваться в памяти.Если он превышает размер по умолчанию (32M), я сожалею, что сообщаю об ошибке.
  • MongoDB数据库复合索引在使用中一定要注意其方向, чтобы полностью понять его логику,要么完全相同,要么完全相反, чтобы избежать аннулирования индекса
  • Для предыдущего:但当索引选择器没有更优解时,即使查询方向不符合索引方向,也会使用目标索引

Наконец

Если вы считаете этот контент полезным:

  1. Конечно, ставьте лайки и поддерживайте~
  2. Ищите и подписывайтесь на официальный аккаунт »это Кервин", болтаем вместе~
  3. Давайте посмотрим на последние несколько статей »Заполнение пробелов», эта серия будет продолжать выпускаться ~