>백엔드 개발 >Golang >소파GO! — Go로 작성된 쿼리 서버로 CouchDB 강화

소파GO! — Go로 작성된 쿼리 서버로 CouchDB 강화

PHPz
PHPz원래의
2024-07-19 12:38:41594검색

CouchGO! — Enhancing CouchDB with Query Server Written in Go

지난 한 달 동안 CouchDB와 관련된 개념 증명 프로젝트를 적극적으로 진행하며 기능을 탐색하고 향후 작업을 준비했습니다. 이 기간 동안 나는 모든 것이 어떻게 작동하는지 이해하기 위해 CouchDB 문서를 여러 번 검토했습니다. 문서를 읽는 동안 JavaScript로 작성된 기본 쿼리 서버와 함께 CouchDB가 제공됨에도 불구하고 사용자 정의 구현을 만드는 것이 상대적으로 간단하며 사용자 정의 솔루션이 이미 존재한다는 진술을 발견했습니다.

몇 가지 빠른 조사를 통해 Python, Ruby 또는 Clojure로 작성된 구현을 찾았습니다. 전체 구현이 그리 길지 않아 보였기 때문에 나만의 사용자 정의 쿼리 서버를 작성하여 CouchDB를 실험해 보기로 결정했습니다. 이를 위해 언어로 Go를 선택했습니다. Helm의 차트에서 Go 템플릿을 사용하는 것 외에는 이 언어에 대한 경험이 많지 않았지만 새로운 것을 시도하고 싶었고 이번 프로젝트가 좋은 기회가 될 것이라고 생각했습니다.

쿼리 서버 이해

작업을 시작하기 전, Query Server가 실제로 어떻게 작동하는지 이해하기 위해 CouchDB 설명서를 한 번 더 살펴봤습니다. 문서에 따르면 쿼리 서버의 상위 수준 개요는 매우 간단합니다.

쿼리 서버는 stdio 인터페이스를 통해 JSON 프로토콜을 통해 CouchDB와 통신하고 모든 설계 함수 호출을 처리하는 외부 프로세스입니다[...].

CouchDB가 Query Server로 보내는 명령의 구조는 [, <*arguments>] 또는 ["ddoc", , [, < funcname>], [, , …]] 디자인 문서의 경우

그래서 기본적으로 제가 해야 했던 일은 STDIO에서 이러한 종류의 JSON을 구문 분석하고, 예상되는 작업을 수행하고, 문서에 지정된 대로 응답을 반환할 수 있는 애플리케이션을 작성하는 것이었습니다. Go 코드에서는 광범위한 명령을 처리하기 위해 많은 유형 캐스팅이 필요했습니다. 각 명령에 대한 구체적인 세부 정보는 설명서의 쿼리 서버 프로토콜 섹션에서 확인할 수 있습니다.

여기서 제가 직면한 한 가지 문제는 쿼리 서버가 디자인 문서에 제공된 임의의 코드를 해석하고 실행할 수 있어야 한다는 것이었습니다. Go가 컴파일된 언어라는 것을 알았기 때문에 이 시점에서 정체될 것으로 예상했습니다. 다행히 Go 코드를 쉽게 해석할 수 있는 Yeagi 패키지를 빨리 찾았습니다. 이를 통해 샌드박스를 생성하고 해석된 코드로 가져올 수 있는 패키지에 대한 액세스를 제어할 수 있습니다. 제 경우에는 Couchgo라는 패키지만 노출하기로 했지만, 다른 표준 패키지도 쉽게 추가할 수 있습니다.

CouchGO를 소개합니다!

작업의 결과 CouchGO!라는 애플리케이션이 탄생했습니다. 등장했다. Query Server Protocol을 따르지만 디자인 문서 기능을 처리하는 고유한 접근 방식이 있으므로 JavaScript 버전을 일대일로 다시 구현하는 것은 아닙니다.

예를 들어 CouchGO!에는 내보내기와 같은 도우미 기능이 없습니다. 값을 내보내려면 지도 함수에서 값을 반환하기만 하면 됩니다. 또한 디자인 문서의 각 함수는 동일한 패턴을 따릅니다. 즉, 함수별 속성을 포함하는 개체인 인수가 하나만 있고 결과로 하나의 값만 반환해야 합니다. 이 값은 기본 값일 필요는 없습니다. 기능에 따라 객체일 수도 있고, 지도일 수도 있고, 심지어 오류일 수도 있습니다.

CouchGO! 작업을 시작하려면 내 GitHub 저장소에서 실행 가능한 바이너리를 다운로드하여 CouchDB 인스턴스 어딘가에 배치하고 CouchDB가 CouchGO!를 시작할 수 있도록 하는 환경 변수를 추가하면 됩니다. 프로세스입니다.

예를 들어, Couchgo 실행 파일을 /opt/couchdb/bin 디렉토리에 배치하는 경우 다음 환경 변수를 추가하여 작동할 수 있습니다.

export COUCHDB_QUERY_SERVER_GO="/opt/couchdb/bin/couchgo"

CouchGO!로 함수 작성하기

CouchGO!로 함수를 작성하는 방법을 빠르게 이해하기 위해 다음 함수 인터페이스를 살펴보겠습니다.

func Func(args couchgo.FuncInput) couchgo.FuncOutput { ... }

CouchGO의 각 기능! 이 패턴을 따르며 Func는 적절한 함수 이름으로 대체됩니다. 현재 카우치GO! 다음 기능 유형을 지원합니다:

  • 지도
  • 줄이기
  • 필터
  • 업데이트
  • 검증(validate_doc_update)

map, Reduce 함수와 함께 유효성 검사_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
}

In CouchGO!, there is no emit function; instead, you return a slice of key-value tuples where both key and value can be of any type. The document object isn't directly passed to the function as in JavaScript; rather, it's wrapped in an object. The document itself is simply a hashmap of various values.

Next, let’s examine the reduce function:

func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput {
  out := 0.0
  for _, value := range args.Values {
    out += value.(float64)
  }
  return out
}

Similar to JavaScript, the reduce function in CouchGO! takes keys, values, and a rereduce parameter, all wrapped into a single object. This function should return a single value of any type that represents the result of the reduction operation.

Finally, let’s look at the Validate function, which corresponds to the validate_doc_update property:

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
}

In this function, we receive parameters such as the new document, old document, user context, and security object, all wrapped into one object passed as a function argument. Here, we’re expected to validate if the document can be updated and return an error if not. Similar to the JavaScript version, we can return two types of errors: ForbiddenError or UnauthorizedError. If the document can be updated, we should return nil.

For more detailed examples, they can be found in my GitHub repository. One important thing to note is that the function names are not arbitrary; they should always match the type of function they represent, such as Map, Reduce, Filter, etc.

CouchGO! Performance

Even though writing my own Query Server was a really fun experience, it wouldn’t make much sense if I didn’t compare it with existing solutions. So, I prepared a few simple tests in a Docker container to check how much faster CouchGO! can:

  • Index 100k documents (indexing in CouchDB means executing map functions from views)
  • Execute reduce function for 100k documents
  • Filter change feed for 100k documents
  • Perform update function for 1k requests

I seeded the database with the expected number of documents and measured response times or differentiated timestamp logs from the Docker container using dedicated shell scripts. The details of the implementation can be found in my GitHub repository. The results are presented in the table below.

Test CouchGO! CouchJS Boost
Indexing 141.713s 421.529s 2.97x
Reducing 7672ms 15642ms 2.04x
Filtering 28.928s 80.594s 2.79x
Updating 7.742s 9.661s 1.25x

As you can see, the boost over the JavaScript implementation is significant: almost three times faster in the case of indexing, more than twice as fast for reduce and filter functions. The boost is relatively small for update functions, but still faster than JavaScript.

Conclusion

As the author of the documentation promised, writing a custom Query Server wasn’t that hard when following the Query Server Protocol. Even though CouchGO! lacks a few deprecated functions in general, it provides a significant boost over the JavaScript version even at this early stage of development. I believe there is still plenty of room for improvements.

If you need all the code from this article in one place, you can find it in my GitHub repository.

Thank you for reading this article. I would love to hear your thoughts about this solution. Would you use it with your CouchDB instance, or maybe you already use some custom-made Query Server? I would appreciate hearing about it in the comments.

Don’t forget to check out my other articles for more tips, insights, and other parts of this series as they are created. Happy hacking!

위 내용은 소파GO! — Go로 작성된 쿼리 서버로 CouchDB 강화의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.