В течение последнего месяца я активно работал над экспериментальными проектами, связанными с CouchDB, изучая его возможности и готовясь к будущим задачам. За это время я несколько раз просматривал документацию CouchDB, чтобы убедиться, что я понимаю, как все работает. Читая документацию, я наткнулся на заявление о том, что, несмотря на то, что CouchDB поставляется с сервером запросов по умолчанию, написанным на JavaScript, создание собственной реализации относительно просто, и специальные решения уже существуют в природе.
Я провел небольшое исследование и нашел реализации, написанные на Python, Ruby или Clojure. Поскольку вся реализация не показалась мне слишком длинной, я решил поэкспериментировать с CouchDB, попытавшись написать собственный сервер запросов. Для этого я выбрал в качестве языка Go. Раньше у меня не было большого опыта работы с этим языком, за исключением использования шаблонов Go в чартах Helm, но мне хотелось попробовать что-то новое, и я подумал, что этот проект станет для этого отличной возможностью.
Прежде чем приступить к работе, я еще раз просмотрел документацию CouchDB, чтобы понять, как на самом деле работает сервер запросов. Согласно документации, общий обзор Query Server довольно прост:
Сервер запросов — это внешний процесс, который взаимодействует с CouchDB через протокол JSON через интерфейс stdio и обрабатывает все вызовы функций проектирования […].
Структура команд, отправляемых CouchDB на сервер запросов, может быть выражена как [, ] или ["ddoc",
Итак, по сути, мне нужно было написать приложение, способное анализировать этот тип JSON из STDIO, выполнять ожидаемые операции и возвращать ответы, как указано в документации. Для обработки широкого спектра команд в коде Go потребовалось много приведения типов. Подробную информацию о каждой команде можно найти в разделе документации «Протокол сервера запросов».
Одна из проблем, с которой я столкнулся здесь, заключалась в том, что сервер запросов должен иметь возможность интерпретировать и выполнять произвольный код, представленный в проектной документации. Зная, что Go — компилируемый язык, я ожидал, что застряну на этом этапе. К счастью, я быстро нашел пакет Yeagi, который позволяет легко интерпретировать код Go. Он позволяет создать песочницу и контролировать доступ к тому, какие пакеты можно импортировать в интерпретируемый код. В моем случае я решил предоставить доступ только к моему пакету под названием «couchgo», но можно легко добавить и другие стандартные пакеты.
В результате моей работы появилось приложение CouchGO! появился. Хотя он следует протоколу сервера запросов, он не является однозначной перереализацией версии JavaScript, поскольку имеет свои собственные подходы к обработке функций проектного документа.
Например, в CouchGO! нет вспомогательной функции, такой как испускание. Чтобы выдать значения, вы просто возвращаете их из функции карты. Кроме того, каждая функция в проектном документе следует одному и тому же шаблону: она имеет только один аргумент, который является объектом, содержащим свойства, специфичные для функции, и в результате должна возвращать только одно значение. Это значение не обязательно должно быть примитивным; в зависимости от функции это может быть объект, карта или даже ошибка.
Чтобы начать работать с CouchGO!, вам просто нужно загрузить исполняемый двоичный файл из моего репозитория GitHub, поместить его где-нибудь в экземпляре CouchDB и добавить переменную среды, которая позволит CouchDB запускать CouchGO! процесс.
Например, если вы поместите исполняемый файл Couchgo в каталог /opt/couchdb/bin, вам нужно добавить следующую переменную среды, чтобы он работал.
export COUCHDB_QUERY_SERVER_GO="/opt/couchdb/bin/couchgo"
Чтобы быстро понять, как писать функции с помощью CouchGO!, давайте рассмотрим следующий интерфейс функций:
func Func(args couchgo.FuncInput) couchgo.FuncOutput { ... }
Каждая функция в CouchGO! будет следовать этому шаблону, где Func заменяется соответствующим именем функции. В настоящее время CouchGO! поддерживает следующие типы функций:
Давайте рассмотрим пример дизайнерского документа, в котором указано представление с функциями карты и сокращения, а также функцией validate_doc_update. Кроме того, нам нужно указать, что мы используем Go в качестве языка.
{ "_id": "_design/ddoc-go", "views": { "view": { "map": "func Map(args couchgo.MapInput) couchgo.MapOutput {\n\tout := couchgo.MapOutput{}\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 1})\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 2})\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 3})\n\t\n\treturn out\n}", "reduce": "func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput {\n\tout := 0.0\n\n\tfor _, value := range args.Values {\n\t\tout = value.(float64)\n\t}\n\n\treturn out\n}" } }, "validate_doc_update": "func Validate(args couchgo.ValidateInput) couchgo.ValidateOutput {\n\tif args.NewDoc[\"type\"] == \"post\" {\n\t\tif args.NewDoc[\"title\"] == nil || args.NewDoc[\"content\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Title and content are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif args.NewDoc[\"type\"] == \"comment\" {\n\t\tif args.NewDoc[\"post\"] == nil || args.NewDoc[\"author\"] == nil || args.NewDoc[\"content\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Post, author, and content are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif args.NewDoc[\"type\"] == \"user\" {\n\t\tif args.NewDoc[\"username\"] == nil || args.NewDoc[\"email\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Username and email are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn couchgo.ForbiddenError{Message: \"Invalid document type\"}\n}", "language": "go" }
Теперь давайте разберем каждую функцию, начиная с функции карты:
func Map(args couchgo.MapInput) couchgo.MapOutput { out := couchgo.MapOutput{} out = append(out, [2]interface{}{args.Doc["_id"], 1}) out = append(out, [2]interface{}{args.Doc["_id"], 2}) out = append(out, [2]interface{}{args.Doc["_id"], 3}) return out }
В CouchGO! нет функции излучения; вместо этого вы возвращаете фрагмент кортежей «ключ-значение», где и ключ, и значение могут быть любого типа. Объект документа не передается функции напрямую, как в JavaScript; скорее, он завернут в объект. Сам документ представляет собой просто хеш-карту различных значений.
Далее давайте рассмотрим функцию сокращения:
func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput { out := 0.0 for _, value := range args.Values { out = value.(float64) } return out }
Подобно JavaScript, функция уменьшения в CouchGO! принимает ключи, значения и параметр rereduce, заключенные в один объект. Эта функция должна возвращать одно значение любого типа, представляющее результат операции сокращения.
Наконец, давайте посмотрим на функцию Validate, которая соответствует свойству validate_doc_update:
func Validate(args couchgo.ValidateInput) couchgo.ValidateOutput { if args.NewDoc["type"] == "post" { if args.NewDoc["title"] == nil || args.NewDoc["content"] == nil { return couchgo.ForbiddenError{Message: "Title and content are required"} } return nil } if args.NewDoc["type"] == "comment" { if args.NewDoc["post"] == nil || args.NewDoc["author"] == nil || args.NewDoc["content"] == nil { return couchgo.ForbiddenError{Message: "Post, author, and content are required"} } return nil } return nil }
В этой функции мы получаем такие параметры, как новый документ, старый документ, пользовательский контекст и объект безопасности, все они заключены в один объект, передаваемый в качестве аргумента функции. Здесь мы должны проверить, можно ли обновить документ, и вернуть ошибку, если нет. Как и в версии JavaScript, мы можем возвращать два типа ошибок: ForbiddenError или UnauthorizedError. Если документ можно обновить, мы должны вернуть ноль.
Более подробные примеры можно найти в моем репозитории на GitHub. Важно отметить, что имена функций не являются произвольными; они всегда должны соответствовать типу функции, которую они представляют, например Map, уменьшить, фильтровать и т. д.
Хотя написание собственного сервера запросов было действительно увлекательным занятием, в этом не было бы особого смысла, если бы я не сравнил его с существующими решениями. Итак, я подготовил несколько простых тестов в Docker-контейнере, чтобы проверить, насколько быстрее CouchGO! может:
Я заполнил базу данных ожидаемым количеством документов и измерил время ответа или журналы с дифференцированными метками времени из контейнера Docker, используя специальные сценарии оболочки. Подробности реализации можно найти в моем репозитории GitHub. Результаты представлены в таблице ниже.
Тест | Диван, ВПЕРЕД! | CouchJS | Способствовать росту |
---|---|---|---|
Индексирование | 141,713 с | 421,529 с | 2,97x |
Сокращение | 7672 мс | 15642 мс | 2,04x |
Фильтрация | 28,928 с | 80,594 с | 2,79x |
Обновление | 7,742 с | 9,661 с | 1,25x |
Как видите, прирост по сравнению с реализацией JavaScript значителен: почти в три раза быстрее в случае индексации, более чем в два раза быстрее для функций сокращения и фильтрации. Прирост относительно невелик для функций обновления, но все же быстрее, чем в JavaScript.
Как и обещал автор документации, написать собственный сервер запросов не так уж сложно, если следовать протоколу сервера запросов. Несмотря на то, что CouchGO! в целом отсутствует несколько устаревших функций, он обеспечивает значительный прирост по сравнению с версией JavaScript даже на этой ранней стадии разработки. Я считаю, что еще есть много возможностей для улучшений.
Если вам нужен весь код из этой статьи в одном месте, вы можете найти его в моем репозитории GitHub.
Спасибо, что прочитали эту статью. Я хотел бы услышать ваши мысли об этом решении. Вы бы использовали его со своим экземпляром CouchDB или, может быть, вы уже используете какой-то собственный сервер запросов? Буду рад услышать об этом в комментариях.
Не забудьте прочитать другие мои статьи, чтобы узнать больше советов, идей и других частей этой серии по мере их создания. Удачного взлома!
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3