Usando el repositorio de git como backend de base de datos


119

Estoy haciendo un proyecto que se ocupa de la base de datos de documentos estructurados. Tengo un árbol de categorías (~ 1000 categorías, hasta ~ 50 categorías en cada nivel), cada categoría contiene varios miles (hasta, digamos, ~ 10000) de documentos estructurados. Cada documento tiene varios kilobytes de datos en alguna forma estructurada (prefiero YAML, pero también puede ser JSON o XML).

Los usuarios de estos sistemas realizan varios tipos de operaciones:

  • recuperación de estos documentos por identificación
  • buscar documentos por algunos de los atributos estructurados dentro de ellos
  • editar documentos (es decir, agregar / eliminar / renombrar / fusionar); cada operación de edición debe registrarse como una transacción con algún comentario
  • ver un historial de cambios registrados para un documento en particular (que incluye ver quién, cuándo y por qué cambió el documento, obtener una versión anterior y probablemente volver a esta si se solicita)

Por supuesto, la solución tradicional sería usar algún tipo de base de datos de documentos (como CouchDB o Mongo) para este problema; sin embargo, este control de versiones (historial) me tentó a una idea loca: ¿por qué no debería usar el gitrepositorio como un backend de base de datos para esta aplicación?

A primera vista, podría resolverse así:

  • categoría = directorio, documento = archivo
  • obtener documento por ID => cambiar directorios + leer un archivo en una copia de trabajo
  • editar documentos con comentarios de edición => realizar confirmaciones de varios usuarios + almacenar mensajes de confirmación
  • history => registro de git normal y recuperación de transacciones más antiguas
  • búsqueda => esa es una parte un poco más complicada, supongo que requeriría la exportación periódica de una categoría a una base de datos relacional con indexación de columnas que permitiremos buscar

¿Existen otros errores comunes en esta solución? ¿Alguien ha intentado implementar dicho backend ya (es decir, para marcos populares: RoR, node.js, Django, CakePHP)? ¿Tiene esta solución alguna posible implicación en el rendimiento o la confiabilidad, es decir, se ha comprobado que git sería mucho más lento que las soluciones de bases de datos tradicionales o habría problemas de escalabilidad / confiabilidad? Supongo que un grupo de servidores de este tipo que empujan / tiran del repositorio de los demás debería ser bastante robusto y confiable.

Básicamente, dime si esta solución funcionará y por qué funcionará o no.


Respuestas:


58

Responder mi propia pregunta no es lo mejor que se puede hacer, pero, dado que finalmente abandoné la idea, me gustaría compartir el fundamento que funcionó en mi caso. Me gustaría enfatizar que esta justificación podría no aplicarse a todos los casos, por lo que depende del arquitecto decidir.

Generalmente, el primer punto principal que mi pregunta pasa por alto es que estoy tratando con un sistema multiusuario que funciona en paralelo, simultáneamente, usando mi servidor con un cliente ligero (es decir, solo un navegador web). De esta manera, tengo que mantener el estado para todos ellos. Hay varios enfoques para este, pero todos son demasiado duros con los recursos o demasiado complejos de implementar (y, por lo tanto, eliminan el propósito original de descargar todas las cosas de implementación difíciles para git en primer lugar):

  • Enfoque "contundente": 1 usuario = 1 estado = 1 copia de trabajo completa de un repositorio que el servidor mantiene para el usuario. Incluso si estamos hablando de una base de datos de documentos bastante pequeña (por ejemplo, cientos de MiB) con ~ 100K de usuarios, mantener un clon de repositorio completo para todos ellos hace que el uso del disco se corra por las nubes (es decir, 100K de usuarios por 100MiB ~ 10 TiB) . Lo que es aún peor, la clonación del repositorio de 100 MiB cada vez lleva varios segundos de tiempo, incluso si se hace de manera bastante efectiva (es decir, sin usar git y desempaquetar y reempacar), lo cual no es aceptable, en mi opinión. Y lo que es aún peor: cada edición que aplicamos a un árbol principal debe llevarse al repositorio de cada usuario, que es (1) acaparamiento de recursos, (2) podría conducir a conflictos de edición no resueltos en el caso general.

    Básicamente, podría ser tan malo como O (número de ediciones × datos × número de usuarios) en términos de uso del disco, y dicho uso del disco automáticamente significa un uso bastante alto de la CPU.

  • Enfoque "Solo usuarios activos": mantenga la copia de trabajo solo para los usuarios activos. De esta manera, generalmente no almacena un clon de repositorio completo por usuario, sino:

    • Cuando el usuario inicia sesión, clona el repositorio. Tarda varios segundos y ~ 100 MiB de espacio en disco por usuario activo.
    • A medida que el usuario continúa trabajando en el sitio, trabaja con la copia de trabajo proporcionada.
    • A medida que el usuario cierra la sesión, su clon del repositorio se copia de nuevo al repositorio principal como una rama, almacenando así solo sus "cambios no aplicados", si los hay, lo que es bastante eficiente en el espacio.

    Por lo tanto, el uso del disco en este caso alcanza su punto máximo en O (número de ediciones × datos × número de usuarios activos), que suele ser ~ 100 ... 1000 veces menor que el número total de usuarios, pero hace que iniciar y cerrar sesión sea más complicado y lento. , ya que implica la clonación de una rama por usuario en cada inicio de sesión y retirar estos cambios al cerrar la sesión o al vencimiento de la sesión (lo que debe hacerse transaccionalmente => agrega otra capa de complejidad). En números absolutos, reduce el uso de disco de 10 TiB a 10..100 GiB en mi caso, eso podría ser aceptable, pero, una vez más, ahora estamos hablando de una base de datos bastante pequeña de 100 MiB.

  • Enfoque de "pago disperso": hacer un "pago disperso" en lugar de un clon de repositorio completo por usuario activo no ayuda mucho. Puede ahorrar ~ 10 veces el uso de espacio en disco, pero a expensas de una carga de CPU / disco mucho mayor en las operaciones que involucran el historial, lo que acaba con el propósito.

  • Enfoque de "grupo de trabajadores": en lugar de hacer clones completos cada vez para una persona activa, podríamos mantener un grupo de clones de "trabajadores", listos para ser utilizados. De esta manera, cada vez que un usuario inicia sesión, ocupa un "trabajador", tirando allí su rama desde el repositorio principal y, cuando cierra la sesión, libera al "trabajador", que hace un reinicio completo inteligente de git para volver a ser solo un clon de repositorio principal, listo para ser utilizado por otro usuario que inicie sesión. No ayuda mucho con el uso del disco (todavía es bastante alto, solo clon completo por usuario activo), pero al menos hace que el inicio / cierre de sesión sea más rápido, como gasto de aún más complejidad.

Dicho esto, tenga en cuenta que calculé intencionalmente números de base de datos y base de usuarios bastante pequeños: 100K usuarios, 1K usuarios activos, base de datos total de 100 MiB + historial de ediciones, 10 MiB de copia de trabajo. Si observa proyectos de crowdsourcing más destacados, hay números mucho más altos allí:

│              │ Users │ Active users │ DB+edits │ DB only │
├──────────────┼───────┼──────────────┼──────────┼─────────┤
│ MusicBrainz  │  1.2M │     1K/week  │   30 GiB │  20 GiB │
│ en.wikipedia │ 21.5M │   133K/month │    3 TiB │  44 GiB │
│ OSM          │  1.7M │    21K/month │  726 GiB │ 480 GiB │

Obviamente, para esa cantidad de datos / actividad, este enfoque sería completamente inaceptable.

En general, habría funcionado, si uno pudiera usar el navegador web como un cliente "grueso", es decir, emitiendo operaciones git y almacenando prácticamente el pago completo en el lado del cliente, no en el lado del servidor.

También hay otros puntos que me he perdido, pero no son tan malos en comparación con el primero:

  • El mismo patrón de tener un estado de edición de usuario "grueso" es controvertido en términos de ORM normales, como ActiveRecord, Hibernate, DataMapper, Tower, etc.
  • Por mucho que haya buscado, no hay ninguna base de código libre existente para hacer ese enfoque de git desde marcos populares.
  • Hay al menos un servicio que de alguna manera se las arregla para hacer eso de manera eficiente, que es obviamente github , pero, por desgracia, su base de código es de código cerrado y sospecho fuertemente que no usan servidores git normales / técnicas de almacenamiento de repositorios en el interior, es decir, básicamente implementaron git alternativo de "big data".

Así, línea de fondo : que es posible, pero para la mayoría de casos de uso actuales no será ni de lejos la solución óptima. Probablemente una mejor alternativa sería acumular su propia implementación de historial de edición de documentos en SQL o intentar utilizar cualquier base de datos de documentos existente.


16
Probablemente un poco tarde para la fiesta, pero tenía un requisito similar a este y de hecho fui por la ruta git. Después de investigar un poco con los componentes internos de git, encontré una manera de hacerlo funcionar. La idea es trabajar con un repositorio simple. Hay algunos inconvenientes, pero lo encuentro viable. He escrito todo en una publicación que quizás desee consultar (si es que hay algo, por interés): kenneth-truyers.net/2016/10/13/git-nosql-database
Kenneth

Otra razón por la que no hago esto son las capacidades de consulta. Los almacenes de documentos suelen indexar documentos, lo que facilita la búsqueda en ellos. Esto no será sencillo con git.
FrankyHollywood

12

Ciertamente un enfoque interesante. Yo diría que si necesita almacenar datos, use una base de datos, no un repositorio de código fuente, que está diseñado para una tarea muy específica. Si puede usar Git listo para usar, entonces está bien, pero probablemente necesite construir una capa de repositorio de documentos sobre él. Entonces, también podría construirlo sobre una base de datos tradicional, ¿verdad? Y si lo que le interesa es el control de versiones integrado, ¿por qué no utilizar una de las herramientas de repositorio de documentos de código abierto ? Hay mucho de donde escoger.

Bueno, si decide optar por el backend de Git de todos modos, básicamente funcionaría para sus requisitos si lo implementara como se describe. Pero:

1) Mencionaste "grupo de servidores que se empujan / jalan entre sí". Lo he pensado durante un tiempo y todavía no estoy seguro. No puede presionar / extraer varios repositorios como una operación atómica. Me pregunto si podría haber alguna posibilidad de que se produzca un desorden de fusión durante el trabajo simultáneo.

2) Quizás no lo necesite, pero una funcionalidad obvia de un repositorio de documentos que no enumeró es el control de acceso. Posiblemente podría restringir el acceso a algunas rutas (= categorías) a través de submódulos, pero probablemente no podrá otorgar acceso a nivel de documento fácilmente.


11

mi valor de 2 centavos. Un poco de nostalgia pero ... Tenía un requisito similar en uno de mis proyectos de incubación. Al igual que el suyo, mis requisitos clave eran una base de datos de documentos (xml en mi caso), con versiones de documentos. Fue para un sistema multiusuario con muchos casos de uso de colaboración. Mi preferencia fue utilizar las soluciones de código abierto disponibles que admitan la mayoría de los requisitos clave.

Para ir al grano, no pude encontrar ningún producto que proporcionara ambos, de una manera que fuera lo suficientemente escalable (número de usuarios, volúmenes de uso, recursos de almacenamiento y computación). Estaba predispuesto a git por toda la capacidad prometedora, y (probables) soluciones que uno podría elaborar a partir de él. A medida que jugaba más con la opción git, pasar de la perspectiva de un solo usuario a una perspectiva de múltiples (mili) usuarios se convirtió en un desafío obvio. Desafortunadamente, no pude hacer un análisis de rendimiento sustancial como lo hizo usted. (.. perezoso / salir temprano .... para la versión 2, mantra) ¡Poder para ti !. De todos modos, mi idea sesgada se ha transformado desde entonces en la siguiente alternativa (aún sesgada): una combinación de herramientas que son las mejores en sus esferas separadas, bases de datos y control de versiones.

Aunque todavía se está trabajando (... y un poco descuidado), la versión transformada es simplemente esto.

  • en la interfaz: (cara al usuario) use una base de datos para el almacenamiento de primer nivel (interactuando con las aplicaciones del usuario)
  • en el backend, use un sistema de control de versiones (VCS) (como git) para realizar el control de versiones de los objetos de datos en la base de datos

En esencia, equivaldría a agregar un complemento de control de versiones a la base de datos, con algo de pegamento de integración, que puede que tenga que desarrollar, pero que puede ser mucho más fácil.

Cómo funcionaría (se supone que funciona) es que los intercambios de datos de la interfaz multiusuario principal se realizan a través de la base de datos. El DBMS manejará todos los temas divertidos y complejos como multiusuario, concurrencia e, operaciones atómicas, etc. En el backend, el VCS realizaría el control de versiones en un solo conjunto de objetos de datos (sin concurrencia o problemas de multiusuario). Para cada transacción efectiva en la base de datos, el control de versiones solo se realiza en los registros de datos que efectivamente habrían cambiado.

En cuanto al pegamento de interfaz, tendrá la forma de una función de interfuncionamiento simple entre la base de datos y el VCS. En términos de diseño, un enfoque simple sería una interfaz impulsada por eventos, con actualizaciones de datos de la base de datos que activan los procedimientos de control de versiones (pista: asumiendo Mysql, uso de disparadores y sys_exec () bla bla ...) En términos de complejidad de implementación, variará desde lo simple y efectivo (por ejemplo, scripting) hasta lo complejo y maravilloso (alguna interfaz de conector programada). Todo depende de cuán loco quieras ir con él y de cuánto capital de sudor estés dispuesto a gastar. Creo que las secuencias de comandos simples deberían hacer la magia. Y para acceder al resultado final, las diversas versiones de datos, una alternativa simple es poblar un clon de la base de datos (más un clon de la estructura de la base de datos) con los datos referenciados por la etiqueta de versión / id / hash en el VCS. de nuevo, este bit será un simple trabajo de consulta / traducción / mapa de una interfaz.

Aún quedan algunos desafíos e incógnitas que abordar, pero supongo que el impacto y la relevancia de la mayoría de ellos dependerán en gran medida de los requisitos de su aplicación y los casos de uso. Algunos pueden terminar siendo un problema. Algunos de los problemas incluyen la coincidencia de rendimiento entre los 2 módulos clave, la base de datos y el VCS, para una aplicación con actividad de actualización de datos de alta frecuencia, escalado de recursos (capacidad de almacenamiento y procesamiento) a lo largo del tiempo en el lado de git como los datos y los usuarios crecer: estable, exponencial o eventualmente meseta

Del cóctel de arriba, esto es lo que estoy preparando actualmente.

  • usando Git para el VCS (inicialmente considerado un buen CVS antiguo debido al uso de solo conjuntos de cambios o deltas entre la versión 2)
  • usando mysql (debido a la naturaleza altamente estructurada de mis datos, xml con esquemas xml estrictos)
  • jugando con MongoDB (para probar una base de datos NoSQl, que se asemeja mucho a la estructura de la base de datos nativa utilizada en git)

Algunos datos divertidos: git en realidad hace cosas claras para optimizar el almacenamiento, como la compresión y el almacenamiento de solo deltas entre revisiones de objetos: SÍ, git solo almacena conjuntos de cambios o deltas entre revisiones de objetos de datos, dónde es aplicable (lo sabe cuando y cómo) . Referencia: packfiles, en lo más profundo de las entrañas de Git : revisión del almacenamiento de objetos de git (sistema de archivos direccionable por contenido), muestra similitudes sorprendentes (desde la perspectiva del concepto) con bases de datos noSQL como mongoDB. Nuevamente, a expensas del capital de sudor, puede proporcionar posibilidades más interesantes para integrar el 2 y ajustar el rendimiento

Si llegó hasta aquí, permítame si lo anterior puede ser aplicable a su caso, y suponiendo que lo sea, cómo encajaría con algunos de los aspectos de su último análisis integral de desempeño.


4

Implementé una biblioteca Ruby además de la libgit2cual hace que esto sea bastante fácil de implementar y explorar. Hay algunas limitaciones obvias, pero también es un sistema bastante liberador ya que obtienes la cadena de herramientas completa de git.

La documentación incluye algunas ideas sobre rendimiento, compensaciones, etc.


2

Como mencionaste, el caso multiusuario es un poco más complicado de manejar. Una posible solución sería utilizar archivos de índice de Git específicos del usuario que resulten en

  • no es necesario realizar copias de trabajo por separado (el uso del disco está restringido a archivos modificados)
  • no es necesario un trabajo preparatorio que requiere mucho tiempo (por sesión de usuario)

El truco consiste en combinar la GIT_INDEX_FILEvariable de entorno de Git con las herramientas para crear confirmaciones de Git manualmente:

A continuación se muestra un esquema de la solución (los hash SHA1 reales se omiten en los comandos):

# Initialize the index
# N.B. Use the commit hash since refs might changed during the session.
$ GIT_INDEX_FILE=user_index_file git reset --hard <starting_commit_hash>

#
# Change data and save it to `changed_file`
#

# Save changed data to the Git object database. Returns a SHA1 hash to the blob.
$ cat changed_file | git hash-object -t blob -w --stdin
da39a3ee5e6b4b0d3255bfef95601890afd80709

# Add the changed file (using the object hash) to the user-specific index
# N.B. When adding new files, --add is required
$ GIT_INDEX_FILE=user_index_file git update-index --cacheinfo 100644 <changed_data_hash> path/to/the/changed_file

# Write the index to the object db. Returns a SHA1 hash to the tree object
$ GIT_INDEX_FILE=user_index_file git write-tree
8ea32f8432d9d4fa9f9b2b602ec7ee6c90aa2d53

# Create a commit from the tree. Returns a SHA1 hash to the commit object
# N.B. Parent commit should the same commit as in the first phase.
$ echo "User X updated their data" | git commit-tree <new_tree_hash> -p <starting_commit_hash>
3f8c225835e64314f5da40e6a568ff894886b952

# Create a ref to the new commit
git update-ref refs/heads/users/user_x_change_y <new_commit_hash>

Dependiendo de sus datos, podría usar un trabajo cron para fusionar las nuevas referencias, masterpero la resolución del conflicto es posiblemente la parte más difícil aquí.

Las ideas para hacerlo más fácil son bienvenidas.


Ese es generalmente un enfoque que no conduce a ninguna parte, a menos que desee tener un concepto completo de transacción y UI para la resolución manual de conflictos. La idea general para los conflictos es hacer que el usuario los resuelva en el momento de la confirmación (es decir, "lo siento, alguien más editó el documento que estaba editando -> por favor vea sus ediciones y sus ediciones y combínelas"). Cuando permite que dos usuarios se comprometan correctamente y luego descubre en un cronjob asincrónico que las cosas se fueron al sur, generalmente no hay nadie disponible para resolver las cosas.
GreyCat
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.