En Blog de Matthew el subdocumento (subdoc) La función API se presenta con un breve resumen: En resumen, subdoc permite un acceso eficaz a partes de documentos (sub-) sin necesidad de transferir todo el documento a través de la red.
A lo largo de este blog, utilizaremos un documento de referencia. Se accederá a este documento de varias formas utilizando la API de subdocumentos. Tenga en cuenta que para cada operación subdocumento, tamaño_doc - tamaño_op
bytes de ancho de banda, donde tamaño_doc
es la longitud del documento, y tamaño_op
es la longitud de la ruta y el valor del subdocumento.
El documento que aparece a continuación ocupa 500 bytes. Realizando un simple get()
consumiría 500 bytes (más la sobrecarga del protocolo) en la respuesta del servidor. Si sólo le interesa la dirección de entrega, puede emitir un mensaje lookup_in('cliente123', SD.get('direcciones.entrega'))
llamada. Sólo recibiría unos 120 bytes a través de la red, lo que supone un ahorro de más de 400 bytes, utilizando una cuarta parte del ancho de banda del documento completo equivalente (fulldoc).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "nombre": "Douglas Reynholm", "email": "douglas@reynholmindustries.com", "direcciones": { "facturación": { "línea1": "Calle Cualquiera 123", "línea2": "Cualquier ciudad", "país": "Reino Unido" }, "entrega": { "línea1": "Calle Cualquiera 123", "línea2": "Cualquier ciudad", "país": "Reino Unido" } }, "compras": { "completo": [ 339, 976, 442, 666 ], "abandonado": [ 157, 42, 999 ] } } |
Demostraré ejemplos utilizando un rama de desarrollo del SDK de Python y Vista previa para desarrolladores de Couchbase Server 4.5.
[EDITAR: Ya está disponible una versión experimental de la API de subdocumentos en la última versión de SDK de Pythonversión 2.0.8, y los ejemplos que figuran a continuación se han actualizado para reflejar la API más reciente].
Puede consultar el resto de novedades de Couchbase 4.5 en Entrada del blog de Don Pinto
Operaciones subdoc
Una operación subdoc es una acción única para una única ruta en un documento. Puede expresarse como GET('direcciones.facturación')
o ARRAY_APPEND('compras.abandonadas', 42)
. Algunas operaciones son búsquedas (simplemente devuelven datos sin modificar el documento), mientras que algunos son mutaciones (modifican el contenido del documento).
Muchas de las operaciones de los subdocumentos son equivalentes a menor escala de las operaciones de los documentos completos. Es útil pensar que un documento es en sí mismo un almacén de claves y valores en miniatura. En el SDK de Python, las operaciones pueden especificarse a través de funciones especiales en el directorio couchbase.subdocumento
que abreviaré en el resto de este blog como SD
. Para ello
1 |
importar couchbase.subdocumento como SD |
Al observar estas operaciones, tenga en cuenta que lo que se transmite por la red son sólo los argumentos pasados a la API de subdoc, y no el contenido del documento completo (como ocurriría con fulldoc). Aunque el documento en sí pueda parecer pequeño, incluso un simple
Operaciones de búsqueda
Las operaciones de búsqueda consultan el documento en busca de una ruta determinada y la devuelven. Puede elegir entre recuperar la ruta del documento mediante el botón GET
o simplemente consultar la existencia de la ruta utilizando el EXISTE
operación. Esta última ahorra aún más ancho de banda al no recuperar el contenido de la ruta si no se necesita.
1 2 |
rv = cubo.buscar_en(cliente123, SD.consiga(direcciones.entrega.pais)) país = rv[0] # => 'Reino Unido' |
1 2 |
rv = cubo.buscar_en(cliente123, SD.existe('compras.pendientes[-1]')) rv.existe(0) # (comprueba si existe la ruta para el primer comando): =>; False |
En el segundo fragmento, también muestro cómo acceder al último elemento de una matriz, utilizando la función especial [-1]
componente del camino.
También podemos combinar estas dos operaciones:
1 2 3 4 5 6 |
rv = cubo.buscar_en(cliente123, SD.consiga(direcciones.entrega.pais), SD.existe('compras.pendientes[-1]')) rv[0] # => 'Reino Unido' rv.existe(1) # => Falso rv[1] # => SubdocPathNotFoundError |
Operaciones de mutación
Las operaciones de mutación modifican una o varias rutas del documento. Estas operaciones pueden dividirse en varios grupos:
- Operaciones diccionario/objeto: Estas operaciones escriben el valor de una clave de diccionario JSON.
- Operaciones de array/lista: Estas operaciones añaden operaciones a matrices/listas JSON.
- Operaciones genéricas: Estas operaciones modifican el propio valor existente y son independientes del contenedor.
Las operaciones de mutación son todo o nadalo que significa que todas las operaciones dentro de mutar_en
tienen éxito, o ninguno.
Operaciones de diccionario
La más sencilla de estas operaciones es UPSERT
. Al igual que el upsert a nivel de documento completo, modificará el valor de una ruta existente o la creará si no existe:
1 |
cubo.mutar_en(cliente123, SD.upsert(fax, '775-867-5309')) |
Además de UPSERT
El INSERTAR
sólo añadirá el nuevo valor a la ruta si no existe.
1 2 |
cubo.mutar_en(cliente123, SD.insertar(compras.completas, [42, Verdadero, Ninguno])) # SubdocPathExistsError |
Aunque la operación anterior fallará, tenga en cuenta que cualquier cosa válida como valor de documento completo también es válida como valor de subdoc: Siempre que pueda serializarse como JSON. El SDK de Python serializa el valor anterior a [42, true, null]
.
Los valores del diccionario también pueden sustituirse o eliminarse:
1 2 3 |
cubo.mutar_en(cliente123, SD.eliminar(direcciones.facturación), SD.sustituir(correo electrónico, doug96@hotmail.com)) |
Operaciones de matriz
Verdadero array append (ARRAY_APPEND
) y prepend (ARRAY_PREPEND
) también pueden realizarse mediante subdoc. A diferencia de las operaciones append/prepend de fulldoc (que simplemente concatenan bytes al valor existente), subdoc append y prepend son conscientes de JSON:
1 2 |
cubo.mutar_en(cliente123, SD.array_append(compras.completas, 777)) # compras.completas es ahora [339, 976, 442, 666, 777] |
1 2 |
cubo.mutar_en(cliente123, SD.array_prepend(compras.abandonadas, 18)) # purchaes.abandoned in now [18, 157, 49, 999] |
También puede crear un documento de sólo matriz y, a continuación, ejecutar array_
utilizando una ruta vacía:
1 2 3 |
cubo.upsert('mi_array', []) cubo.mutar_en('mi_array', SD.array_append('', algún elemento)) # el documento mi_array es ahora ["algún elemento"] |
También existe un soporte limitado para tratar las matrices como conjuntos únicos, utilizando la función ARRAY_ADDUNIQUE
comando. Esto hará una comprobación para determinar si el valor dado existe o no antes de añadir realmente el elemento a la matriz:
1 2 3 4 5 |
cubo.mutar_en(cliente123, SD.array_addunique(compras.completas, 95)) # => Éxito cubo.mutar_en(cliente123, SD.array_addunique('compras.abandonado', 42)) # => SubdocPathExists excepción! |
Las operaciones de matriz también pueden utilizarse como base para crear colas FIFO o LIFO eficientes. En primer lugar, cree la cola:
1 |
cubo.upsert(mi_cola, []) |
Añadir elementos al final
1 |
cubo.mutar_en(mi_cola, SD.array_append('', trabajo:953)) |
Artículo de consumo desde el principio.
1 2 3 4 |
rv = cubo.buscar_en(mi_cola, SD.consiga('[0]')) job_id = rv[0] cubo.mutar_en(mi_cola, SD.eliminar('[0]'), cas=rv.cas) ejecutar_trabajo(job_id) |
El ejemplo anterior realiza una GET
seguido de un ELIMINAR
. En ELIMINAR
sólo se realiza una vez que la aplicación ya tiene el trabajo, y sólo tendrá éxito si el documento no ha cambiado desde entonces (para asegurar que el primer elemento de la cola es el que acabamos de eliminar).
Contraoperaciones
Las operaciones de contador permiten manipular un numérico dentro de un documento. Estas operaciones son lógicamente similares a las operaciones contador
en un documento entero.
1 2 |
rv = cubo.mutar_en(cliente123, SD.contador(Inicio de sesión, 1)) cur_count = rv[0] # => 1 |
En CONTADOR
realiza una operación aritmética simple con un valor numérico (el valor se crea si aún no existe).
CONTADOR
también puede disminuir:
1 2 3 4 |
cubo.upsert(jugador432, {oro: 1000}) rv = cubo.mutar_en(jugador432, SD.contador(oro, -150)) imprimir("al jugador432 le queda {0} de oro.formato(rv[0])) # => al jugador 432 ahora le quedan 850 de oro |
Tenga en cuenta que el valor existente para las operaciones de contador debe estar dentro del rango de un entero con signo de 64 bits.
Creación de intermediarios
Todos los ejemplos anteriores se refieren a la creación de un único campo nuevo dentro de un diccionario existente. Sin embargo, la creación de una nueva jerarquía dará lugar a un error:
1 2 3 |
cubo.mutar_en(cliente123, SD.upsert(teléfono.casa, {'num': '775-867-5309', ext: 16})) # => SubdocPathNotFound |
A pesar de que la operación UPSERT
por defecto, el subdoc se negará a crear las jerarquías que falten. La dirección crear_padres
sin embargo permite que tenga éxito: añadir el nivel de protocolo la opción se llama F_MKDIRP
como el -p
de la mkdir
en plataformas tipo Unix.
1 2 3 4 |
cubo.mutar_en(cliente123, SD.upsert(teléfono.casa, {'num': '775-867-5309', ext: 16}, crear_padres=Verdadero)) |
Subdocumento y CAS
Subdoc elimina casi por completo la necesidad de seguimiento CAS. Las operaciones con subdocumentos son atómicas y, por tanto, si dos subprocesos distintos acceden a dos subdocumentos distintos, no se producirá ningún conflicto. Por ejemplo, los dos bloques siguientes pueden ejecutarse simultáneamente sin riesgo de conflicto:
1 |
cubo.mutar_en(cliente123, SD.array_append(compras.completas, 999)) |
1 |
cubo.mutar_en(cliente123, SD.array_append('compras.abandonado', 998)) |
Incluso cuando se modifica el mismo parte del documento, las operaciones no entrarán necesariamente en conflicto, por ejemplo dos ARRAY_PREPEND
a la misma matriz, ambas tendrán éxito, sin sobrescribir nunca a la otra.
Esto no significa que el CAS ya no sea necesario: a veces es importante asegurarse de que el documento completo no ha cambiado de estado desde la última operación: esto es especialmente importante en el caso de ELIMINAR
para garantizar que el elemento que se elimina no ha sido sustituido por otro.
Preguntas frecuentes sobre las operaciones con subdocumentos en Couchbase
A lo largo del desarrollo del subdoc, me han hecho varias preguntas sobre lo que hace, y responderé por turnos:
¿Cuál es la diferencia entre Subdoc y N1QL?
N1QL es un lenguaje de consulta rico y expresivo que permite buscar y posiblemente mutar varios documentos a la vez. Subdoc es una API/implementación de alto rendimiento diseñada para buscar dentro de un documento único.
Subdoc es un alto rendimiento conjunto de API sencillas y discretas para acceder a los datos dentro de un mismo documento, con el objetivo de reducir ancho de banda de la red y aumentar el rendimiento global. Se aplica como parte del servicio KV y, por tanto, es muy coherente con él.
N1QL es un lenguaje de consulta enriquecido capaz de buscar múltiples documentos dentro de Couchbase que cumplan ciertos criterios. Funciona fuera de el servicio KV, realizando peticiones optimizadas de KV e índices para satisfacer las consultas entrantes. La coherencia con el servicio de KV es configurable por consulta (por ejemplo, el valor UTILIZAR TECLAS
y la cláusula consistencia_de_exploración
opción).
¿Cuándo debo utilizar N1QL y cuándo subdoc?
N1QL responde a preguntas como Buscar todos los documentos en los que X=42 e Y=77 mientras que subdoc responde a preguntas como Obtener X e Y del documento Z. Más concretamente, subdoc debe utilizarse cuando se conozcan todos los ID de documento (en otras palabras, si una consulta N1QL contiene UTILIZAR TECLAS
puede ser candidato a subdoc).
Sin embargo, ambos no son mutuamente excluyentes, y es posible utilizar tanto N1QL como subdoc en una aplicación.
Son mutar_en
y buscar_en
¿Atómico?
Sí, son atómicas. Ambas operaciones tienen garantizado que todos sus subcomandos (p. ej. CONTADOR
, GET
, EXISTE
, ADD_UNIQUE
) operan sobre la misma versión del documento.
¿Cómo accedo a varios documentos con subdoc?
No hay ninguna multi para subdoc, ya que subdoc opera dentro del ámbito de un único documento. Dado que los documentos se reparten por todo el clúster (esto es común a Couchbase y a todos los demás almacenes NoSQL), las operaciones múltiples no podrían garantizar el mismo nivel de transacciones y atomicidad entre documentos.
No me gusta la convención de nomenclatura para las matrices. ¿Por qué no usaste añadir
, añada
etc.
Existen muchos lenguajes y parece que todos tienen una idea diferente de cómo llamar a las funciones de acceso a matrices:
- Genérico: añadir al final, añadir al principio
- C++:
push_back()
,push_front()
- Python:
añadir()
,insertar(0)
,ampliar
- Perl, Ruby, Javascript, PHP:
push()
,deshacer()
- Java, C#:
añadir()
El término añadir
ya se utiliza en Couchbase para referirse a la concatenación de bytes del documento completo, por lo que consideré incoherente utilizar este término de una manera diferente en subdoc.
¿Por qué CONTADOR
requieren enteros con signo de 64 bits?
Esto se debe a que el código del subdoc está implementado en C++. Es posible que futuras implementaciones permitan una gama más amplia de valores numéricos existentes (por ejemplo, valores grandes, valores no integrales, etc.).
¿Cómo puedo realizar una pop? ¿por qué no hay POP
¿Operación?
POP
se refiere al acto de eliminar un elemento (por ejemplo, de una matriz) y devolverlo, en una sola operación.
POP
puede implementarse en el futuro, pero su uso es intrínsecamente peligroso:
Dado que la operación se realiza a través de la red, es posible que el servidor haya ejecutado la eliminación del elemento pero que la conexión de red haya finalizado antes de que el cliente reciba el valor anterior. Como el valor ya no está en el documento, se pierde permanentemente.
¿Puedo utilizar CAS con operaciones subdoc?
Sí, en lo que respecta al uso de CAS, las operaciones Subdoc son operaciones normales de la API del KV, similares a upsert
, consiga
etc.
¿Puedo utilizar requisitos de durabilidad con operaciones subdoc?
Sí, en lo que respecta a los requisitos de durabilidad, mutar_en
se ve como upsert
, insertar
y sustituir
.