Секционированные базы данных
Секционированная база данных объединяет документы в логические секции с помощью ключа секционирования. Все документы относятся к какой-либо секции, и многим документам обычно назначается один и тот же ключ секционирования. Преимущество секционированных баз данных заключается в том, что вторичные индексы могут значительно эффективнее находить подходящие документы, поскольку их записи содержатся в соответствующей секции. Это означает, что при чтении по вторичному индексу сканируется только один диапазон секции, а не выполняется чтение из копии каждого шарда.
Чтобы познакомить вас с секционированными базами данных, рассмотрим пример использования, который поможет описать преимущества этой функции. Для этого примера возьмём базу данных, в которой хранятся показания датчиков влажности почвы из крупной сети.
Примечание
Перед прочтением этого документа вам следует ознакомиться с теорией шардирования в CouchDB.
Традиционно документ в этой базе данных может иметь примерно такую структуру:
{
"_id": "sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf",
"_rev":"1-14e8f3262b42498dbd5c672c9d461ff0",
"sensor_id": "sensor-260",
"location": [41.6171031, -93.7705674],
"field_name": "Bob's Corn Field #5",
"readings": [
["2019-01-21T00:00:00", 0.15],
["2019-01-21T06:00:00", 0.14],
["2019-01-21T12:00:00", 0.16],
["2019-01-21T18:00:00", 0.11]
]
} Примечание
Хотя в этом примере используются датчики IoT, главное, на что следует обратить внимание, — это логическая группировка документов. В подобных сценариях документы могут группироваться по пользователям, а научные данные — по экспериментам.
Итак, у нас есть множество датчиков, сгруппированных по полям, которые они отслеживают, а также их показания за определённый день (или другой подходящий период времени).
Помимо документов, для запросов к базе данных нам могут понадобиться два вторичных индекса, примерно такого вида:
function(doc) {
if(doc._id.indexOf("sensor-reading-") != 0) {
return;
}
for(var r in doc.readings) {
emit([doc.sensor_id, r[0]], r[1])
}
} и:
function(doc) {
if(doc._id.indexOf("sensor-reading-") != 0) {
return;
}
emit(doc.field_name, doc.sensor_id)
} Определив эти два индекса, мы можем легко найти все показания для заданного датчика или перечислить все датчики в заданном поле.
К сожалению, в CouchDB при чтении по любому из этих индексов необходимо найти копию каждого шарда и запросить документы, относящиеся к определённому датчику или полю. Это означает, что по мере увеличения числа шардов в базе данных для каждого запроса к индексу требуется выполнять больше работы. Это излишне, поскольку нас интересует лишь небольшое количество документов. К счастью для вас, дорогой читатель, секционированные базы данных были созданы именно для решения этой проблемы.
Что такое секция?
В предыдущем разделе мы представили гипотетическую базу данных с показаниями датчиков из сервиса мониторинга полей с помощью IoT. В этом конкретном случае вполне логично сгруппировать все документы по полю sensor_id. В таком случае sensor_id будет ключом секционирования.
Хорошая секция обладает двумя основными свойствами. Во-первых, она должна иметь высокую кардинальность. То есть в большой секционированной базе данных секций должно быть намного больше, чем документов в любой отдельной секции. База данных с одной секцией будет антипаттерном для этой функции. Во-вторых, объём данных в каждой секции должен быть «небольшим». Обычно рекомендуется ограничивать размер отдельных секций десятью гигабайтами (10 ГБ). Для документов с показаниями датчиков из нашего примера это соответствует примерно 60 000 лет данных.
Примечание
Параметр max_partition_size в CouchDB задаёт ограничение на размер секции. Значение этого параметра по умолчанию — 10 ГиБ, но его можно изменить. Если задать для этого параметра значение 0, ограничение на размер секции будет отключено.
Зачем использовать секции?
Главное преимущество секционированных баз данных — производительность секционированных запросов. В больших базах данных с множеством документов часто встречаются группы связанных документов, к которым обращаются вместе.
С помощью секций мы можем эффективнее выполнять запросы к отдельным группам документов, размещая всю группу в определённом шарде на диске. Таким образом, при выполнении запроса движку представлений нужно обратиться только к одной копии заданного диапазона шардов, а не выполнять запрос по всем шардам q базы данных. Это означает, что вам не нужно ждать ответа от всех шардов q, что повышает эффективность и скорость работы.
Секции на примерах
Чтобы создать секционированную базу данных, достаточно передать параметр строки запроса:
shell> curl -X PUT 'http://adm:pass@127.0.0.1:5984/my_new_db?partitioned=true'
{"ok":true} Убедиться, что наша база данных секционирована, можно, просмотрев информацию о ней:
shell> curl http://adm:pass@127.0.0.1:5984/my_new_db
{
"cluster": {
"n": 3,
"q": 8,
"r": 2,
"w": 2
},
"compact_running": false,
"db_name": "my_new_db",
"disk_format_version": 7,
"doc_count": 0,
"doc_del_count": 0,
"instance_start_time": "0",
"props": {
"partitioned": true
},
"purge_seq": "0-g1AAAAFDeJzLYWBg4M...",
"sizes": {
"active": 0,
"external": 0,
"file": 66784
},
"update_seq": "0-g1AAAAFDeJzLYWBg4M..."
} Теперь вы увидите, что элемент "props" содержит "partitioned": true.
Примечание
Каждый документ в секционированной базе данных (за исключением документов _design и _local) должен иметь формат «partition:docid». Точнее говоря, секцией для данного документа является всё, что находится до первого двоеточия. Идентификатор документа — это всё, что находится после первого двоеточия; он может содержать и другие двоеточия.
Примечание
Системные базы данных (например, _users) нельзя секционировать. Это связано с тем, что у системных баз данных уже есть собственные несовместимые требования к идентификаторам документов.
Теперь, когда мы создали секционированную базу данных, пора добавить в неё документы. Используя наш предыдущий пример, можно сделать это следующим образом:
shell> cat doc.json
{
"_id": "sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf",
"sensor_id": "sensor-260",
"location": [41.6171031, -93.7705674],
"field_name": "Bob's Corn Field #5",
"readings": [
["2019-01-21T00:00:00", 0.15],
["2019-01-21T06:00:00", 0.14],
["2019-01-21T12:00:00", 0.16],
["2019-01-21T18:00:00", 0.11]
]
}
shell> $ curl -X POST -H "Content-Type: application/json" \
http://adm:pass@127.0.0.1:5984/my_new_db -d @doc.json
{
"ok": true,
"id": "sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf",
"rev": "1-05ed6f7abf84250e213fcb847387f6f5"
} Единственное изменение в первом примере документа состоит в том, что теперь мы добавляем имя секции к идентификатору документа: оно ставится перед прежним идентификатором и отделяется от него двоеточием.
Примечание
Имя секции в идентификаторе документа не обладает особыми свойствами. Внутри базы данных для хеширования документа и определения его шарда используется только секция, а не весь идентификатор документа.
Работа с документами в секционированной базе данных ничем не отличается от работы с документами в несекционированной базе данных. Доступны все API, а существующий клиентский код будет работать без изменений.
Теперь, когда мы создали документ, можно получить некоторую информацию о секции, в которой он находится:
shell> curl http://adm:pass@127.0.0.1:5984/my_new_db/_partition/sensor-260
{
"db_name": "my_new_db",
"doc_count": 1,
"doc_del_count": 0,
"partition": "sensor-260",
"sizes": {
"active": 244,
"external": 347
}
} Также можно перечислить все документы в секции:
shell> curl http://adm:pass@127.0.0.1:5984/my_new_db/_partition/sensor-260/_all_docs
{"total_rows": 1, "offset": 0, "rows":[
{
"id":"sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf",
"key":"sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf",
"value": {"rev": "1-05ed6f7abf84250e213fcb847387f6f5"}
}
]} Обратите внимание, что можно использовать все стандартные возможности запросов _all_docs. Обращение к _all_docs через конечную точку /dbname/_partition/name/_all_docs — в основном лишь удобный способ гарантировать, что запросы ограничены заданной секцией. Пользователи могут использовать обычный /dbname/_all_docs для чтения документов из нескольких секций. Производительность обоих типов запросов одинакова.
Далее мы создадим документ проектирования с индексом, позволяющим получить все показания заданного датчика. Функция map похожа на ту, что использовалась в предыдущем примере, за исключением того, что теперь она учитывает изменение формата идентификатора документа.
function(doc) {
if(doc._id.indexOf(":sensor-reading-") < 0) {
return;
}
for(var r in doc.readings) {
emit([doc.sensor_id, r[0]], r[1])
}
} Загрузив документ проектирования, мы можем попробовать выполнить секционированный запрос:
shell> cat ddoc.json
{
"_id": "_design/sensor-readings",
"views": {
"by_sensor": {
"map": "function(doc) { ... }"
}
}
}
shell> $ curl -X POST -H "Content-Type: application/json" http://adm:pass@127.0.0.1:5984/my_new_db -d @ddoc.json
{
"ok": true,
"id": "_design/sensor-readings",
"rev": "1-13859808da293bd72fde3b31be97372a"
}
shell> curl http://adm:pass@127.0.0.1:5984/my_new_db/_partition/sensor-260/_design/sensor-readings/_view/by_sensor
{"total_rows":4,"offset":0,"rows":[
{"id":"sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf","key":["sensor-260","0"],"value":null},
{"id":"sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf","key":["sensor-260","1"],"value":null},
{"id":"sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf","key":["sensor-260","2"],"value":null},
{"id":"sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf","key":["sensor-260","3"],"value":null}
]} Ура! Наш первый секционированный запрос. Опытных пользователей это может не особенно впечатлить: изменились лишь формат идентификатора документа и путь для обращения к представлениям. Однако для тех, кто ценит повышение производительности, это действительно важно. Поскольку известно, что результаты представления находятся в указанной секции, секционированные запросы теперь выполняются почти так же быстро, как поиск документов!
Последнее, что мы рассмотрим, — как запрашивать данные из нескольких секций. Для этого реализуем запрос из первоначального примера, который выбирает датчики по полю. Функция map будет учитывать новый формат идентификатора документа, но в остальном останется такой же, как и предыдущая версия:
function(doc) {
if(doc._id.indexOf(":sensor-reading-") < 0) {
return;
}
emit(doc.field_name, doc.sensor_id)
} Затем мы создадим новый документ проектирования с этой функцией. Обратите внимание, что элемент "options" содержит "partitioned": false.
shell> cat ddoc2.json
{
"_id": "_design/all_sensors",
"options": {
"partitioned": false
},
"views": {
"by_field": {
"map": "function(doc) { ... }"
}
}
}
shell> $ curl -X POST -H "Content-Type: application/json" http://adm:pass@127.0.0.1:5984/my_new_db -d @ddoc2.json
{
"ok": true,
"id": "_design/all_sensors",
"rev": "1-4a8188d80fab277fccf57bdd7154dec1"
} Примечание
По умолчанию документы проектирования в секционированной базе данных являются секционированными. Документы проектирования, содержащие представления для запросов по нескольким секциям, должны содержать элемент "partitioned": false в объекте "options".
Примечание
Документы проектирования бывают секционированными или глобальными. Они не могут одновременно содержать секционированные и глобальные индексы.
Чтобы получить ответ со списком всех датчиков в поле, можно отправить запрос следующего вида:
shell> curl -u adm:pass http://adm:pass@127.0.0.1:15984/my_new_db/_design/all_sensors/_view/by_field
{"total_rows":1,"offset":0,"rows":[
{"id":"sensor-260:sensor-reading-ca33c748-2d2c-4ed1-8abf-1bca4d9d03cf","key":"Bob's Corn Field #5","value":"sensor-260"}
]} Обратите внимание, что для глобальных запросов мы не используем путь /dbname/_partition/.... Это связано с тем, что глобальные запросы по определению не ограничиваются отдельными секциями. За исключением наличия параметра "partitioned": false в документе проектирования, глобальные документы проектирования и запросы ведут себя так же, как документы проектирования в несекционированных базах данных.
Предупреждение
Иными словами, глобальные запросы выполняются так же, как запросы к несекционированным базам данных. Повышение производительности обеспечивают только секционированные запросы к секционированной базе данных.
Copyright © 2025 The Apache Software Foundation — Licensed under the Apache License 2.0
https://docs.couchdb.org/en/3.5.1/partitioned-dbs/index.html