Esta pergunta é sobre melhores práticas em arquitetura.
Nossa Arquitetura Atual
Eu tenho uma classe PHP que acessa o MySQL para informações do usuário. Vamos chamá-lo User
. User
é acessado muitas vezes, por isso implementamos camadas de cache para reduzir a carga.
A primeira camada é o que chamamos de cache "por solicitação". Depois que os dados foram recuperados do MySQL, armazenamos os dados em uma propriedade privada de User
. Quaisquer pedidos subsequentes para os dados retornam a propriedade em vez de solicitar novamente os dados do MySQL.
Como a solicitação da Web vive e morre conforme a solicitação, esse cache apenas impede que o aplicativo acesse o MySQL mais de uma vez em uma única solicitação.
Nossa segunda camada é o Memcached. Quando a propriedade privada está vazia, primeiro verificamos os dados no Memcached. Se o Memcached estiver vazio, consultamos os dados do MySQL, atualizamos o Memcached e atualizamos a propriedade privada de User
.
A questão
Nossa aplicação é um jogo e, às vezes, é imperativo que alguns dados estejam o mais atualizados possível. No período de aproximadamente cinco minutos, uma solicitação de leitura para os dados do usuário pode ocorrer 10 ou 11 vezes; uma atualização pode ocorrer. Os pedidos de leitura subsequentes precisam estar atualizados ou a mecânica do jogo falha.
Então, o que fizemos foi implementar um pedaço de código que é executado quando ocorre uma atualização do banco de dados. Esse código define a chave no Memcached com os dados atualizados, para que todas as solicitações subsequentes ao Memcached estejam atualizadas.
Isso é ótimo? Há alguma preocupação com o desempenho ou outras "dicas" que devemos estar atentos ao tentar manter uma espécie de "cache ativo" como esse?
fonte
Respostas:
Minha recomendação é examinar seu perfil de uso e seus requisitos para o cache.
Não vejo razão para deixar dados obsoletos no cache de memórias. Eu acho que você escolheu a abordagem correta, ou seja: atualize o DB.
De qualquer forma, você precisará de um wrapper na atualização do banco de dados (o que você fez). Seu código para atualizar o usuário no banco de dados e na RAM também deve ser enviado para o memcached OU uma expiração no memcached.
Por exemplo - se seus usuários normalmente fazem uma atualização uma vez por sessão como parte do logoff, não faz muito sentido atualizar os dados no cache (por exemplo, pontuação total alta) - você deve expirar imediatamente.
Se, no entanto, eles atualizarem os dados (por exemplo, estado atual do jogo) e, 0,2 segundos depois, você terá uma ocorrência imediata na página PHP que solicitará os dados, desejando que sejam atualizados no cache.
fonte
Eu não faria isso exatamente como você descreveu. O que você precisa fazer é decidir se você realmente precisa de dados completamente atualizados. Em seguida, se você precisar, decida quais partes dos dados devem estar sempre atualizadas e separe-as das coisas que podem ser armazenadas em cache em sua arquitetura.
Por exemplo, você provavelmente deseja atualizar o endereço de e-mail do usuário assim que o alterar, para não enviar e-mails para o endereço errado, mas é improvável que a data de nascimento ou sobrenome do usuário precise ser completamente atualizado para fornecer uma experiência decente ao usuário. (NB: Não estou usando um exemplo de arquitetura de jogo, pois não sei que tipo de jogo apontar e acho que este é bastante fácil de entender).
Dessa forma, você tem dois conjuntos de dados claros: dados armazenáveis em cache de curto e longo prazo. Provavelmente, você pode se safar com uma duração de cache de mais ou menos um minuto nos dados de curto prazo, apenas para aliviar a carga no banco de dados, mas os dados de longo prazo podem ser deixados no cache em uma duração variável, desde que sejam usava.
Então você precisa lidar com as atualizações. Primeiro, eu usaria um gatilho de banco de dados para simplesmente remover itens do cache quando estiverem desatualizados. Isso forçará sua camada de negócios a acionar uma atualização de cache na próxima vez em que solicitar os dados, liberando espaço no cache se os dados não estiverem sendo usados (por exemplo, se um usuário alterar seu endereço de email e sair imediatamente) . Se isso causar problemas de desempenho na interface do usuário (ou seja, introduzir muito atraso enquanto aguarda a atualização do cache), você poderá simplesmente acionar a chamada de cache assim que o item for removido do cache. Eu também procuraria otimizar os tempos de leitura do banco de dados para esse pequeno conjunto de dados, para garantir que qualquer atraso induzido na atualização do cache seja mínimo (isso deve ser mais fácil, pois você só precisa carregar dados que realmente precisa).
O que eu não faria, em nenhuma circunstância, é adicionar um método adicional de preenchimento do cache, pois você precisará manter a chamada (e ganchos de API etc.) em dois locais.
Quanto às dicas, a principal coisa que você precisa ter cuidado se estiver escrevendo diretamente no cache é a sincronização. Se muitos threads tentarem ler enquanto você estiver fazendo sua atualização silenciosa, você poderá ter sérios problemas de dados inválidos, o que impedirá o ponto de tentar manter os dados atualizados em primeiro lugar.
fonte