A deduplicação de usuários é uma atividade importante para qualquer pessoa que gerencie um corpus de dados relacionados a usuários. Em alguns casos, você pode acabar com várias versões do mesmo usuário no seu banco de dados. Isso pode acontecer por vários motivos: gerenciamento de dados inadequado, dados de usuário incompatíveis, erro do usuário ou do sistema etc. O que também é comum é a agregação de informações de usuários de vários sistemas diferentes. Talvez você tenha o seu conjunto de dados principal de usuários, mas também queira enriquecê-lo com dados de terceiros, como mídia social, sistemas de emissão de bilhetes ou de registro, ou talvez até mesmo informações adicionais de usuários de outros silos dentro da sua própria organização.

Esta é uma postagem de blog de convidado escrita por Andy Kruth. Andy Kruth é consultor principal da Avalon Consulting, LLC onde se concentra na arquitetura e no desenvolvimento de sistemas de Big Data, bem como em soluções NoSQL. Esta postagem foi influenciada pelo trabalho realizado para o Cincinnati Reds. Embora o exemplo a seguir possa parecer simples, ele se baseia em um aplicativo muito mais complexo criado para os Reds, no qual os dados de usuários de várias facetas do negócio dos Reds foram deduplicados em um único conjunto de dados de conta e domicílio.
Seja qual for o caso, a deduplicação das informações do usuário em um conjunto de dados conciso e preciso é uma atividade comum. Uma abordagem tradicional para realizar isso é simplesmente executar todas as informações do usuário por meio de um aplicativo para fazer a correspondência entre os registros com base na heurística definida e gerar um único conjunto de dados. Embora isso "funcione", geralmente é uma ação que precisa ser executada em lote regularmente - ou, pelo menos, regularmente em relação a quaisquer deltas que você possa capturar. Mas a desvantagem dessa abordagem é que sempre que ações em lote ou programadas são usadas, há um grande potencial para resultados de dados obsoletos.
Com a introdução do Eventos do CouchbaseAgora você tem uma nova opção de desduplicação eficaz, e ela é em tempo real! Vou me abster das informações gerais sobre Eventing que já foram discutidas exaustivamente em várias outras publicações (consulte aquie aquie aqui). Em vez disso, esta publicação se concentrará em um exemplo simples de como você pode desenvolver um mecanismo de deduplicação em tempo real usando o Couchbase Eventing. Vamos começar.
A configuração do balde
Para esta demonstração, você precisará de um total de 4 baldes:
- metadata (metadados) - esse é o compartimento de metadados obrigatório que contém todas as informações para executar suas Eventing Functions.
- staging - esse bucket funcionará como uma zona de aterrissagem para todos os seus dados de usuário.
- fieldindex - esse bucket funcionará como um índice de informações de campo que precisamos para combinar os usuários
- usuários - este bloco é o nosso produto final: um conjunto de dados de usuários conciso
A configuração de funções
Quando criarmos nossa função de deduplicação (chamei a minha de "dedupe"), aponte o bucket de metadados para "metadata" e o bucket de origem para "staging". Você também precisará definir dois aliases: "users" pode apontar para o bucket de usuários e "fieldindex" pode apontar para o bucket de fieldindex.
O código de função
Para esta demonstração, consideraremos um cenário de deduplicação muito simples: Queremos uma lista concisa de usuários com base em seu endereço de e-mail e, para cada usuário, uma lista de todos os documentos originais que contêm esse e-mail para fins de auditoria posterior.
Para este exemplo, trabalharemos com documentos muito simples. Aqui está um para lhe dar uma ideia:
|
1 2 3 |
"test::1" { "email": "abc@abc.com" } |
O código da função na íntegra tem a seguinte aparência:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
function OnUpdate(doc, meta) { var email = getEmailFromDoc(doc) if (email != null) { var user = getUserFromEmail(email) var userDoc = {} if (user != null) { userDoc = users[user] userDoc.matches = userDoc.matches.concat([meta.id]) } else { userDoc = doc userDoc.userid = meta.id userDoc.created = Date.now() userDoc.matches = [meta.id] } users[userDoc.userid] = userDoc addEmailToFieldIndex(email, userDoc.userid) } else { // doc does not have an email and is not valid for deduplication } } function getEmailFromDoc(doc) { for (var key in doc) { var field = key.toLowerCase() if (field.includes("email")) { return doc[key] } } return null } function getUserFromEmail(email) { try { return fieldindex["email::" + email].userid } catch (e) { // user was not found, let's just return null } return null } function addEmailToFieldIndex(email, user) { fieldindex["email::" + email] = {"userid": user} } |
Analisando o documento
Começamos com a função OnUpdate(doc, meta). Essa função é chamada para cada mutação de documentos no bucket de preparação, ou seja, sempre que um dos nossos documentos brutos é adicionado ou atualizado. A primeira etapa é determinar o endereço de e-mail do documento recebido. Para fazer isso, temos uma pequena função auxiliar: getEmailFromDoc(doc). Essa função simplesmente percorre todos os nomes de campo do documento em busca de um que contenha a string "email" e, em seguida, retorna esse valor. Nessa função, você também pode optar por procurar nomes de campos específicos. É preciso ter em mente que, se você tiver várias fontes de dados aqui, cada fonte poderá usar nomes de campo diferentes, portanto, uma abordagem generalizada acima pode funcionar para você.
Encontrar uma correspondência
Com o endereço de e-mail em mãos, é hora de seguir em frente. Se não tivermos um e-mail, estaremos simplesmente desconsiderando o documento neste exemplo. Supondo que obtivemos um endereço de e-mail com sucesso, a próxima etapa é recuperar nosso usuário canônico do bucket "users" se esse usuário já existir. Para fazer isso, temos uma segunda função auxiliar: getUserFromEmail(email). Essa função utiliza nosso bucket "fieldindex", que usaremos simplesmente como um valor-chave para procurar e-mails. Buscamos no compartimento "fieldindex" um documento que corresponda ao nosso e-mail. Você notará que prefixamos as chaves no "fieldindex" com um tipo de dado, o que permitirá que você adicione mais tipos de dados posteriormente sem se confundir! Se encontrarmos o e-mail em "fieldindex", retornaremos o ID de usuário associado; caso contrário, apenas nulo.
Deduplicação
De volta à função OnUpdate, se conseguirmos encontrar um ID de usuário existente, recuperaremos esse usuário existente do bucket "users" e simplesmente adicionaremos o meta.id dos nossos documentos atuais à lista de documentos correspondentes. Se não encontrarmos um ID de usuário existente, teremos de criar um novo usuário. Para fazer isso, basta começar com o documento original e adicionar alguns campos, incluindo um carimbo de data/hora de criação e o início de um campo de correspondências que conterá uma lista de documentos correspondentes ao e-mail desse usuário.
Depois de obter ou criar um novo documento de usuário, armazenamos esse documento no bucket "users". Precisamos de um meta.id para armazenar o usuário. Neste exemplo, estamos simplesmente usando o meta.id do documento original que gera esse usuário, mas você pode escolher outra coisa, fica a seu critério. Se o usuário já existir, ele será simplesmente substituído.
Indexação do usuário
Por fim, temos que atualizar nosso bucket "fieldindex" para que os futuros documentos com esse endereço de e-mail possam ser identificados corretamente. Para fazer isso, usamos nossa função auxiliar final: addEmailToFieldIndex(email, user). Essa função simplesmente grava o e-mail fornecido como uma chave e o ID do usuário fornecido como o corpo do documento no bucket "fieldindex".
E é isso! Em apenas 50 linhas de código, você tem o início de um mecanismo de desduplicação de usuários em tempo real. Vamos testá-lo.
Testando a função
Depois de implementar essa função, podemos testá-la criando alguns documentos em nosso bucket de "preparação".
Primeiro, vamos criar um usuário de linha de base. Crie um novo documento no bucket "staging" com a seguinte aparência:
|
1 2 3 |
"test::1" { "email": "abc@abc.com" } |
Você poderá ver os efeitos imediatos nos compartimentos "fieldindex" e "users". Em "fieldindex", temos nosso documento de chave/valor criado:
|
1 2 3 |
email::abc@abc.com { "userid":"test::1" } |
Observe que ele é simplesmente o e-mail que aponta para um ID de usuário que, nesse caso, é o meta.id do nosso primeiro documento original: test::1.
Em seguida, observe o bucket "users" (usuários). Aqui temos o novo usuário que foi criado:
|
1 2 3 4 5 6 |
test::1 { "email":"abc@abc.com", "userid":"test::1", "created":1546531965825, "matches":["test::1"] } |
Observe que temos o endereço de e-mail original, bem como alguns campos adicionais, incluindo uma lista completa de documentos correspondentes (que, neste momento, é apenas o nosso primeiro documento).
Vamos adicionar mais alguns documentos para testar nossos casos extremos. Adicione os dois documentos a seguir ao bucket "staging":
|
1 2 3 4 5 6 7 |
test::2 { "name":"Tavi" } test::3 { "useremail":"abc@abc.com" } |
O documento test::2 não tem um campo de e-mail e serve apenas para testar o fato de que estamos desconsiderando documentos que não têm um e-mail.
O documento test::3 testa algumas coisas. Primeiro, o campo de e-mail é chamado de "useremail", mas isso deve ser detectado sem problemas devido à nossa maneira geral de localizar campos de e-mail. Segundo, ele tem um e-mail correspondente! Vamos verificar o nosso grupo de "usuários" e ver como as coisas ficaram. Ainda temos apenas um usuário, mas o documento é um pouco diferente:
|
1 2 3 4 5 6 |
test::1 { "email":"abc@abc.com", "userid":"test::1", "created":1546531965825, "matches":["test::1", "test::3"] } |
Observe aqui que a única diferença é que agora temos uma referência ao documento test::3 em nosso campo de correspondências. Viva! Conseguimos deduzir esses documentos com sucesso.
Conclusão
Conforme mostrado acima, o novo sistema Eventing do Couchbase pode ser usado para criar um mecanismo de deduplicação de usuários em tempo real. Embora esse exemplo tenha sido extremamente simplificado, o javascript (a linguagem escolhida para Eventing Functions) é extremamente versátil e você pode fazer praticamente tudo o que quiser.
Um mecanismo de desduplicação mais funcional pode levar em conta muito mais campos do que apenas os e-mails dos usuários. Ele pode usar heurísticas muito mais complexas para fazer a correspondência entre os documentos. Você pode até considerar um segundo nível de correspondência em que agrupa usuários específicos em Households com base em suas informações de identificação. Tudo isso depende de você, mas com o Couchbase Eventing a deduplicação em tempo real é facilitada.