Antipadrão Sem Colocação em CacheNo Caching antipattern

Numa aplicação de cloud que processe demasiados pedidos simultâneos, obter repetidamente os mesmos dados pode reduzir o desempenho e a escalabilidade.In a cloud application that handles many concurrent requests, repeatedly fetching the same data can reduce performance and scalability.

Descrição do problemaProblem description

Quando os dados não são colocados em cache, pode causar vários comportamentos indesejáveis, incluindo:When data is not cached, it can cause a number of undesirable behaviors, including:

  • Obter repetidamente as mesmas informações de um recurso que é dispendioso de aceder, em termos de custos gerais de E/S ou latência.Repeatedly fetching the same information from a resource that is expensive to access, in terms of I/O overhead or latency.
  • Construir repetidamente os mesmos objetos ou estruturas de dados para múltiplos pedidos.Repeatedly constructing the same objects or data structures for multiple requests.
  • Efetuar chamadas excessivas para um serviço remoto com uma quota de serviço e limites para os clientes que ultrapassem um determinado valor.Making excessive calls to a remote service that has a service quota and throttles clients past a certain limit.

Por sua vez, estes problemas podem originar fracos tempos de resposta, maior contenção no arquivo de dados e fraca escalabilidade.In turn, these problems can lead to poor response times, increased contention in the data store, and poor scalability.

O exemplo seguinte utiliza o Entity Framework para ligar a uma base de dados.The following example uses Entity Framework to connect to a database. Cada pedido de cliente resulta numa chamada para a base de dados, mesmo se múltiplos pedidos estejam a obter exatamente os mesmos dados.Every client request results in a call to the database, even if multiple requests are fetching exactly the same data. O custo de pedidos repetidos, em termos de custos gerais de E/S e custos de acesso a dados, pode acumular-se rapidamente.The cost of repeated requests, in terms of I/O overhead and data access charges, can accumulate quickly.

public class PersonRepository : IPersonRepository
{
    public async Task<Person> GetAsync(int id)
    {
        using (var context = new AdventureWorksContext())
        {
            return await context.People
                .Where(p => p.Id == id)
                .FirstOrDefaultAsync()
                .ConfigureAwait(false);
        }
    }
}

Pode encontrar o exemplo completo aqui.You can find the complete sample here.

Este anti-padrão ocorre normalmente porque:This antipattern typically occurs because:

  • A não utilização de uma cache é mais simples de implementar e funciona bem com pouca carga.Not using a cache is simpler to implement, and it works fine under low loads. A colocação em cache torna o código mais complicado.Caching makes the code more complicated.
  • As vantagens e desvantagens de utilizar uma cache não são compreendidas claramente.The benefits and drawbacks of using a cache are not clearly understood.
  • Não existe nenhuma preocupação relativamente aos custos gerais de manter a precisão e a atualização dos dados em cache.There is concern about the overhead of maintaining the accuracy and freshness of cached data.
  • Uma aplicação foi migrada de um sistema no local, em que a latência de rede não foi um problema e o sistema foi executado em hardware de elevado desempenho dispendioso, pelo que a colocação em cache não foi considerada na conceção original.An application was migrated from an on-premises system, where network latency was not an issue, and the system ran on expensive high-performance hardware, so caching wasn't considered in the original design.
  • Os programadores não têm consciência de que a colocação em cache é uma possibilidade num determinado cenário.Developers aren't aware that caching is a possibility in a given scenario. Por exemplo, os programadores podem não pensar em utilizar ETags quando implementarem uma API Web.For example, developers may not think of using ETags when implementing a web API.

Como resolver o problemaHow to fix the problem

A estratégia de colocação em cache mais popular é a pedido ou cache-aside.The most popular caching strategy is the on-demand or cache-aside strategy.

  • Na leitura, a aplicação tenta ler os dados da cache.On read, the application tries to read the data from the cache. Se os dados não estiverem na cache, a aplicação obtém os mesmos da origem de dados e adiciona-os à cache.If the data isn't in the cache, the application retrieves it from the data source and adds it to the cache.
  • Na escrita, a aplicação escreve a alteração diretamente na origem de dados e remove o valor antigo da cache.On write, the application writes the change directly to the data source and removes the old value from the cache. Será obtido e adicionado à cache da próxima vez que for necessário.It will be retrieved and added to the cache the next time it is required.

Esta abordagem é adequada para dados alterados com frequência.This approach is suitable for data that changes frequently. Segue-se o exemplo anterior atualizado para utilizar o padrão Cache-Aside.Here is the previous example updated to use the Cache-Aside pattern.

public class CachedPersonRepository : IPersonRepository
{
    private readonly PersonRepository _innerRepository;

    public CachedPersonRepository(PersonRepository innerRepository)
    {
        _innerRepository = innerRepository;
    }

    public async Task<Person> GetAsync(int id)
    {
        return await CacheService.GetAsync<Person>("p:" + id, () => _innerRepository.GetAsync(id)).ConfigureAwait(false);
    }
}

public class CacheService
{
    private static ConnectionMultiplexer _connection;

    public static async Task<T> GetAsync<T>(string key, Func<Task<T>> loadCache, double expirationTimeInMinutes)
    {
        IDatabase cache = Connection.GetDatabase();
        T value = await GetAsync<T>(cache, key).ConfigureAwait(false);
        if (value == null)
        {
            // Value was not found in the cache. Call the lambda to get the value from the database.
            value = await loadCache().ConfigureAwait(false);
            if (value != null)
            {
                // Add the value to the cache.
                await SetAsync(cache, key, value, expirationTimeInMinutes).ConfigureAwait(false);
            }
        }
        return value;
    }
}

Tenha em atenção que o método GetAsync chama agora a classe CacheService, em vez de chamar diretamente a base de dados.Notice that the GetAsync method now calls the CacheService class, rather than calling the database directly. Primeiro, a classe CacheService tenta obter o item da Cache de Redis do Azure.The CacheService class first tries to get the item from Azure Redis Cache. Se o valor não for encontrado na Cache de Redis, CacheService invoca uma função lambda transmitida ao mesmo pelo chamador.If the value isn't found in Redis Cache, the CacheService invokes a lambda function that was passed to it by the caller. A função lambda é responsável por obter os dados da base de dados.The lambda function is responsible for fetching the data from the database. Esta implementação desacopla o repositório da solução de colocação em cache específica e desacopla CacheService da base de dados.This implementation decouples the repository from the particular caching solution, and decouples the CacheService from the database.

ConsideraçõesConsiderations

  • Se a cache não estiver disponível, talvez devido a uma falha transitória, não devolva um erro ao cliente.If the cache is unavailable, perhaps because of a transient failure, don't return an error to the client. Em vez disso, obtenha os dados da origem de dados original.Instead, fetch the data from the original data source. No entanto, tenha em atenção que, enquanto a cache está a ser recuperada, o arquivo de dados original pode ser inundado de pedidos, o que resulta em tempos limite e falhas de ligações.However, be aware that while the cache is being recovered, the original data store could be swamped with requests, resulting in timeouts and failed connections. (Afinal de contas, esta é uma das motivações para utilizar uma cache.) Utilize uma técnica, como o [padrão Disjuntor Automático] circuit-breaker para evitar sobrecarregar a origem de dados.(After all, this is one of the motivations for using a cache in the first place.) Use a technique such as the Circuit Breaker pattern to avoid overwhelming the data source.

  • As aplicações que colocam dados não estáticos em cache devem ser concebidas para suportar uma eventual consistência.Applications that cache nonstatic data should be designed to support eventual consistency.

  • Para APIs Web, pode suportar a colocação em cache do lado do cliente ao incluir um cabeçalho Cache-Control nas mensagens de pedido e resposta, e ao utilizar ETags para identificar as versões de objetos.For web APIs, you can support client-side caching by including a Cache-Control header in request and response messages, and using ETags to identify versions of objects. Para obter mais informações, veja Implementação de APIs.For more information, see API implementation.

  • Não tem de colocar todas as entidades em cache.You don't have to cache entire entities. Se a maior parte de uma entidade for estática, mas apenas uma pequena parte for alterada com frequência, coloque os elementos estáticos em cache e obtenha os elementos dinâmicos da origem de dados.If most of an entity is static but only a small piece changes frequently, cache the static elements and retrieve the dynamic elements from the data source. Esta abordagem pode ajudar a reduzir o volume de E/S a efetuar na origem de dados.This approach can help to reduce the volume of I/O being performed against the data source.

  • Em alguns casos, se os dados voláteis tiverem curta duração, pode ser útil colocá-los em cache.In some cases, if volatile data is short-lived, it can be useful to cache it. Por exemplo, considere um dispositivo que envie continuamente atualizações de estado.For example, consider a device that continually sends status updates. Poderá fazer sentido colocar estas informações em cache à medida que chegam e não escrevê-las num arquivo persistente.It might make sense to cache this information as it arrives, and not write it to a persistent store at all.

  • Para impedir que os dados se tornem obsoletos, muitas soluções de colocação em cache suportam períodos de expiração configuráveis, para que os dados sejam removidos automaticamente da cache após um intervalo especificado.To prevent data from becoming stale, many caching solutions support configurable expiration periods, so that data is automatically removed from the cache after a specified interval. Poderá ter de otimizar o prazo de expiração para o seu cenário.You may need to tune the expiration time for your scenario. Os dados altamente estáticos podem permanecer na cache por períodos mais longos do que os dados voláteis, que se podem tornar obsoletos rapidamente.Data that is highly static can stay in the cache for longer periods than volatile data that may become stale quickly.

  • Se a solução de colocação em cache não fornecer expiração incorporada, poderá ter de implementar um processo em segundo plano que varra ocasionalmente a cache, para impedir que cresça sem limites.If the caching solution doesn't provide built-in expiration, you may need to implement a background process that occasionally sweeps the cache, to prevent it from growing without limits.

  • Além da colocação de dados em cache a partir de uma origem de dados externa, pode utilizar a colocação em cache para guardar os resultados de cálculos complexos.Besides caching data from an external data source, you can use caching to save the results of complex computations. No entanto, antes de o fazer, instrumente a aplicação para determinar se está realmente vinculada à CPU.Before you do that, however, instrument the application to determine whether the application is really CPU bound.

  • Poderá ser útil avisar a cache quando a aplicação for iniciada.It might be useful to prime the cache when the application starts. Povoe a cache com os dados com maior probabilidade de serem utilizados.Populate the cache with the data that is most likely to be used.

  • Inclua sempre instrumentação que detete acertos e falhas de acerto na cache.Always include instrumentation that detects cache hits and cache misses. Utilize estas informações para otimizar as políticas de colocação em cache, como quais os dados a colocar em cache e o intervalo de tempo para armazenar os dados na cache antes de expirarem.Use this information to tune caching policies, such what data to cache, and how long to hold data in the cache before it expires.

  • Se a falta de colocação em cache for um estrangulamento, adicionar a colocação em cache pode aumentar tanto o volume de pedidos que o front-end da Web fica sobrecarregado.If the lack of caching is a bottleneck, then adding caching may increase the volume of requests so much that the web front end becomes overloaded. Os clientes podem começar a receber erros de HTTP 503 (Serviço Indisponível).Clients may start to receive HTTP 503 (Service Unavailable) errors. Estes são uma indicação de que deve aumentar horizontalmente o front-end.These are an indication that you should scale out the front end.

Como detetar o problemaHow to detect the problem

Pode efetuar os passos seguintes para ajudar a identificar se a falta de colocação em cache está a causar problemas de desempenho:You can perform the following steps to help identify whether lack of caching is causing performance problems:

  1. Reveja a conceção da aplicação.Review the application design. Faça um inventário de todos os arquivos de dados utilizados pela aplicação.Take an inventory of all the data stores that the application uses. Determine se a aplicação está a utilizar uma cache para cada um deles.For each, determine whether the application is using a cache. Se possível, determine a frequência de alteração dos dados.If possible, determine how frequently the data changes. Bons candidatos iniciais para colocação em cache incluem dados alterados lentamente e dados de referência estáticos lidos com frequência.Good initial candidates for caching include data that changes slowly, and static reference data that is read frequently.

  2. Instrumente a aplicação e monitorize o sistema para descobrir com que frequência a aplicação obtém dados ou calcula informações.Instrument the application and monitor the live system to find out how frequently the application retrieves data or calculates information.

  3. Crie um perfil da aplicação num ambiente de teste para capturar as métricas de baixo nível sobre os custos gerais associados a operações de acesso a dados ou a outros cálculos efetuados frequentemente.Profile the application in a test environment to capture low-level metrics about the overhead associated with data access operations or other frequently performed calculations.

  4. Execute testes de carga num ambiente de teste para identificar como o sistema responde a uma carga de trabalho normal e a uma sobrecarga.Perform load testing in a test environment to identify how the system responds under a normal workload and under heavy load. Os testes de carga devem simular o padrão de acesso a dados observado no ambiente de produção através de cargas de trabalho realistas.Load testing should simulate the pattern of data access observed in the production environment using realistic workloads.

  5. Examine as estatísticas de acesso a dados dos arquivos de dados subjacentes e reveja com que frequência são repetidos os mesmos pedidos de dados.Examine the data access statistics for the underlying data stores and review how often the same data requests are repeated.

Diagnóstico de exemploExample diagnosis

As secções seguintes aplicam estes passos para o exemplo de aplicação descrito anteriormente.The following sections apply these steps to the sample application described earlier.

Instrumentar a aplicação e monitorizar o sistemaInstrument the application and monitor the live system

Instrumente a aplicação e monitorize-a para obter informações sobre os pedidos específicos que os utilizadores fazem enquanto a aplicação está em produção.Instrument the application and monitor it to get information about the specific requests that users make while the application is in production.

A imagem seguinte mostra a captura de dados de monitorização do New Relic durante um teste de carga.The following image shows monitoring data captured by New Relic during a load test. Neste caso, a única operação GET de HTTP efetuada é Person/GetAsync.In this case, the only HTTP GET operation performed is Person/GetAsync. No entanto, num ambiente de produção, conhecer a frequência relativa em que cada pedido é efetuado pode dar-lhe informações sobre que recursos devem ser colocados em cache.But in a live production environment, knowing the relative frequency that each request is performed can give you insight into which resources should be cached.

O New Relic mostra os pedidos de servidor para a aplicação CachingDemo

Se precisar de uma análise mais detalhada, pode utilizar um gerador de perfis para capturar os dados de desempenho de baixo nível num ambiente de teste (não o sistema de produção).If you need a deeper analysis, you can use a profiler to capture low-level performance data in a test environment (not the production system). Observe as métricas, como taxas de pedidos de E/S, utilização da memória e utilização da CPU.Look at metrics such as I/O request rates, memory usage, and CPU utilization. Estas métricas podem mostrar um elevado número de pedidos para um arquivo de dados ou serviço, bem como o processamento repetido que executa o mesmo cálculo.These metrics may show a large number of requests to a data store or service, or repeated processing that performs the same calculation.

Testar a carga da aplicaçãoLoad test the application

O gráfico seguinte mostra os resultados do teste de carga da aplicação de exemplo.The following graph shows the results of load testing the sample application. O teste de carga simula uma carga de até 800 utilizadores a executar uma série típica de operações.The load test simulates a step load of up to 800 users performing a typical series of operations.

Resultados do teste de carga de desempenho para o cenário sem colocação em cache

O número de testes com êxito efetuados por segundo atinge um patamar, o que origina um abrandamento dos pedidos adicionais.The number of successful tests performed each second reaches a plateau, and additional requests are slowed as a result. O tempo médio de teste aumenta firmemente com a carga de trabalho.The average test time steadily increases with the workload. O tempo de resposta uniformiza após os picos de carga dos utilizadores.The response time levels off once the user load peaks.

Examinar as estatísticas de acesso a dadosExamine data access statistics

As estatísticas de acesso a dados e outras informações fornecidas por um arquivo de dados pode fornecer informações úteis, como que consultas são repetidas com mais frequência.Data access statistics and other information provided by a data store can give useful information, such as which queries are repeated most frequently. Por exemplo, no Microsoft SQL Server, a vista de gestão sys.dm_exec_query_stats tem informações estatísticas para consultas executadas recentemente.For example, in Microsoft SQL Server, the sys.dm_exec_query_stats management view has statistical information for recently executed queries. O texto para cada consulta está disponível na vista sys.dm_exec-query_plan.The text for each query is available in the sys.dm_exec-query_plan view. Pode utilizar uma ferramenta como o SQL Server Management Studio para executar a seguinte consulta SQL e determinar a frequência de execução das consultas.You can use a tool such as SQL Server Management Studio to run the following SQL query and determine how frequently queries are performed.

SELECT UseCounts, Text, Query_Plan
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)

A coluna UseCount nos resultados indica a frequência de execução de cada consulta.The UseCount column in the results indicates how frequently each query is run. A imagem seguinte mostra que a terceira consulta foi executada mais de 250 000 vezes, significativamente mais do que qualquer outra consulta.The following image shows that the third query was run more than 250,000 times, significantly more than any other query.

Resultados da consulta às vistas de gestão dinâmicas no SQL Server Management Server

Segue-se a consulta SQL Server que está a causar muitos pedidos da base de dados:Here is the SQL query that is causing so many database requests:

(@p__linq__0 int)SELECT TOP (2)
[Extent1].[BusinessEntityId] AS [BusinessEntityId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [Person].[Person] AS [Extent1]
WHERE [Extent1].[BusinessEntityId] = @p__linq__0

Esta é a consulta que o Entity Framework gera no método GetByIdAsync apresentado anteriormente.This is the query that Entity Framework generates in GetByIdAsync method shown earlier.

Implementar a solução e verificar o resultadoImplement the solution and verify the result

Depois de incorporar uma cache, repita os testes de carga e compare os resultados com os testes de carga anteriores sem uma cache.After you incorporate a cache, repeat the load tests and compare the results to the earlier load tests without a cache. Seguem-se os resultados do teste de carga depois de adicionar uma cache à aplicação de exemplo.Here are the load test results after adding a cache to the sample application.

Resultados do teste de carga de desempenho para o cenário com colocação em cache

O volume de testes com êxito continua a atingir um patamar, mas com uma carga de utilizadores superior.The volume of successful tests still reaches a plateau, but at a higher user load. A taxa de pedidos nesta carga é significativamente maior do que a anterior.The request rate at this load is significantly higher than earlier. O tempo médio de teste continua a aumentar com a carga, mas o tempo de resposta máximo é de 0,05 ms, em comparação com o anterior 1 ms — um melhoramento de 20×.Average test time still increases with load, but the maximum response time is 0.05 ms, compared with 1ms earlier — a 20× improvement.