CouchDB 2.0: краткий обзор и мои первые впечатления
26 сентября 2016
CouchDB (не путать с Couchbase!) — документо-ориентированная СУБД, написанная на языке Erlang. В вышедшей недавно версии 2.0 в CouchDB добавили Riak-подобное шардирование, что наконец-то делает эту СУБД кому-то интересной. К тому же, в отличие от Riak, в CouchDB всегда была совершенно бесплатная (не в enterprise edition версии и т.д.) кросс-датацентровая master-slave и master-master репликация. Давайте попробуем разобраться, как же этим хозяйством вообще пользоваться, и подходит ли оно уже для продакшена.
Установка
Для своих страшных экспериментов я использовал Ubuntu 16.04. Готовых пакетов для различных дистрибутивов Linux разработчики CouchDB не предоставляют, поэтому имеет смысл один раз настроить его в Docker или LXC-контейнере, и потом использовать этот контейнер на всех узлах кластера. Для быстрой установки CouchDB мной был написан bash-скрипт. Имеет смысл сразу воспользоваться им. В ближайших двух параграфах я расскажу, как этот скрипт работает.
Ставим зависимости:
sudo apt-get --no-install-recommends -y install \
build-essential pkg-config runit erlang \
libicu-dev libmozjs185-dev libcurl4-openssl-dev
Качаем архив с исходниками с зеркала, которое предложат на официальном сайте. Затем говрим:
cd apache-couchdb-2.0.0/
./configure && make release
Создаем пользователя couchdb:
--no-create-home \
--shell /bin/bash \
--group --gecos \
"CouchDB Administrator" couchdb
Копируем rel/couchdb в домашний каталог пользователя и выставляем правильные права:
sudo chown -R couchdb:couchdb /home/couchdb
sudo find /home/couchdb -type d -exec chmod 0770 {} \;
sudo sh -c 'chmod 0644 /home/couchdb/etc/*'
Проверяем, что все было сделано правильно:
У меня в логи стали сыпаться ворнинги про отсутствующую базу данных _users. Не переживайте, мы это вот-вот пофиксим.
На http://localhost:5984 должны увидеть:
"couchdb": "Welcome",
"version": "2.0.0",
"vendor": {
"name": "The Apache Software Foundation"
}
}
Заметьте, что все общение клиентов с CouchDB происходит по REST API (иногда не REST, а просто какому-то кастомному HTTP API), со всеми вытекающими отсюда преимуществами и недостатками.
На http://localhost:5984/_utils/ увидим красивый веб-интерфейс вроде такого:
В веб-интерфейсе открываем Setup и создаем кластер из одной ноды (там все просто). После этого в логи перестанут сыпаться какие-либо ошибки.
Автозапуск через runit
Для автоматического запуска CouchDB при запуске системы разработчики рекомендуют использовать runit (как подсказал мне @listochkin, читается run-it, а не r-unit :).
Создаем директорию, куда будут писаться логи:
sudo chown couchdb:couchdb /var/log/couchdb
Создаем директории с конфигурацией для запуска couchdb:
sudo mkdir /etc/sv/couchdb/log
В /etc/sv/couchdb/log/run пишем:
exec svlogd -tt /var/log/couchdb
Опционально можно создать /etc/sv/couchdb/log/config c указанием, когда производить ротацию логов и подобного рода вешей, если дэфолты не устраивают. Формат конфига, а также дэфолты описаны в man svlogd
.
Также создаем скрипт /etc/sv/couchdb/run:
export HOME=/home/couchdb
exec 2>&1
exec chpst -u couchdb /home/couchdb/bin/couchdb
Делаем обоим скриптам chmod u+x
. Затем:
Через несколько секунд runit должен заметить новый сервис и запустить его. Управление:
sudo sv stop couchdb
sudo sv start couchdb
Можно проверить, что после рестарта системы CouchDB запускается автоматически.
Объединение нод в кластер
Важно! Максимальный размер кластера в CouchDB по умолчанию составляет 8 нод. Чтобы изменить это, нужно отредактировать значение параметра q в секции [cluster]
в etc/local.ini. Подробности см здесь.
Поднимаем три совершенно ненастроенные ноды. Если нода уже настроена, например, вы создали кластер в диалоге Setup, вот вам bash-скрипт, который все это откатывает.
Шаг 1. В /home/couchdb/etc/vm.args меняем параметры name и cookie. Кука должна быть супер секретной (pwgen 16
) и одинаковой на всех нодах кластера; имена нод должны быть уникальны:
-setcookie eY2chohl4siecaib
Примечание: Узнать больше про распределенный Erlang и его куки можно из заметки Мои страшные эксперименты с Erlang и gen_server.
На всех нодах говорим:
Смотрим список известных нод:
Пример ответа:
"all_nodes": [
"couchdb@10.110.2.4"
],
"cluster_nodes": [
"couchdb@10.110.2.4",
"couchdb@localhost"
]
}
Шаг 2. На каждой ноде указываем имя пользователя и пароль, а также какой IP-адрес должна слушать нода:
localhost:5984/_node/couchdb@10.110.2.4/_config/admins/admin \
-d '"password"'
curl -X PUT \
localhost:5984/_node/couchdb@10.110.2.4/_config/chttpd/bind_address \
-d '"0.0.0.0"' --user admin
Шаг 3. Объединяем ноды в кластер. Для этого на одной ноде для кадой ноды выполняем:
-H "Content-Type: application/json" \
http://localhost:5984/_cluster_setup \
-d '{"action":"enable_cluster","bind_address":"0.0.0.0","username'\
'":"admin","password":"password","port":5984,"remote_node":"10.'\
'110.2.5","remote_current_user":"admin","remote_current_password":'\
'"password" }' \
--user admin
curl -X POST \
-H "Content-Type: application/json" \
http://localhost:5984/_cluster_setup \
-d '{"action": "add_node", "host":"10.110.2.5", "port": "5984", '\
'"username": "admin", "password":"password"}' \
--user admin
По окончании добавления нод говорим:
-H "Content-Type: application/json" \
http://localhost:5984/_cluster_setup \
-d '{"action": "finish_cluster"}' \
--user admin
Проверяем, что ноды действительно объединились в кластер:
Список all_nodes — это ноды, которые сейчас онлайн, cluster_nodes — это вообще все существующие ноды.
Работа с документами
Итак, у нас есть настроенный кластер CouchDB. Попробуем сделать с ним что-нибудь сравнительно полезное.
Создаем пользователя для доступа к БД:
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "afiskon", "password": "secret", "roles": [],'\
' "type": "user"}'
Заметьте, что пользователей в CouchDB может создавать кто угодно, как это делается на веб-сайтах. Тут пишут, что это такая фича.
Проверяем, что пользователь действительно создался:
-d 'name=afiskon&password=secret'
Ответ:
Под админом создаем базу contacts (по аналогии с базой из заметки Пишем простой RESTful сервис с использованием Scotty):
Даем права для работы с этой базой пользователю afiskon:
http://localhost:5984/contacts/_security \
-H "Content-Type: application/json" \
-d '{"admins": { "names": [], "roles": [] }, "members": '\
'{ "names": ["afiskon"], "roles": [] } }' \
--user admin
Проверяем, что пользователь теперь имеет доступ к базе:
В ответ приходит довольно большой документ с информацией о количестве документов в базе, сколько места на диске она занимает, и тд.
Проверяем, что пользователь не может удалить базу:
Для этого пользователь должен быть среди admins базы, а не members.
Создаем документ:
-d '{"name":"Alex","phone":"123"}' --user afiskon
Ответ:
"ok":true,
"id":"contact_12345",
"rev":"1-85bda81e2e23351d07f0f9bbcb216032"
}
Читаем документ:
Ответ:
"_id":"contact_12345",
"_rev":"1-85bda81e2e23351d07f0f9bbcb216032",
"name":"Alex",
"phone":"123"
}
Обновление и удаление документа выглядит так. Вы читаете весь документ, меняете его, и затем шлете запрос с указанием _rev считанного документа. Это нужно для ситуаций, когда один документ одновременно меняется несколькими клиентами. В сущности это означает, что вы всегда делаете CAS. Напомню, что в тех же Riak и Cassandra это не так. Поддержка CAS в частности означает, что поверх CouchDB можно написать распределенные транзакции.
Пример обновления документа:
http://localhost:5984/contacts/contact_12345 \
-d '{"_rev":"1-85bda81e2e23351d07f0f9bbcb216032","name":"Alex",'\
'"phone":"456"}' \
--user afiskon
В случае конфликта получим ответ:
Удаление документа:
'http://localhost:5984/contacts/contact_12345'\
'?rev=2-972083a24122b6dc340e213ce50aa81e' \
--user afiskon
А еще можно получить id всех документов в базе:
Пример ответа:
{
"id":"contact_12345",
"key":"contact_12345",
"value":{"rev":"6-4f271d4a106be6c1286b71a861c66bf1"}
}
]}
В качестве домашнего задания можете провести такой эксперимент. Уроните одну ноду. Убедитесь, что документ можно обновить, используя только оставшиеся две ноды. Затем поднимите упавшую ноду и быстро-быстро запросите с нее документ. Убедитесь, что никакого конфликта не происходит и вы получаете последнюю ревизию.
Разумеется, рассмотреть весь API CouchDB в рамках одной статьи не представляется возможным. Подробности вы найдете в официальной документации. Там еще немало интересного про аттачи, репликацию и views, а также специальный API поиска безо всякого MapReduce.
Заключение
Несмотря на то, что в первом приближении все вроде как здорово и замечательно, тащить эту систему в продакшен я бы лично не спешил.
Во-первых, никто не знает, сколько в ней еще никем не обнаруженных багов. Никогда не забуду, как в свое время мы радостно протащили в продакшен Couchbase, а спустя какое-то время выяснили, что кластер в нем может развалиться и не собраться обратно.
Во-вторых, внимательно прочитав документацию, я выявил несколько неприятных моментов. Оказывается, документы на самом деле никогда до конца не удаляются, сколько компакшенов не запускай. То есть, со временем база будет только расти. Что мешает вычищать такие документы спустя, скажем, месяц после их удаления, мне неясно. Кроме того, автоматического решардинга в CouchDB на данный момент нет, делать его придется вручную с scp и прочими плясками с бубнами. Наконец, я не обнаружил возможности указывать документам TTL, а это просто мастхев, если вы хотите класть в CouchDB сессии, коды капч, и подобного рода вещи.
Из приятных моментов хочется отметить, что CouchDB действительно пытается не терять данные. Например, он делает fsync на все записи. В отличие от него тот же Couchbase по умолчанию старается держать все в памяти, сбрасывая данные на диск лишь изредка. Впрочем, в Couchbase есть репликация, так что, возможно, это и не такая уж большая проблема. Еще из интересного — максимальный размер документа в CouchDB по умолчанию равен 64 Мб и может быть увеличен при помощи параметра max_document_size. Для сравнения, в MongoDB и Couchbase захардкожено, что докуметов размером больше 16 и 20 Мб соответственно не бывает. Также CouchDB из коробки поддерживает сжатие документов при помощи snappy и zlib, а также подписку на обновление документов при помощи change feeds, push/pull replication и возможности вызывать указанные программы при изменении документов.
Наконец, сообщество разработчиков CouchDB — пожалуй, самое открытое и дружелюбное из всех, что мне до сих пор встречались. Мейнтейнеры проекта отвечают на любые вопросы в IRC и охотно принимают посылаемые пулл реквесты (см раз, два). Если вдруг вы ищите интересный open source проект для работы над ним в свободное время, лучше CouchDB вам будет сложно что-то найти.
Таковы мои впечатления от CouchDB. А вы что скажете?
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.