Elasticsearch é um mecanismo de pesquisa e análise de código aberto baseado no Apache Lucene. Ao criar aplicativos em dados de captura de dados de alteração (CDC) usando o Elasticsearch, você desejará arquitetar o sistema para lidar com atualizações ou modificações frequentes nos documentos existentes em um índice.
Neste blog, examinaremos as diferentes opções disponíveis para atualizações, incluindo atualizações completas, atualizações parciais e atualizações com script. Também discutiremos o que acontece nos bastidores do Elasticsearch ao modificar um documento e como as atualizações frequentes afetam a utilização da CPU no sistema.
Para entender melhor os casos de uso que possuem atualizações frequentes , vejamos um aplicativo de busca para um serviço de streaming de vídeo como o Netflix. Quando um usuário pesquisa um programa, ou seja, “thriller político”, ele recebe um conjunto de resultados relevantes com base em palavras-chave e outros metadados.
Vejamos um exemplo de documento no Elasticsearch do programa “House of Cards”:
{ "name": "House of Cards", "description": "Frank Underwood is a Democrat appointed as the Secretary of State. Along with his wife, he sets out on a quest to seek revenge from the people who betrayed him while successfully rising to supremacy.", "genres": ["drama", "thriller"], "views": 100, }
A pesquisa pode ser configurada no Elasticsearch para usar name
e description
como campos de pesquisa de texto completo. O campo views
, que armazena o número de visualizações por título, pode ser usado para impulsionar o conteúdo, classificando os programas mais populares em posições mais altas. O campo views
é incrementado toda vez que um usuário assiste a um episódio de um programa ou filme.
Ao usar essa configuração de pesquisa em um aplicativo da escala da Netflix , o número de atualizações realizadas pode facilmente ultrapassar milhões por minuto, conforme determinado pelo Relatório de engajamento da Netflix . De acordo com o Relatório, os usuários assistiram cerca de 100 bilhões de horas de conteúdo de janeiro a julho. Supondo um tempo médio de exibição de 15 minutos por episódio ou filme, o número de visualizações por minuto chega a 1,3 milhão em média. Com a configuração de pesquisa especificada acima, cada visualização exigiria uma atualização na escala de milhões.
Muitos aplicativos de pesquisa e análise podem passar por atualizações frequentes, especialmente quando criados com base em dados do CDC.
Vamos nos aprofundar em um exemplo geral de como realizar uma atualização no Elasticsearch com o código abaixo:
- from elasticsearch import Elasticsearch # Connect to your Elasticsearch instance es = Elasticsearch([{'host': 'localhost', 'port': 9200}]) # Index name and document ID you want to update index_name = 'movies' document_id = 'your_document_id' # Retrieve the current document to get the current 'views' value try: current_doc = es.get(index=index_name, id=document_id) current_views = current_doc['_source']['views'] except Exception as e: print(f"Error retrieving current document: {e}") current_views = 0 # Set a default value if there's an error # Define the update body to increment 'views' by 1 update_body = { "doc": { "views": current_views + 1 # Increment 'views' by 1 } } # Perform the update try: es.update(index=index_name, id=document_id, body=update_body) print("Document updated successfully!") except Exception as e: print(f"Error updating document: {e}")
Ao realizar uma atualização no Elasticsearch, você pode usar a API de índice para substituir um documento existente ou a API de atualização para fazer uma atualização parcial em um documento.
A API de índice recupera o documento inteiro, faz alterações no documento e, em seguida, reindexa o documento. Com a API de atualização, basta enviar os campos que deseja modificar, em vez do documento inteiro. Isso ainda resulta na reindexação do documento, mas minimiza a quantidade de dados enviados pela rede. A API de atualização é especialmente útil nos casos em que o tamanho do documento é grande e o envio do documento inteiro pela rede será demorado.
Vamos ver como a API de índice e a API de atualização funcionam usando código Python.
from elasticsearch import Elasticsearch # Connect to Elasticsearch es = Elasticsearch([{'host': 'localhost', 'port': 9200}]) # Index name and document ID index_name = "your_index" document_id = "1" # Retrieve the existing document existing_document = es.get(index=index_name, id=document_id) # Make your changes to the document existing_document["_source"]["field1"] = "new_value1" existing_document["_source"]["field2"] = "new_value2" # Call the index API to perform the full update es.index(index=index_name, id=document_id, body=existing_document["_source"])
Como você pode ver no código acima, a API de índice requer duas chamadas separadas para Elasticsearch, o que pode resultar em desempenho mais lento e maior carga em seu cluster.
As atualizações parciais usam internamente a API reindex , mas foram configuradas para exigir apenas uma única chamada de rede para melhor desempenho.
from elasticsearch import Elasticsearch # Connect to Elasticsearch es = Elasticsearch([{'host': 'localhost', 'port': 9200}]) # Index name and document ID index_name = "your_index" document_id = "1" # Specify the fields to be updated update_fields = { "field1": "new_value1", "field2": "new_value2" } # Use the update API to perform a partial update es.update(index=index_name, id=document_id, body={"doc": update_fields})
Você pode usar a API de atualização no Elasticsearch para atualizar a contagem de visualizações, mas, por si só, a API de atualização não pode ser usada para incrementar a contagem de visualizações com base no valor anterior. Isso ocorre porque precisamos da contagem de visualizações mais antiga para definir o novo valor de contagem de visualizações.
Vamos ver como podemos consertar isso usando uma linguagem de script poderosa, Painless.
Painless é uma linguagem de script projetada para Elasticsearch e pode ser usada para cálculos de consulta e agregação, condicionais complexas, transformações de dados e muito mais. Painless também permite o uso de scripts em consultas de atualização para modificar documentos com base em lógica complexa.
No exemplo abaixo, usamos um script Painless para realizar uma atualização em uma única chamada de API e incrementar a nova contagem de visualizações com base no valor da contagem de visualizações antiga.
from elasticsearch import Elasticsearch # Connect to your Elasticsearch instance es = Elasticsearch([{'host': 'localhost', 'port': 9200}]) # Index name and document ID you want to update index_name = 'movies' document_id = 'your_document_id' # Define the Painless script for the update update_script = { "script": { "lang": "painless", "source": "ctx._source.views += 1" # Increment 'views' by 1 } } # Perform the update using the Painless script try: es.update(index=index_name, id=document_id, body=update_script) print("Document updated successfully!") except Exception as e: print(f"Error updating document: {e}")
O script Painless é bastante intuitivo de entender, ele simplesmente aumenta a contagem de visualizações em 1 para cada documento.
Objetos aninhados no Elasticsearch são uma estrutura de dados que permite a indexação de matrizes de objetos como documentos separados dentro de um único documento pai. Objetos aninhados são úteis ao lidar com dados complexos que formam naturalmente uma estrutura aninhada, como objetos dentro de objetos. Em um documento típico do Elasticsearch, os arrays de objetos são nivelados, mas o uso do tipo de dados aninhados permite que cada objeto no array seja indexado e consultado de forma independente.
Scripts simples também podem ser usados para atualizar objetos aninhados no Elasticsearch.
from elasticsearch import Elasticsearch # Connect to your Elasticsearch instance es = Elasticsearch([{'host': 'localhost', 'port': 9200}]) # Index name and document ID for the example index_name = 'your_index' document_id = 'your_document_id' # Specify the nested field and the updated value nested_field = "nested_field_name" updated_value = "new_value" # Define the Painless script for the update update_script = { "script": { "lang": "painless", "source": "ctx._source.nested_field_name = params.updated_value", "params": { "updated_value": updated_value } } } # Perform the update using the Update API and the Painless script try: es.update(index=index_name, id=document_id, body=update_script) print("Nested object updated successfully!") except Exception as e: print(f"Error updating nested object: {e}")
Adicionar um novo campo a um documento no Elasticsearch pode ser realizado por meio de uma operação de índice.
Você pode atualizar parcialmente um documento existente com o novo campo usando a API de atualização. Quando o mapeamento dinâmico no índice está ativado, a introdução de um novo campo é simples. Basta indexar um documento contendo esse campo e o Elasticsearch descobrirá automaticamente o mapeamento adequado e adicionará o novo campo ao mapeamento.
Com o mapeamento dinâmico no índice desabilitado, você precisará usar a API de mapeamento de atualização. Você pode ver abaixo um exemplo de como atualizar o mapeamento do índice adicionando um campo “categoria” ao índice de filmes.
PUT /movies/_mapping { "properties": { "category": { "type": "keyword" } } }
Embora o código seja simples, o Elasticsearch internamente está fazendo muito trabalho pesado para realizar essas atualizações porque os dados são armazenados em segmentos imutáveis. Como resultado, o Elasticsearch não pode simplesmente fazer uma atualização local em um documento. A única maneira de realizar uma atualização é reindexar o documento inteiro, independentemente de qual API é usada.
Elasticsearch usa Apache Lucene nos bastidores. Um índice Lucene é composto por um ou mais segmentos. Um segmento é uma estrutura de índice independente e imutável que representa um subconjunto do índice geral. Quando documentos são adicionados ou atualizados, novos segmentos Lucene são criados e documentos mais antigos são marcados para exclusão reversível. Com o tempo, à medida que novos documentos são adicionados ou os existentes são atualizados, vários segmentos podem se acumular. Para otimizar a estrutura do índice, Lucene mescla periodicamente segmentos menores em segmentos maiores.
Como cada operação de atualização é uma operação de reindexação, todas as atualizações são essencialmente inserções com exclusões suaves.
Existem implicações de custo em tratar uma atualização como uma operação de inserção. Por um lado, a exclusão suave de dados significa que os dados antigos ainda serão retidos por algum tempo, sobrecarregando o armazenamento e a memória do índice. A execução de operações de exclusão reversível, reindexação e coleta de lixo também causa um grande impacto na CPU, um custo que é agravado pela repetição dessas operações em todas as réplicas.
As atualizações podem ficar mais complicadas à medida que seu produto cresce e seus dados mudam com o tempo. Para manter o desempenho do Elasticsearch, você precisará atualizar os shards, analisadores e tokenizers em seu cluster, exigindo uma reindexação de todo o cluster. Para aplicativos de produção, isso exigirá a configuração de um novo cluster e a migração de todos os dados. A migração de clusters consome muito tempo e está sujeita a erros, por isso não é uma operação fácil.
A simplicidade das operações de atualização no Elasticsearch pode mascarar as tarefas operacionais pesadas que acontecem nos bastidores do sistema. O Elasticsearch trata cada atualização como uma inserção, exigindo que o documento completo seja recriado e reindexado. Para aplicativos com atualizações frequentes, isso pode rapidamente se tornar caro, como vimos no exemplo da Netflix, onde milhões de atualizações acontecem a cada minuto. Recomendamos fazer atualizações em lote usando a API em massa , que adiciona latência à sua carga de trabalho, ou procurar soluções alternativas quando se deparar com atualizações frequentes no Elasticsearch.
Rockset, um banco de dados de pesquisa e análise construído na nuvem, é uma alternativa mutável ao Elasticsearch. Sendo construído no RocksDB , um armazenamento de valores-chave popularizado por sua mutabilidade, o Rockset pode fazer atualizações locais em documentos. Isso resulta na atualização e reindexação apenas do valor de campos individuais, em vez de todo o documento.
Se quiser comparar o desempenho do Elasticsearch e do Rockset para cargas de trabalho com muitas atualizações, você pode iniciar uma avaliação gratuita do Rockset com US$ 300 em créditos.