Hazelcast

Data Grid e processamento distribuído

Domine IMap, entry processors, particionamento, backups e o modelo de processamento onde os dados vivem no Hazelcast.

Intermediário 45 min 35 pontos Leitura 0%

Nesta aula você vai

  • Configurar mapas distribuídos com políticas de eviction, TTL e backup
  • Implementar entry processors para lógica atômica nos dados
  • Explicar como o particionamento afeta performance e tolerância a falhas

Data Grid e processamento distribuído

Objetivos

Nesta aula você vai:

  • Configurar um data grid Hazelcast com parâmetros de produção conscientes
  • Executar lógica nos dados com entry processors em vez de round-trips desnecessários
  • Entender particionamento, backups e o impacto em latência e disponibilidade

Introdução

Um data grid é como uma narrativa contada por vários narradores simultâneos, cada um responsável por um capítulo — mas o leitor deve perceber uma história única. O Hazelcast IMap é esse contrato: chaves distribuídas em partições, backups em nós distintos, operações que parecem locais mas atravessam a rede quando necessário.

A diferença entre usar Hazelcast como "Redis em Java" e usar como data grid está no processamento: mover código até os dados, não dados até o código. Esta aula ensina essa inversão — a mesma que separa leitura passiva de crítica literária ativa.

Conteúdo

IMap — o coração do data grid

IMap<K,V> é um ConcurrentHashMap distribuído. Operações básicas:

IMap<String, Integer> estoque = hz.getMap("estoque-produtos");

estoque.put("SKU-42", 100);
estoque.putIfAbsent("SKU-99", 50);

Integer qtd = estoque.get("SKU-42");
boolean removido = estoque.remove("SKU-42", 100);

Configuração declarativa (hazelcast.xml ou programática):

<map name="estoque-produtos">
  <backup-count>1</backup-count>
  <async-backup-count>0</async-backup-count>
  <time-to-live-seconds>3600</time-to-live-seconds>
  <max-idle-seconds>1800</max-idle-seconds>
  <eviction eviction-policy="LRU" max-size-policy="PER_NODE" size="10000"/>
</map>
Parâmetro Função
backup-count Cópias síncronas da partição em outros nós
time-to-live-seconds Expiração absoluta da entrada
max-idle-seconds Expiração por inatividade
eviction Remove entradas quando mapa excede limite por nó

Particionamento e data locality

Chaves são hasheadas para uma de N partições (padrão: 271). Cada partição tem um owner — o membro responsável por operações de escrita.

chave "SKU-42" → hash → Partição 17 → Owner: Nó B
  • Leitura no owner — latência mínima
  • Leitura no backup — possível com configuração, reduz carga no owner
  • Leitura em nó sem cópia — proxy remoto, latência de rede

Para minimizar tráfego, co-localize processamento e dados com entry processors.

Entry Processors — lógica no lugar certo

Anti-padrão:

// Duas viagens de rede: GET + PUT
Integer atual = estoque.get("SKU-42");
estoque.put("SKU-42", atual - 1);

Padrão correto:

import com.hazelcast.map.EntryProcessor;

public class DecrementarEstoque implements EntryProcessor<String, Integer, Integer> {
  @Override
  public Integer process(Map.Entry<String, Integer> entry) {
    Integer valor = entry.getValue();
    if (valor == null || valor <= 0) return -1;
    entry.setValue(valor - 1);
    return valor - 1;
  }
}

// Uma viagem — executa no owner da partição
Integer restante = estoque.executeOnKey("SKU-42", new DecrementarEstoque());

Entry processors executam atomicamente na partição — equivalente conceitual a script Lua no Redis, mas com código Java tipado e testável.

Processamento em lote e predicados

Para operações em múltiplas chaves:

import com.hazelcast.query.Predicates;

// Atualiza todas as entradas com estoque < 10
estoque.executeOnEntries(
  entry -> {
    if (entry.getValue() < 10) entry.setValue(0);
    return null;
  },
  Predicates.lessThan("this", 10)
);

Cuidado: executeOnEntries sem predicado varre todo o mapa — operação cara em datasets grandes. Sempre filtre.

Near Cache — leitura local

Clientes Java podem habilitar Near Cache — cópia local das entradas mais acessadas:

ClientConfig config = new ClientConfig();
NearCacheConfig nearCache = new NearCacheConfig("estoque-produtos")
  .setInvalidateOnChange(true);
config.addNearCacheConfig(nearCache);
Benefício Custo
Leituras locais em microssegundos Memória adicional no cliente
Reduz tráfego de rede Risco de dados stale se invalidação falhar

Ideal para catálogos lidos intensamente, raramente alterados.

Jet — streaming e batch (visão geral)

Hazelcast Jet processa fluxos de dados distribuídos — Kafka, JDBC, IMap — com pipeline declarativo:

// Conceitual — pipeline que conta eventos por tipo
Pipeline p = Pipeline.create();
p.readFrom(Sources.mapJournal("eventos", START_FROM_OLDEST))
 .groupingKey(e -> e.getTipo())
 .aggregate(AggregateOperations.counting())
 .writeTo(Sinks.logger());

Jet transforma Hazelcast de data grid em motor de stream processing leve — alternativa a Flink/Spark para cargas moderadas integradas ao mesmo cluster.

Tolerância a falhas

Com backup-count=1, perda de um nó não perde dados — partições promovidas de backup a owner. Tempo de recuperação depende do tamanho das partições e número de backups.

Nó B (owner P17) falha → Nó C (backup P17) promovido → Cluster reequilibrado

Monitore eventos de MEMBER_REMOVED e métricas de rebalancing. Durante rebalancing, latência pode aumentar temporariamente.

Resumo

  • IMap é mapa distribuído com TTL, eviction e backups configuráveis por mapa
  • Particionamento determina qual nó é owner de cada chave — afeta latência de leitura/escrita
  • Entry processors executam lógica atomicamente no owner, evitando round-trips GET/PUT
  • Near Cache acelera leituras no cliente à custa de memória e complexidade de invalidação
  • Jet estende Hazelcast para processamento de streams integrado ao data grid
  • Backups síncronos garantem durabilidade em memória clusterizada — não substituem persistência em disco para dados críticos