Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Carregar dados sob demanda em um cache de um armazenamento de dados. Isso pode melhorar o desempenho e também ajuda a manter a consistência entre dados mantidos no cache e os dados no armazenamento de dados subjacente.
Contexto e problema
Os aplicativos usam um cache para melhorar o acesso repetido às informações mantidas em um armazenamento de dados. No entanto, não é realista esperar que os dados armazenados em cache sejam sempre consistentes com o armazenamento de dados. Os aplicativos devem implementar uma estratégia que ajude a garantir que os dados no cache sejam o mais up-todata possível. A estratégia também deve ser capaz de detectar quando os dados armazenados em cache ficam obsoletos e tratá-los adequadamente.
Solução
Muitos sistemas de armazenamento em cache comerciais fornecem operações de read-through e write-through/write-behind. Nesses sistemas, um aplicativo recupera dados referenciando o cache. Se os dados não estiverem no cache, o aplicativo os recuperará do armazenamento de dados e os adicionará ao cache. Todas as modificações nos dados mantidos no cache são automaticamente gravadas no armazenamento de dados também.
Para caches que não têm essa funcionalidade, é de responsabilidade dos aplicativos que usam o cache manter os dados.
Um aplicativo pode emular a funcionalidade de cache de read-through implementando a estratégia de cache-aside. Essa estratégia carrega os dados em cache sob demanda. A figura ilustra usando o padrão de Cache-Aside para armazenar dados no cache.
- O aplicativo determina se o item está atualmente mantido no cache tentando ler do cache.
- Se o item não estiver atual no cache (um erro de cache), o aplicativo recuperará o item do armazenamento de dados.
- O aplicativo adiciona o item ao cache e o retorna ao chamador.
Se um aplicativo atualiza as informações, ele pode seguir a estratégia de write-through modificando o armazenamento de dados e invalidando o item correspondente no cache.
Quando o item é necessário novamente, a estratégia de cache-aside recupera os dados atualizados do armazenamento de dados e os adiciona ao cache.
Problemas e considerações
Considere os seguintes pontos ao decidir como implementar esse padrão:
Tempo de vida dos dados armazenados em cache. Muitos caches usam uma política de expiração para invalidar dados e removê-los do cache se não forem acessados por um período definido. Para o cache-aside ser efetivado, verifique se a política de expiração corresponde ao padrão de acesso para aplicativos que usam os dados. Não torne o período de expiração muito curto porque a expiração prematura pode fazer com que os aplicativos recuperem continuamente os dados do armazenamento de dados e os adicionem ao cache. Da mesma forma, não faça o período de validade longo demais a ponto dos dados em cache se tornarem provavelmente obsoletos. Lembre-se de que o cache é mais eficiente para dados relativamente estáticos ou dados lidos com frequência.
Removendo dados. A maioria dos caches tem um tamanho limitado em comparação com o armazenamento de dados de origem dos dados. Se o cache exceder seu limite de tamanho, ele removerá os dados. A maioria dos caches adota uma política menos usada recentemente para selecionar itens a serem removidos, mas pode ser personalizável.
Configuração. A configuração de cache pode ser definida globalmente e por item armazenado em cache. Uma única política de remoção global pode não atender a todos os itens. Uma configuração em um item de cache poderá ser apropriada se um item for caro de recuperar. Nessa situação, faz sentido manter o item no cache, mesmo que ele seja acessado com menos frequência do que itens mais baratos.
Desobstrução do cache. Muitas soluções preenchem o cache com os dados que um aplicativo pode precisar como parte do processo de inicialização. O padrão de Cache-Aside ainda pode ser útil se alguns desses dados expirar ou for removido.
Consistência. Implementar o padrão de Cache-Aside não garante a consistência entre o armazenamento de dados e o cache. Por exemplo, um processo externo pode alterar um item no armazenamento de dados a qualquer momento. Essa alteração não aparece no cache até que o item seja carregado novamente. Em um sistema que replica dados em armazenamentos de dados, a consistência pode ser desafiadora se a sincronização ocorrer com frequência.
Armazenamento em cache local (na memória). Um cache pode ser local para uma instância de aplicativo e armazenado na memória. O cache-aside pode ser útil nesse ambiente, se um aplicativo acessar os mesmos dados repetidamente. No entanto, um cache local é privado e, por isso, diferentes instâncias do aplicativo podem ter uma cópia dos mesmos dados armazenados em cache. Esses dados podem rapidamente se tornar inconsistentes entre os caches, por isso é necessário expirar os dados mantidos em um cache privado e atualizá-los com mais frequência. Nesses cenários, considere a possibilidade de investigar o uso de um mecanismo de cache compartilhado ou distribuído.
Cache semântico. Algumas cargas de trabalho podem se beneficiar de fazer a recuperação de cache com base no significado semântico em vez de chaves exatas. Isso reduz o número de solicitações e tokens enviados para modelos de linguagem. Verifique se os dados armazenados em cache se beneficiam da equivalência semântica e não correm o risco de retornar respostas não relacionadas ou contêm dados privados e confidenciais. Por exemplo, "Qual é o meu salário anual para casa?" é semanticamente semelhante a "Qual é o meu salário anual de levar para casa?" mas se perguntado por dois usuários diferentes, então a resposta não deve ser a mesma, nem você gostaria de incluir esses dados confidenciais em seu cache.
Quando usar esse padrão
Use esse padrão quando:
- Um cache não fornece read-through nativo e as operações de write-through.
- A demanda de recursos é imprevisível. Esse padrão permite que os aplicativos carreguem dados sob demanda. Ele não faz suposições sobre quais dados um aplicativo requer com antecedência.
Esse padrão pode não ser adequado:
- Se os dados forem confidenciais ou relacionados à segurança. Pode ser inadequado armazená-lo em um cache, especialmente se o cache for compartilhado entre vários aplicativos ou usuários. Sempre vá para a fonte primária dos dados.
- Quando o conjunto de dados armazenados em cache é estático. Se os dados se ajustarem ao espaço de cache disponível, prime o cache com os dados na inicialização e aplique uma política que impeça a expiração dos dados.
- Quando a maioria das solicitações não experimenta um cache atingido. Nessa situação, a sobrecarga de verificar o cache e carregar dados nele pode superar os benefícios do cache.
- Ao armazenar em cache informações de estado de sessão em um aplicativo Web hospedado em um farm da Web. Nesse ambiente, você deve evitar introduzir dependências baseadas na afinidade de cliente-servidor.
Design de carga de trabalho
Um arquiteto deve avaliar como o padrão de Cache-Aside pode ser usado em um design para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. Por exemplo:
| Pilar | Como esse padrão apoia os objetivos do pilar |
|---|---|
| As decisões de design de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e a garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. | O cache cria replicação de dados e, de maneiras limitadas, pode ser usado para preservar a disponibilidade de dados acessados com frequência se o armazenamento de dados de origem estiver temporariamente indisponível. Além disso, se houver um mau funcionamento no cache, a carga de trabalho poderá retornar ao armazenamento de dados de origem. - RE:05 Redundância |
| A eficiência de desempenho ajuda sua carga de trabalho a atender com eficiência às demandas por meio de otimizações em dimensionamento, dados e código. | O uso de uma cabine de cache melhora o desempenho para dados de leitura pesada que são alterados com pouca frequência e podem tolerar alguma desatualização. - PE:08 Desempenho de dados - PE:12 Otimização contínua de desempenho |
Tal como acontece com qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com este padrão.
Exemplo
Considere usar o Redis Gerenciado do Azure para criar um cache distribuído que várias instâncias de aplicativo podem compartilhar.
Este exemplo de código a seguir usa o cliente StackExchange.Redis, que é uma biblioteca de cliente Redis gravada para o .NET. Para se conectar a uma instância do Redis Gerenciado do Azure, chame o método estático ConnectionMultiplexer.Connect e passe a cadeia de conexão. O método retorna um ConnectionMultiplexer que representa a conexão. Uma abordagem para compartilhar uma instância do ConnectionMultiplexer em seu aplicativo deve ter uma propriedade estática que retorna uma instância conectada, semelhante ao exemplo a seguir. Essa abordagem oferece uma maneira thread-safe de inicializar somente uma única instância conectada.
private static ConnectionMultiplexer Connection;
// Redis connection string information
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
return ConnectionMultiplexer.Connect(cacheConnection);
});
public static ConnectionMultiplexer Connection => lazyConnection.Value;
O método GetMyEntityAsync no exemplo de código a seguir mostra uma implementação do padrão Cache-Aside. Esse método recupera um objeto do cache usando a abordagem read-through.
Um objeto é identificado usando uma ID inteira como chave. O método GetMyEntityAsync tenta recuperar um item com essa chave do cache. Se um item correspondente for encontrado, o cache o retornará. Se não houver nenhuma correspondência no cache, o método GetMyEntityAsync recupera o objeto de um armazenamento de dados, adiciona-o ao cache e, em seguida, retorna-o. O código que lê os dados do armazenamento de dados não é mostrado aqui, pois depende do armazenamento de dados. O item armazenado em cache está configurado para expirar para evitar que ele fique obsoleto se outro serviço ou processo o atualizar.
// Set five minute expiration as a default
private const double DefaultExpirationTimeInMinutes = 5.0;
public async Task<MyEntity> GetMyEntityAsync(int id)
{
// Define a unique key for this method and its parameters.
var key = $"MyEntity:{id}";
var cache = Connection.GetDatabase();
// Try to get the entity from the cache.
var json = await cache.StringGetAsync(key).ConfigureAwait(false);
var value = string.IsNullOrWhiteSpace(json)
? default(MyEntity)
: JsonConvert.DeserializeObject<MyEntity>(json);
if (value == null) // Cache miss
{
// If there's a cache miss, get the entity from the original store and cache it.
// Code has been omitted because it is data store dependent.
value = ...;
// Avoid caching a null value.
if (value != null)
{
// Put the item in the cache with a custom expiration time that
// depends on how critical it is to have stale data.
await cache.StringSetAsync(key, JsonConvert.SerializeObject(value)).ConfigureAwait(false);
await cache.KeyExpireAsync(key, TimeSpan.FromMinutes(DefaultExpirationTimeInMinutes)).ConfigureAwait(false);
}
}
return value;
}
Os exemplos usam Redis Gerenciados do Azure para acessar o repositório e recuperar informações do cache. Para obter mais informações, consulte Criar um Redis Gerenciado do Azure e usar o Azure Redis no .NET Core.
O UpdateEntityAsync método mostrado abaixo demonstra como invalidar um objeto no cache quando o aplicativo altera o valor. O código atualiza o armazenamento de dados original e, em seguida, remove o item do cache.
public async Task UpdateEntityAsync(MyEntity entity)
{
// Update the object in the original data store.
await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);
// Invalidate the current cache object.
var cache = Connection.GetDatabase();
var id = entity.Id;
var key = $"MyEntity:{id}"; // The key for the cached object.
await cache.KeyDeleteAsync(key).ConfigureAwait(false); // Delete this key from the cache.
}
Observação
A ordem das etapas é importante. Atualize o armazenamento de dados antes de remover o item do cache. Se você remover o item armazenado em cache primeiro, haverá uma pequena janela de tempo em que um cliente pode buscar o item antes que o armazenamento de dados seja atualizado. Nessa situação, a busca resulta em uma falha de cache (porque o item foi removido do cache). A falha de cache faz com que a versão anterior do item seja buscada no armazenamento de dados e adicionada novamente ao cache. O resultado são dados de cache obsoletos.
Recursos relacionados
As informações a seguir também podem ser relevantes ao implementar esse padrão:
O padrão de aplicativo web confiável mostra como aplicar o padrão de cache-aside a aplicativos web convergindo para a nuvem.
Diretrizes de cache. Fornece informações adicionais sobre como você pode armazenar dados em cache em uma solução de nuvem, e os problemas que você deve considerar ao implementar um cache.
Primer de Consistência de Dados. Os aplicativos de nuvem normalmente armazenam dados em vários repositórios de dados e locais. Gerenciar e manter a consistência de dados nesse ambiente é um aspecto crítico do sistema, particularmente os problemas de simultaneidade e disponibilidade que podem surgir. Este primer descreve problemas sobre a consistência entre dados distribuídos e resume como um aplicativo pode implementar a consistência eventual para manter a disponibilidade dos dados.
Use o Redis Gerenciado do Azure como um cache semântico. Este tutorial mostra como implementar o cache semântico usando o Redis Gerenciado do Azure.