Armazenamento em cache por dicionários na memória. Estamos fazendo tudo errado?

8

Essa abordagem é praticamente a maneira aceita de fazer qualquer coisa em nossa empresa. Um exemplo simples: quando uma parte de dados de um cliente é solicitada a partir de um serviço, buscamos todos os dados para esse cliente (parte relevante para o serviço) e os salvamos em um dicionário na memória e os servimos a partir daí nas solicitações a seguir (executamos serviços singleton). Qualquer atualização vai para o DB e atualiza o dicionário na memória. Parece tudo simples e inofensivo, mas à medida que implementamos regras comerciais mais complicadas, o cache fica fora de sincronia e precisamos lidar com erros difíceis de encontrar. Às vezes, adiamos a gravação no banco de dados, mantendo novos dados em cache até então. Há casos em que armazenamos milhões de linhas na memória porque a tabela possui muitas relações com outras tabelas e precisamos mostrar dados agregados rapidamente.

Todo esse manuseio de cache é uma grande parte de nossa base de código e sinto que não é o caminho certo para fazê-lo. Todo esse malabarismo adiciona muito ruído ao código e dificulta a compreensão da lógica real dos negócios. No entanto, acho que não podemos fornecer dados em um período de tempo razoável se precisarmos acessar o banco de dados todas as vezes.

Estou infeliz com a situação atual, mas não tenho uma alternativa melhor. Minha única solução seria usar o cache de segundo nível do NHibernate, mas quase não tenho experiência com ele. Sei que muitos campanies usam Redis ou MemCached pesadamente para obter desempenho, mas não tenho idéia de como os integraria ao nosso sistema. Também não sei se eles podem ter um desempenho melhor do que consultas e estruturas de dados na memória.

Existem abordagens alternativas que eu deveria procurar?

user73983
fonte

Respostas:

9

Primeiro você última pergunta: Por que Redis / memcached?

Não, eles não são (geralmente) mais rápidos que os simples dicionários em processo. A vantagem surge quando você tem vários processos de trabalho ou mesmo muitas máquinas da camada de aplicativos. Nesse caso, em vez de cada processo ter seu próprio cache pequeno, todos compartilham um único cache grande (distribuído). Com caches maiores, você obtém melhores taxas de acerto.

Como você pode ver, a camada de cache se torna um recurso compartilhado, muito parecido com o banco de dados, mas (espero) mais rápido.

Agora, sobre a grande parte: como evitar a bagunça?

Parece que seu problema é manter o cache consistente e, ao mesmo tempo, separá-lo do banco de dados. Eu vejo três pontos de dor lá:

  1. invalidação de cache. Isto é apenas difícil. Às vezes, a solução mais fácil é adicionar um ID de geração a cada registro e usá-lo como parte da chave do cache. Quando os dados são atualizados, você obtém um ID de nova geração e a próxima consulta de cache não será atingida, então você acessa o banco de dados e atualiza o cache. Obviamente, a entrada (agora não utilizada) deve ter um prazo de validade razoável, para que seja removida do cache.

  2. Escreva de volta. Você diz que trabalha no cache e atualiza o banco de dados posteriormente. Isso é perigoso; a maioria das arquiteturas evita essa ideia. Uma etapa na direção certa seria marcar todas as entradas novas ou modificadas no cache como 'sujas', para que possam ser liberadas no banco de dados por um processo dissociado. Uma idéia melhor pode ser adicionar a uma fila de mensagens assim que ela for modificada, tornando a gravação no banco de dados efetivamente 'inline but assync'. No final, acho que você deve perceber que esse não é um uso válido para um cache; é uma "área de preparação" que deve ser tratada com uma arquitetura diferente da camada de cache.

  3. sincronização interprocessos: como o cache em processo é privado para cada processo, qualquer modificação não é propagada para outros processos até que sejam liberados no banco de dados. Isso pode estar correto no design do seu aplicativo (tipo de isolamento de transação do pobre homem), mas pode ter resultados indesejados. Uma arquitetura muito mais gerenciável é uma camada de cache que é apenas uma API mais rápida para o banco de dados, com as mesmas propriedades compartilhadas que o banco de dados e tão 'autoritativa' quanto ela. Para isso, você precisa de caches fora de processo, como memcached ou Redis.

Javier
fonte
8
Há apenas duas coisas difíceis na Ciência da Computação: invalidação de cache e nomeação de coisas.
22812 Michael Borgwardt
12
Há apenas duas coisas difíceis na Ciência da Computação: invalidação de cache, nomeação de coisas e erros pontuais.
Matthew King
2
@ MatthewKing Existem apenas três coisas difíceis em Ciência da Computação: erros de dois em dois.
Jimmy Hoffa
@MatthewKing, eu amo o humor. :)
Anthony Gatlin