Em Blog do Matthew o subdocumento (subdoc) O recurso da API é apresentado com uma breve visão geral: Em resumo, o subdoc permite o acesso eficiente a partes de documentos (submarino-documentos) sem exigir a transferência de todo o documento pela rede.
Ao longo deste blog, usaremos um documento de referência. Esse documento será acessado de várias maneiras usando a API de subdocumentos. Observe que para cada operação de subdocumento, tamanho_doc - tamanho_op
bytes de largura de banda estão sendo salvos, onde tamanho_doc
é o tamanho do documento, e tamanho_op
é o comprimento do caminho e o valor do subdocumento.
O documento abaixo tem 500 bytes. A execução de um simples get()
consumiria 500 bytes (mais sobrecarga de protocolo) na resposta do servidor. Se você se importar apenas com o endereço de entrega, poderá emitir um lookup_in('customer123', SD.get('addresses.delivery'))
chamada. Você receberia apenas cerca de 120 bytes pela rede, uma economia de mais de 400 bytes, usando um quarto da largura de banda do 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 |
{ "name" (nome): "Douglas Reynholm", "email": "douglas@reynholmindustries.com", "endereços": { "faturamento": { "line1": "123 Any Street" (123 Qualquer Rua), "line2": "Qualquer cidade", "país": "Reino Unido" }, "entrega": { "line1": "123 Any Street" (123 Qualquer Rua), "line2": "Qualquer cidade", "país": "Reino Unido" } }, "compras": { "completo": [ 339, 976, 442, 666 ], "abandonado": [ 157, 42, 999 ] } } |
Demonstrarei exemplos usando um ramo de desenvolvimento do Python SDK e Visualização para desenvolvedores do Couchbase Server 4.5.
[EDITAR: Uma versão experimental da API de subdocumento já está disponível na versão mais recente do SDK do PythonA versão 2.0.8, e os exemplos abaixo foram atualizados para refletir a API mais recente]
Você pode ler sobre os outros novos recursos do Couchbase 4.5 em Postagem no blog de Don Pinto
Operações de subdocumentos
Uma operação de subdoc é uma ação única para um único caminho em um documento. Isso pode ser expresso como GET('addresses.billing')
ou ARRAY_APPEND('purchases.abandoned', 42)
. Algumas operações são pesquisas (eles simplesmente retornam dados sem modificar o documento), enquanto alguns são mutações (eles modificam o conteúdo do documento).
Muitas das operações de subdoc são equivalentes em escala menor das operações de fulldoc. É útil pensar em um único documento como sendo um armazenamento de valores-chave em miniatura. No SDK do Python, as operações podem ser especificadas por meio de funções especiais na seção couchbase.subdocument
que abreviarei no restante deste blog como SD
. Isso é feito por
1 |
importação couchbase.subdocumento como SD |
Ao examinar essas operações, observe que o que está sendo transmitido pela rede são apenas os argumentos passados para a própria API subdoc, e não o conteúdo de todo o documento (como aconteceria com o fulldoc). Embora o documento em si possa parecer pequeno, mesmo um simples
Operações de pesquisa
As operações de pesquisa consultam o documento em busca de um determinado caminho e retornam esse caminho. Você tem a opção de realmente recuperação o caminho do documento usando o OBTER
operação, ou simplesmente Consultar a existência do caminho usando o EXISTE
operação. A última economiza ainda mais largura de banda por não recuperar o conteúdo do caminho se ele não for necessário.
1 2 |
rv = balde.lookup_in('customer123', SD.obter('addresses.delivery.country')) país = rv[0] # => 'Reino Unido' |
1 2 |
rv = balde.lookup_in('customer123', SD.existe('purchases.pending[-1]')) rv.existe(0) # (verificar se o caminho para o primeiro comando existe): =>; False |
No segundo snippet, também mostro como acessar o último elemento de uma matriz, usando a função especial [-1]
componente de caminho.
Também podemos combinar essas duas operações:
1 2 3 4 5 6 |
rv = balde.lookup_in('customer123', SD.obter('addresses.delivery.country'), SD.existe('purchases.pending[-1]')) rv[0] # => 'Reino Unido' rv.existe(1) # => False rv[1] # => SubdocPathNotFoundError |
Operações de mutação
As operações de mutação modificam um ou mais caminhos no documento. Essas operações podem ser divididas em vários grupos:
- Operações de dicionário/objeto: Essas operações gravam o valor de uma chave de dicionário JSON.
- Operações de matriz/lista: Essas operações adicionam operações à matriz/lista JSON.
- Operações genéricas: Essas operações modificam o próprio valor existente e são independentes do contêiner.
As operações de mutação são tudo ou nadao que significa que todas as operações dentro de mutate_in
são bem-sucedidos, ou nenhum deles é.
Operações de dicionário
A mais simples dessas operações é UPSERT
. Assim como o upsert em nível de documento completo, ele modificará o valor de um caminho existente ou o criará se ele não existir:
1 |
balde.mutate_in('customer123', SD.upsert('fax', '775-867-5309')) |
Além de UPSERT
, o INSERIR
só adicionará o novo valor ao caminho se ele não existir.
1 2 |
balde.mutate_in('customer123', SD.inserir('purchases.complete', [42, Verdadeiro, Nenhum])) # SubdocPathExistsError |
Embora a operação acima falhe, observe que qualquer coisa válida como um valor de documento completo também é válida como um valor de subdoc: Desde que possa ser serializado como JSON. O Python SDK serializa o valor acima para [42, true, null]
.
Os valores do dicionário também podem ser substituídos ou removidos:
1 2 3 |
balde.mutate_in('customer123', SD.remover('addresses.billing'), SD.substituir('email', 'doug96@hotmail.com')) |
Operações de matriz
True array append (ARRAY_APPEND
) e prepend (ARRAY_PREPEND
) também podem ser executadas usando subdoc. Diferentemente das operações fulldoc append/prepend (que simplesmente concatenam bytes ao valor existente), as operações subdoc append e prepend são compatíveis com JSON:
1 2 |
balde.mutate_in('customer123', SD.array_append('purchases.complete', 777)) # purchases.complete agora é [339, 976, 442, 666, 777] |
1 2 |
balde.mutate_in('customer123', SD.array_prepend('purchases.abandoned', 18)) # purchaes.abandoned in now [18, 157, 49, 999] |
Você também pode criar um documento somente de matriz e, em seguida, executar matriz_
usando um caminho vazio:
1 2 3 |
balde.upsert('my_array', []) balde.mutate_in('my_array', SD.array_append('', 'algum elemento')) # o documento my_array agora é ["algum elemento"] |
Também existe um suporte limitado para tratar matrizes como conjuntos exclusivos, usando o ARRAY_ADDUNIQUE
comando. Isso fará uma verificação para determinar se o valor fornecido existe ou não antes de realmente adicionar o item à matriz:
1 2 3 4 5 |
balde.mutate_in('customer123', SD.array_addunique('purchases.complete', 95)) # => Sucesso balde.mutate_in('customer123', SD.array_addunique('compras.abandonado', 42)) # => SubdocPathExists exceção! |
As operações de matriz também podem ser usadas como base para filas FIFO ou LIFO eficientes. Primeiro, crie a fila:
1 |
balde.upsert('my_queue', []) |
Adicionar itens ao final
1 |
balde.mutate_in('my_queue', SD.array_append('', 'job:953')) |
Item de consumo desde o início.
1 2 3 4 |
rv = balde.lookup_in('my_queue', SD.obter('[0]')) job_id = rv[0] balde.mutate_in('my_queue', SD.remover('[0]'), cas=rv.cas) run_job(job_id) |
O exemplo acima executa um OBTER
seguido de um REMOVER
. O REMOVER
só é executado quando o aplicativo já tiver o trabalho e só será bem-sucedido se o documento não tiver sido alterado desde então (para garantir que o primeiro item na fila seja o que acabamos de remover).
Operações de combate
As operações de contador permitem a manipulação de um numérico dentro de um documento. Essas operações são logicamente semelhantes às operações de contador
em um documento inteiro.
1 2 |
rv = balde.mutate_in('customer123', SD.contador('logins', 1)) cur_count = rv[0] # => 1 |
O CONTADOR
realiza uma aritmética simples em relação a um valor numérico (o valor é criado se ainda não existir).
CONTADOR
também pode diminuir:
1 2 3 4 |
balde.upsert('player432', {"ouro: 1000}) rv = balde.mutate_in('player432', SD.contador("ouro, -150)) impressão('player432 now has {0} gold remaining'.formato(rv[0])) # => o jogador 432 agora tem 850 de ouro restante |
Observe que o valor existente para operações de contador deve estar dentro do intervalo de um número inteiro assinado de 64 bits.
Criação de intermediários
Todos os exemplos acima se referem à criação de um único campo novo em um dicionário existente. Entretanto, a criação de uma nova hierarquia resultará em um erro:
1 2 3 |
balde.mutate_in('customer123', SD.upsert('phone.home', {'num': '775-867-5309', 'ext': 16})) # => SubdocPathNotFound |
Apesar de a operação ser uma UPSERT
Por padrão, o subdoc se recusará a criar hierarquias ausentes. O criar_pais
No entanto, a opção permite que ele seja bem-sucedido: adicione o nível de protocolo em que a opção é chamada F_MKDIRP
, como o -p
da opção mkdir
em plataformas do tipo Unix.
1 2 3 4 |
balde.mutate_in('customer123', SD.upsert('phone.home', {'num': '775-867-5309', 'ext': 16}, criar_pais=Verdadeiro)) |
Subdocumento e CAS
O subdoc elimina principalmente a necessidade de rastreamento CAS. As operações de subdoc são atômicas e, portanto, se dois threads diferentes acessarem dois subdocumentos diferentes, não haverá conflito. Por exemplo, os dois blocos a seguir podem ser executados simultaneamente sem nenhum risco de conflito:
1 |
balde.mutate_in('customer123', SD.array_append('purchases.complete', 999)) |
1 |
balde.mutate_in('customer123', SD.array_append('compras.abandonado', 998)) |
Mesmo ao modificar o mesmo parte do documento, as operações não necessariamente entrarão em conflito, por exemplo, duas operações simultâneas ARRAY_PREPEND
para o mesmo array serão bem-sucedidos, nunca substituindo o outro.
Isso não significa que o CAS não seja mais necessário - às vezes é importante garantir que o documento completo não mudou de estado desde a última operação: isso é especialmente importante no caso de REMOVER
para garantir que o elemento que está sendo removido não tenha sido substituído por outra coisa.
Perguntas frequentes sobre operações de subdocumentos no Couchbase
No decorrer do desenvolvimento do subdoc, fizeram-me várias perguntas sobre o que ele faz, e eu responderei em seguida:
Qual é a diferença entre Subdoc e N1QL?
O N1QL é uma linguagem de consulta rica e expressiva que permite que você pesquise e possivelmente altere vários documentos de uma só vez. Subdoc é uma API/implementação de alto desempenho projetada para pesquisar em um documento único.
Subdoc é um alto desempenho conjunto de APIs simples e discretas para acessar dados em um único documento, com o objetivo de reduzir largura de banda da rede e aumentando a taxa de transferência geral. Ele é implementado como parte do serviço KV e, portanto, é bastante consistente com ele.
N1QL é um linguagem de consulta avançada capaz de pesquisar vários documentos no Couchbase que atendam a determinados critérios. Ele opera externo o serviço de KV, fazendo solicitações otimizadas de KV e índice para satisfazer as consultas recebidas. A consistência com o serviço de KV é configurável por consulta (por exemplo, o USAR CHAVES
e a cláusula scan_consistency
opção).
Quando devo usar o N1QL e quando devo usar o subdoc?
O N1QL responde a perguntas como Encontre todos os documentos em que X=42 e Y=77 Enquanto o subdoc responde a perguntas como Obter X e Y do documento Z. Mais especificamente, o subdoc deve ser usado quando todos os IDs de documentos são conhecidos (em outras palavras, se uma consulta N1QL contiver USAR CHAVES
pode ser um candidato a subdoc).
No entanto, os dois não são mutuamente exclusivos, e é possível usar tanto o N1QL quanto o subdoc em um aplicativo.
São mutate_in
e lookup_in
atômica?
Sim, elas são atômicas. Ambas as operações têm a garantia de ter todos os seus subcomandos (por exemplo CONTADOR
, OBTER
, EXISTE
, ADD_UNIQUE
) operam na mesma versão do documento.
Como faço para acessar vários documentos com subdoc?
Não há nenhuma boa fé multi operação para subdoc, pois subdoc opera dentro do escopo de um único documento. Como os documentos são fragmentados no cluster (isso é comum ao Couchbase e a todos os outros armazenamentos NoSQL), as operações múltiplas não poderiam garantir o mesmo nível de transações e atomicidade entre os documentos.
Não gosto da convenção de nomes para matrizes. Por que você não usou anexar
, adicionar
etc.?
Há muitas linguagens no mercado e parece que todas elas têm uma ideia diferente de como chamar as funções de acesso ao array:
- Genérico: adicionar ao final, adicionar à frente
- C++:
push_back()
,push_front()
- Python:
anexar()
,inserir(0)
,estender
- Perl, Ruby, Javascript, PHP:
push()
,unshift()
- Java, C#:
add()
O termo anexar
já é usado no Couchbase para se referir à concatenação de bytes do documento completo, por isso considerei inconsistente usar esse termo de uma maneira ainda diferente no subdoc.
Por que a CONTADOR
requerem números inteiros assinados de 64 bits?
Isso é resultado do fato de o código do subdoc ser implementado em C++. Implementações futuras poderão permitir uma faixa mais ampla de valores numéricos existentes (por exemplo, valores grandes, valores não integrais etc.).
Como faço para executar um pop? por que não há POP
operação?
POP
refere-se ao ato de remover um item (por exemplo, de uma matriz) e devolvê-lo, em uma única operação.
POP
pode de fato ser implementado no futuro, mas usá-lo é inerentemente perigoso:
Como a operação está sendo feita pela rede, é possível que o servidor tenha executado a remoção do item, mas que a conexão de rede tenha sido encerrada antes que o cliente receba o valor anterior. Como o valor não está mais no documento, ele é perdido permanentemente.
Posso usar o CAS com operações de subdocumentos?
Sim, com relação ao uso do CAS, as operações de subdoc são operações normais da API KV, semelhantes a upsert
, obter
etc.
Posso usar requisitos de durabilidade com operações de subdoc?
Sim, com relação aos requisitos de durabilidade, mutate_in
é visto como upsert
, inserir
e substituir
.