База данных состояний на основе CouchDB

Варианты баз данных состояний

На данный момент базы данных глобальных состояний одноранговых узлов могут быть реализованы на основе LevelDB и CouchDB. По умолчанию для базы данных состояний используется LevelDB, которая встроена в процессы одноранговых узлов. CouchDB — это альтернативная внешняя база данных состояний. Как и хранилище пар «ключ-значение» LevelDB, база данных CouchDB может хранить любые двоичные данные, моделируемые в чейнкоде (для данных в формате, отличном от JSON, в CouchDB используются вложения). При использовании в качестве хранилища объектов типа документ, CouchDB позволяет хранить данные в формате JSON, выполнять расширенные запросы и использовать индексы в запросах.

LevelDB и CouchDB поддерживают стандартные операции чейнкода, такие как получение и установка значения ключа (актива), а также запросы на основе ключей. Ключи можно запрашивать по диапазону, причем составные ключи можно моделировать для возможности осуществления эквивалентных запросов по нескольким параметрам. Например, составной ключ owner,asset_id можно использовать запроса всех активов, принадлежащих определенному объекту. Такие запросы на основе ключей можно использовать как для чтения данных реестра, так и в транзакциях, которые обновляют реестр.

Моделирование данных в формате JSON позволяет выполнять расширенные запросы по значениям данных, а не только по ключам. Это облегчает чтение данных, хранящихся в реестре блокчейна, приложениями и чейнкодом. CouchDB позволяет обеспечить соответствие требованиям аудитов и отчетности для вариантов использования, в которых LevelDB не отвечает эти требованиям. При использовании CouchDB и моделировании данных в JSON, также можно развертывать индексы вместе с чейнкодом. Индексы повышают гибкость и эффективность запросов, позволяя обрабатывать большие наборы данных из чейнкода.

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

Примечание

Ключ документа JSON в CouchDB может содержать только символы в кодировке UTF-8 и не может начинаться с символа подчеркивания («_»). Как в случае с CouchDB, так и в LevelDB, следует избегать использования символа U+0000 (нулевой байт) в ключах.

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

  • Любое поле, начинающееся с символа подчеркивания, "_"
  • ~version

Из-за несовместимости данных между LevelDB и CouchDB выбор базы данных должен быть сделан до развертывания промышленной сети. База данных не может быть преобразована позднее.

Использование CouchDB из чейнкода

Запросы чейнкода

Большинство функций API оболочки чейнкода можно использовать с базами данных состояний LevelDB или CouchDB, например, GetState, PutState, GetStateByRange, GetStateByPartialCompositeKey. Также, при использовании CouchDB в качестве базы данных состояний и активов в формате JSON для моделирования в чейнкоде, расширенные запросы в формате JSON к базе данных состояний можно выполнять с помощью функции API GetQueryResult, передавая в нее строку запроса CouchDB. Строка запроса соответствует синтаксису запроса CouchDB в формате JSON.

В примере чейнкода marbles02 демонстрируется использование запросов CouchDB. С помощью функции queryMarblesByOwner() осуществляются параметризованные запросы с передачей идентификатора владельца в чейнкод. Далее из базы данных состояний запрашиваются документы JSON, соответствующие типу “marble“ и указанному идентификатору владельца, с помощью синтаксиса запросов в формате JSON.

{"selector":{"docType":"marble","owner":<OWNER_ID>}}

Результаты расширенных запросов полезны для получения подробной информации о данных, хранящихся в реестре. Однако нет гарантии, что результаты расширенных запросов будут оставаться неизменными между выполнением чейнкода и моментом записи. То есть не следует использовать расширенный запрос и обновлять реестр канала в одной транзакции. Например, при выполнении расширенного запроса для всех активов владельца Alice и передаче их владельцу Bob, новый актив может быть передан Alice другой транзакцией в промежутке между моментами выполнения и записи чейнкода.

Разбиение на страницы результатов запросов CouchDB

Fabric поддерживает разбиение на страницы результатов расширенных запросов и запросов по диапазону. Функции API разбиения на страницы позволяют указывать размер страницы и закладки как для запросов по диапазону, так и для расширенных запросов. Для поддержки эффективного разбиения на страницы необходимо использовать соответствующие функции API Fabric. В частности, ключевое слово CouchDB limit не учитываться в запросах CouchDB, поскольку Fabric управляет разбивкой результатов запроса на страницы и неявно устанавливает ограничение pageSize, которое передается в CouchDB.

Если pageSize указан с помощью API разбиения на страницы (GetStateByRangeWithPagination(), GetStateByPartialCompositeKeyWithPagination() и GetQueryResultWithPagination()), в чейнкод возвращается набор результатов (привязанный к pageSize) вместе с закладкой. Закладка может быть возвращена из чейнкода вызывающим клиентам, которые могут использовать закладку в последующем запросе для получения следующей «страницы» результатов.

Функции API разбиения на страницы предназначены для использования только в транзакциях чтения, а результаты запросов предназначены для поддержки требований клиентов к разбиению на страницы. Для транзакций, в которых необходимо производить чтение и запись, следует использовать API запросов чейнкода без разбивки на страницы. Из чейнкода можно перебирать результаты до желаемой позиции.

Независимо от использования функций API разбивки на страницы, размеры всех запросов чейнкода ограничиваются параметром totalQueryLimit (по умолчанию имеет значение 100000) в файле core.yaml. Это максимальное количество результатов, которые чейнкод может обработать и возвратить клиенту. Такое ограничение введено, чтобы избежать случайных или злонамеренно длительных запросов.

Примечание

Независимо от того, какие используются запросы, обычные или с разбиением на страницы, одноранговый узел будет запрашивать данные из CouchDB пакетами, размер которых ограничен параметром internalQueryLimit (по умолчанию 1000) в файле core.yaml. Такой механизм обеспечивает передачу результатов запросов между одноранговым узлом и CouchDB пакетами разумных размеров и является прозрачным для чейнкода и вызывающего клиента.

Пример использования разбиения на страницы приводится в разделе руководства Использование базы данных CouchDB.

Индексы в CouchDB

Индексы в CouchDB повышают эффективность запросов в формате JSON, а также необходимы для любых запросов в формате JSON с сортировкой. Индексы позволяют запрашивать данные из чейнкода при наличии большого объема данных в реестре. Индексы можно упаковывать вместе с чейнкодом в каталоге /META-INF/statedb/couchdb/indexes. Индексы определяются в отдельных текстовых файлах с расширением *.json, причем определение индекса должно быть в формате JSON согласно синтаксису индексов в формате JSON для CouchDB. Например, для осуществления приведенного выше запроса объектов marble используется индекс для полей docType и owner:

{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"}

Пример индекса можно найти здесь.

Все индексы в каталоге чейнкода META-INF/statedb/couchdb/indexes упаковываются вместе с чейнкодом для развертывания. Индексы развертываются в канале однорангового узла и базе данных чейнкода, когда пакет чейнкода устанавливается на этот узел и определение чейнкода записывается в канал. При установке чейнкода, а затем записи определения чейнкода в канале, индексы развертываются во время записи чейнкода. Если чейнкод уже определен в канале, а пакет чейнкода впоследствии установлен на узле, присоединенном к каналу, индексы развертываются во время установки чейнкода.

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

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

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

Конфигурация CouchDB

Для использования CouchDB в качестве базы данных состояний следует изменить параметр stateDatabase конфигурации с goleveldb на CouchDB. Кроме того, в параметре couchDBAddress должна указываться база данных CouchDB, которая будет использоваться одноранговым узлом. Если доступ к базе данных CouchDB ограничен, необходимо указать имя пользователя и пароль администратора в соответствующих параметрах. Дополнительные параметры находятся в разделе couchDBConfig. Описание этих параметров приводится в комментариях непосредственно в файле конфигурации. Изменения в файле core.yaml вступают в силу сразу после перезапуска однорангового узла.

Также можно передать переменные среды Docker, например, CORE_LEDGER_STATE_STATEDATABASE и CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS, чтобы переопределить соответствующие значения в файле core.yaml.

Ниже приведен раздел stateDatabase файла core.yaml:

state:
  # stateDatabase - options are "goleveldb", "CouchDB"
  # goleveldb - default state database stored in goleveldb.
  # CouchDB - store state database in CouchDB
  stateDatabase: goleveldb
  # Limit on the number of records to return per query
  totalQueryLimit: 10000
  couchDBConfig:
     # It is recommended to run CouchDB on the same server as the peer, and
     # not map the CouchDB container port to a server port in docker-compose.
     # Otherwise proper security must be provided on the connection between
     # CouchDB client (on the peer) and server.
     couchDBAddress: couchdb:5984
     # This username must have read and write authority on CouchDB
     username:
     # The password is recommended to pass as an environment variable
     # during start up (e.g. LEDGER_COUCHDBCONFIG_PASSWORD).
     # If it is stored here, the file must be access control protected
     # to prevent unintended users from discovering the password.
     password:
     # Number of retries for CouchDB errors
     maxRetries: 3
     # Number of retries for CouchDB errors during peer startup
     maxRetriesOnStartup: 10
     # CouchDB request timeout (unit: duration, e.g. 20s)
     requestTimeout: 35s
     # Limit on the number of records per each CouchDB query
     # Note that chaincode queries are only bound by totalQueryLimit.
     # Internally the chaincode may execute multiple CouchDB queries,
     # each of size internalQueryLimit.
     internalQueryLimit: 1000
     # Limit on the number of records per CouchDB bulk update batch
     maxBatchUpdateSize: 1000
     # Warm indexes after every N blocks.
     # This option warms any indexes that have been
     # deployed to CouchDB after every N blocks.
     # A value of 1 will warm indexes after every block commit,
     # to ensure fast selector queries.
     # Increasing the value may improve write efficiency of peer and CouchDB,
     # but may degrade query response time.
     warmIndexesAfterNBlocks: 1

Для баз данных CouchDB, размещенных в контейнерах Docker, поставляемых с Hyperledger Fabric, существует возможность устанавливать имя пользователя и пароль CouchDB с помощью передаваемых переменных среды COUCHDB_USER и COUCHDB_PASSWORD в сценарии Docker Compose.

При установке CouchDB вне образов Docker, поставляемых с Fabric, для установки имени пользователя и пароля администратора необходимо отредактировать файл local.ini этой установки.

Сценарии Docker compose позволяют задать только имя пользователя и пароль при создании контейнера. Файл local.ini необходимо отредактировать, если необходимо изменить имя пользователя или пароль после создания контейнера.

При необходимости привязать порт контейнера fabric-couchdb к порту хоста, важно понимать о последствиях такого действия для безопасности. Привязка порта контейнера CouchDB в среде разработки предоставляет доступ к REST API CouchDB и позволяет визуализировать базу данных через веб-интерфейс CouchDB (Fauxton). В промышленной среде следует воздержаться от привязки порта хоста, чтобы ограничить доступ к контейнеру CouchDB. Тогда только одноранговый узел будет иметь возможность доступа к контейнеру CouchDB.

Примечание

Параметры CouchDB для однорангового узла считываются при каждом запуске однорангового узла.

Рекомендации по использованию запросов

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

  • При использовании запросов JSON:

    • Создавайте индексы в пакете чейнкода.
    • В запросах избегайте таких операторов, как $or, $in и $regex, которые приводят к полному сканированию базы данных.
  • При использовании запросов по диапазону, запросов по составным ключам и запросов в формате JSON:

    • Используйте функцию разбиения на страницы вместо одного большого набора результатов.
  • При необходимости создания панели аналитики и отчетности в рамках приложения рекомендуется осуществлять запросы к базе данных вне сети, которая реплицирует данные, хранящиеся на одноранговых узлах. Это позволит запросить и проанализировать данные блокчейн с помощью оптимизированной для этих целей базы данных без ухудшения производительности сети или нарушения выполнения транзакций. Для этого можно использовать события блока или чейнкода в приложении для записи данных транзакции в базу данных вне сети или передачи данных на аналитику. При получении блока приложение-обработчик блоков просматривает транзакции блоков и добавляет запись в виде «ключ-значение» в хранилище для каждого действительного набора rwset транзакции. Служба Службы событий одноранговых узлов в каналах предоставляет воспроизводимые события для обеспечения целостности данных в последующих хранилищах.