"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Sofá¡GO! — Mejora de CouchDB con Query Server escrito en Go

Sofá¡GO! — Mejora de CouchDB con Query Server escrito en Go

Publicado el 2024-08-06
Navegar:880

CouchGO! — Enhancing CouchDB with Query Server Written in Go

Durante el último mes, estuve trabajando activamente en proyectos de prueba de concepto relacionados con CouchDB, explorando sus características y preparándome para tareas futuras. Durante este período, revisé la documentación de CouchDB varias veces para asegurarme de comprender cómo funciona todo. Mientras leía la documentación, me encontré con una afirmación de que, a pesar de que CouchDB se envía con un servidor de consultas predeterminado escrito en JavaScript, crear una implementación personalizada es relativamente simple y ya existen soluciones personalizadas.

Investigué un poco y encontré implementaciones escritas en Python, Ruby o Clojure. Como la implementación completa no pareció demasiado larga, decidí experimentar con CouchDB intentando escribir mi propio servidor de consultas personalizado. Para hacer esto, elegí Go como idioma. No había tenido mucha experiencia con este lenguaje antes, excepto el uso de plantillas Go en los gráficos de Helm, pero quería probar algo nuevo y pensé que este proyecto sería una gran oportunidad para ello.

Comprender el servidor de consultas

Antes de comenzar a trabajar, revisé la documentación de CouchDB una vez más para comprender cómo funciona realmente Query Server. Según la documentación, la descripción general de alto nivel del Query Server es bastante simple:

El servidor de consultas es un proceso externo que se comunica con CouchDB a través del protocolo JSON a través de una interfaz stdio y maneja todas las llamadas a funciones de diseño […].

La estructura de los comandos enviados por CouchDB al Query Server se puede expresar como [, ] o ["ddoc", , [, ], [ , ,…]] en el caso de documentos de diseño.

Básicamente, lo que tenía que hacer era escribir una aplicación capaz de analizar este tipo de JSON desde STDIO, realizar las operaciones esperadas y devolver respuestas como se especifica en la documentación. Hubo mucha conversión de tipos involucrada para manejar una amplia gama de comandos en el código Go. Los detalles específicos sobre cada comando se pueden encontrar en la sección Protocolo de Query Server de la documentación.

Un problema que enfrenté aquí fue que Query Server debería poder interpretar y ejecutar código arbitrario proporcionado en documentos de diseño. Sabiendo que Go es un lenguaje compilado, esperaba quedarme estancado en este punto. Afortunadamente, encontré rápidamente el paquete Yeagi, que es capaz de interpretar el código Go con facilidad. Permite crear un sandbox y controlar el acceso a qué paquetes se pueden importar en el código interpretado. En mi caso, decidí exponer solo mi paquete llamado Couchgo, pero también se pueden agregar fácilmente otros paquetes estándar.

¡Presentamos CouchGO!

Como resultado de mi trabajo, surgió una aplicación llamada CouchGO! surgió. Aunque sigue el protocolo Query Server, no es una reimplementación uno a uno de la versión JavaScript ya que tiene sus propios enfoques para manejar las funciones del documento de diseño.

Por ejemplo, en CouchGO!, no existe una función auxiliar como emitir. Para emitir valores, simplemente los devuelve desde la función de mapa. Además, cada función en el documento de diseño sigue el mismo patrón: tiene solo un argumento, que es un objeto que contiene propiedades específicas de la función, y se supone que devuelve solo un valor como resultado. Este valor no tiene por qué ser primitivo; dependiendo de la función, puede ser un objeto, un mapa o incluso un error.

Para comenzar a trabajar con CouchGO!, solo necesitas descargar el binario ejecutable de mi repositorio de GitHub, colocarlo en algún lugar de la instancia de CouchDB y agregar una variable de entorno que permita a CouchDB iniciar CouchGO! proceso.

Por ejemplo, si coloca el ejecutable de Couchgo en el directorio /opt/couchdb/bin, agregará la siguiente variable de entorno para permitir que funcione.

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

Funciones de escritura con CouchGO!

Para comprender rápidamente cómo escribir funciones con CouchGO!, exploremos la siguiente interfaz de funciones:

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

¡Cada función en CouchGO! seguirá este patrón, donde Func se reemplaza con el nombre de función apropiado. Actualmente, CouchGO! admite los siguientes tipos de funciones:

  • Mapa
  • Reducir
  • Filtrar
  • Actualizar
  • Validar (validate_doc_update)

Examinemos un documento de diseño de ejemplo que especifica una vista con funciones de mapa y reducción, así como una función validar_doc_update. Además, debemos especificar que estamos usando Go como idioma.

{
  "_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"
}

Ahora, analicemos cada función comenzando con la función de mapa:

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
}

En CouchGO!, no hay función de emisión; en su lugar, devuelve una porción de tuplas clave-valor donde tanto la clave como el valor pueden ser de cualquier tipo. El objeto del documento no se pasa directamente a la función como en JavaScript; más bien, está envuelto en un objeto. El documento en sí es simplemente un mapa hash de varios valores.

A continuación, examinemos la función de reducción:

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

Similar a JavaScript, la función de reducción en CouchGO! toma claves, valores y un parámetro de reducción, todo ello envuelto en un solo objeto. Esta función debe devolver un valor único de cualquier tipo que represente el resultado de la operación de reducción.

Finalmente, veamos la función Validar, que corresponde a la propiedad validar_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
}

En esta función, recibimos parámetros como el documento nuevo, el documento antiguo, el contexto del usuario y el objeto de seguridad, todo incluido en un objeto pasado como argumento de función. Aquí, se espera que validemos si el documento se puede actualizar y devuelva un error en caso contrario. De manera similar a la versión de JavaScript, podemos devolver dos tipos de errores: ForbiddenError o UnauthorizedError. Si el documento se puede actualizar, deberíamos devolver cero.

Para ejemplos más detallados, se pueden encontrar en mi repositorio de GitHub. Una cosa importante a tener en cuenta es que los nombres de las funciones no son arbitrarios; siempre deben coincidir con el tipo de función que representan, como Mapa, Reducir, Filtrar, etc.

Sofá¡GO! Actuación

Aunque escribir mi propio Query Server fue una experiencia realmente divertida, no tendría mucho sentido si no lo comparara con las soluciones existentes. Entonces, preparé algunas pruebas simples en un contenedor Docker para verificar qué tan rápido es CouchGO. poder:

  • Indexar 100.000 documentos (indexar en CouchDB significa ejecutar funciones de mapa desde vistas)
  • Ejecutar función de reducción para 100k documentos
  • Feed de cambio de filtro para 100.000 documentos
  • Realizar función de actualización para 1k solicitudes

Sembré la base de datos con la cantidad esperada de documentos y medí los tiempos de respuesta o diferencié los registros de marcas de tiempo del contenedor Docker usando scripts de shell dedicados. Los detalles de la implementación se pueden encontrar en mi repositorio de GitHub. Los resultados se presentan en la siguiente tabla.

Prueba Sofá¡GO! SofáJS Aumentar
Indexación 141.713s 421.529s 2,97x
Reducción 7672 ms 15642ms 2,04x
Filtración 28.928s 80.594s 2,79x
Actualizando 7.742s 9.661s 1,25x

Como puede ver, el impulso con respecto a la implementación de JavaScript es significativo: casi tres veces más rápido en el caso de la indexación, más del doble de rápido para las funciones de reducción y filtrado. El impulso es relativamente pequeño para las funciones de actualización, pero aún más rápido que JavaScript.

Conclusión

Como prometió el autor de la documentación, escribir un Query Server personalizado no fue tan difícil si se seguía el protocolo del Query Server. Aunque CouchGO! Aunque carece de algunas funciones obsoletas en general, proporciona un impulso significativo con respecto a la versión de JavaScript incluso en esta etapa inicial de desarrollo. Creo que todavía hay mucho margen de mejora.

Si necesitas todo el código de este artículo en un solo lugar, puedes encontrarlo en mi repositorio de GitHub.

Gracias por leer este artículo. Me encantaría escuchar tu opinión sobre esta solución. ¿Lo usaría con su instancia de CouchDB o tal vez ya use algún servidor de consultas personalizado? Agradecería saberlo en los comentarios.

No olvides consultar mis otros artículos para obtener más consejos, ideas y otras partes de esta serie a medida que se crean. ¡Feliz pirateo!

Declaración de liberación Este artículo se reproduce en: https://dev.to/kishieel/couchgo-enhancing-couchdb-with-query-server-writing-in-go-304n?1 Si hay alguna infracción, comuníquese con [email protected] para borrarlo
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3