Статьи

Понимание характеристик производительности секционированных коллекций

В TokuMX 1.5, которая находится прямо за углом, большая особенность будет разделять коллекции . Эта функция похожа на многораздельные таблицы в Oracle, MySQL, SQL Server и Postgres. У многих возникает вопрос: «Почему я должен использовать многораздельные таблицы?» Короче, это сложно. Ответ зависит от вашей рабочей нагрузки, вашей схемы и выбранной вами базы данных. Например, этот пост, связанный с Oracle, гласит: «Любой, у кого неразделенные базы данных более 500 гигабайт, терпит бедствие». Это не относится к TokuDB или TokuMX. Тем не менее, секционированные таблицы являются ценными; Именно поэтому мы добавляем их в TokuMX 1.5.

В этой статье я хочу сделать шаг назад и объяснить технические характеристики многораздельных таблиц на техническом уровне. Я не буду фокусироваться на случаях использования или сценариях. Вместо этого я буду сравнивать производительность и поведение основных операций в многораздельной таблице с показателями обычной таблицы.

Обратите внимание, что коллекции в MongoDB и TokuMX аналогичны таблицам в реляционных базах данных, а документы аналогичны строкам. В оставшейся части этого поста я буду ссылаться на коллекции и документы, но вся информация в равной степени относится и к реляционным базам данных.

Во-первых, быстрое резюме. Что такое секционированная коллекция? Цитирую себя отсюда :

«Секционированная коллекция — это коллекция, которая под покровами разбита (или разделена) на несколько отдельных коллекций, основанных на диапазонах« ключа раздела ». С точки зрения разработчика приложения, коллекция — это просто еще одна коллекция ».

Надеемся, что с самого начала первая характеристика производительности очевидна: мгновенное удаление больших объемов данных путем удаления разделов. Если у вас есть коллекция, хранящая данные временных рядов, и вы хотите, чтобы скользящий период данных продолжался шесть месяцев, то при обычном сборе, когда вы вставляете новые данные, вам нужно удалить данные, возраст которых превышает 6 месяцев. Удаление данных может быть столь же дорогостоящим, как и вставка данных, так как оба должны находить документы для удаления и поддерживать вторичные индексы. Это может сократить вашу пропускную способность записи в два раза. В случае секционированных коллекций стоимость удаления этих данных практически бесплатна, поскольку все, что вы делаете, — это, по сути, файлы «rm», связанные с самым старым разделом. Именно так TokuMX поддерживает свой оплог , ежедневно разбивая и удаляя старые данные.

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

  • Читает
  • Пишет

Запросы, будь то «SELECT…» в реляционных базах данных, find () в MongoDB / TokuMX или агрегация в MongoDB / TokuMX, считываются. Вставки в основном просто пишет. Обновления и удаления представляют собой комбинацию операций чтения и записи. Чтения (в действительности запросы) выполняются для поиска документов, подлежащих обновлению или удалению, а последующие записи выполняются для выполнения обновления / удаления. Таким образом, большая часть понимания характеристик производительности секционированных коллекций заключается в понимании того, как они выполняют чтение и запись.

Пишет

Из моего прочтения, когда люди обсуждают запись в многораздельные коллекции, они обычно предполагают, что почти все записи идут в последний раздел (как это было бы с данными временных рядов). По крайней мере, это большая мотивация. Причина в следующем. Базы данных на основе B-дерева (такие как MySQL, Oracle, Postgres и SQL Server) борются с записями, когда индексы коллекции не помещаются в памяти. Вот почему MySQL и MongoDB так плохо борются с iibench.

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

С другой стороны, TokuDB и TokuMX, которые используют индексы Fractal Tree , не борются с записями, когда индексы коллекции не помещаются в памяти. Вот почему они так хорошо работают с iibench. Таким образом, с TokuDB и TokuMX нет необходимости разбивать разделы на такую ​​степень детализации, чтобы последний раздел всегда помещался в памяти. Если вы чувствуете необходимость ежедневного разделения с другими базами данных, возможно, вы можете уменьшить гранулярность до еженедельной, ежемесячной или вообще не делать этого. Это то, что нужно учитывать.

Читает

Чтение смешное, история проста и сложна. История проста следующим образом: чтение — это чтение, независимо от структуры данных или расположения данных. Если у вас есть 1 ТБ данных, а некоторые конкретные данные не находятся в памяти, а вместо этого находятся на диске, то для чтения этих данных требуется ввод / вывод. Принадлежат ли данные разделенному набору, обычному набору, B-дереву, дереву фракталов, что бы то ни было, это не имеет значения. Ввод / вывод будет выполнен. Это одна из причин, по которой рабочие нагрузки с высокой нагрузкой чтения одинаково работают в MongoDB, MySQL, TokuDB и TokuMX. Это также опровергает аргумент, что запросы к многораздельной коллекции «с большей вероятностью останутся в памяти». Это просто неправда. Если шаблон использования коллекции содержит данные запроса в памяти для многораздельной коллекции, эти данные также будут в памяти для неразделенной коллекции.

Ключом к пониманию того, как чтения (в действительности запросы) выполняются в многораздельной коллекции по сравнению с неразделенной коллекцией, является понимание алгоритма или плана запроса, используемого для выполнения этого чтения. И вот тут история усложняется. Производительность запросов в секционированных и обычных коллекциях зависит от того, насколько эффективно они могут найти документы, необходимые для ответа на запрос.

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

Если запрос использует другой индекс, все становится интересным. Чтобы понять, как это происходит, мы должны понимать, как секционированная коллекция выполняет такой запрос. Лучший способ сделать это с примерами. Для всех примеров предположим, что у нас есть коллекция «foo», которая:

  • разделен на отметку времени, то есть имеет первичный ключ {ts: 1, _id: 1}
  • имеет вторичный индекс на «а»

Итак, это вывод db.foo.getIndexes ():

> db.foo.getIndexes()
[
       {
               "key" : {
                       "ts" : 1,
                       "_id" : 1
               },
               "unique" : true,
               "ns" : "test.foo",
               "name" : "primaryKey",
               "clustering" : true
       },
       {
               "key" : {
                       "_id" : 1
               },
               "unique" : true,
               "ns" : "test.foo",
               "name" : "_id_"
       },
       {
               "key" : {
                       "a" : 1
               },
               "ns" : "test.foo",
               "name" : "a_1"
       }
]

Пример 1:

db.foo.find({a:100})

Давайте рассмотрим эти два случая:

  • Запрос находит один документ (или очень мало).
  • Запрос находит много документов.

В случае, когда запрос находит несколько документов, многораздельные коллекции могут работать плохо. Вот почему. В обычной коллекции один вторичный поиск выполняется во вторичном индексе, а затем в первичном ключе, чтобы найти документ, и все готово. С разделенной коллекцией поиск должен быть выполнен в каждом разделе, потому что любой раздел может иметь этот документ, который мы ищем. Таким образом, количество поисков эквивалентно количеству разделов, которые у нас есть. Если у нас большое количество разделов (например, 100 или 1000), это может быть очень плохо.

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

Таким образом, в этом примере, в зависимости от того, сколько данных возвращается, многораздельные коллекции могут работать где угодно — от «точно так же» до «ужасно худшего».

Итак, мораль здесь заключается в том, чтобы быть осторожным с выполненными запросами, которые должны запрашивать все разделы.

Пример 2:

db.foo.find({
    $and : [
          { a : 100 },
          { ts : {
              $gte: ISODate("2014-06-01T00:00:00.000Z"),
              $lt: ISODate("2014-06-02T00:00:00.000Z")
          } }
    ]
})

В этом примере мы все еще хотим использовать вторичный индекс «a», но учтите, что теперь у нас есть дополнительный фильтр для «ts», который является первым полем нашего ключа разделения (то есть первичного ключа).

В обычной коллекции, если используется вторичный индекс «а», обрабатываются все документы, где а равно 100. Это включает в себя документы, где ts не попадает в указанный диапазон дат. Таким образом, обычная коллекция может обработать гораздо больше документов, чем необходимо. Секционированная коллекция, с другой стороны, заметит, что существует предложение, позволяющее запросу адресовать подмножество разделов, а именно те, которые перекрываются с указанным диапазоном дат. Это уменьшает количество обрабатываемых документов и ускоряет запрос.

В этом конкретном примере разделенная коллекция может работать намного лучше. Проблема, однако, заключается в том, что вторичный индекс «а» не является оптимальным. Независимо от того, является ли коллекция секционированной или нет, этот запрос использует составной индекс {a: 1, ts: 1}. Так что да, запрос к многораздельной коллекции может использовать преимущества фильтрации разделов для повышения скорости запросов, но вполне возможно (если маловероятно), что правильный составной индекс, включающий ключ раздела, будет работать так же хорошо.

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

  • Если запросы не включают ключ разделения (например, отметку времени в приведенном выше примере), тогда запросы могут работать НАМНОГО хуже. В лучшем случае они будут работать сопоставимо.
  • Если запросы содержат ключ разделения, они могут работать лучше, но это, вероятно, связано с неоптимальной индексацией. Если ключ раздела добавляется к вторичному индексу, тогда запросы, вероятно, будут работать лучше. Существуют угловые случаи, в которые я не буду углубляться.
  • При правильной индексации гранулярность разделов (например, разделение по месяцам и по дням) не должна иметь большого значения. Существуют угловые случаи, в которые я не буду углубляться.

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