Erros nos servidores web são muito comuns. Diversos motivos podem acontecer: um banco de dados caiu, faltou memória, deu um problema na rede, o sistema dinâmico tá consumindo muita CPU, um htaccess com erro de sintaxe, uma função mal-escrita entrou em loop infinito, entre muitos outros. Quando algum erro como esse ocorre, o usuário recebe um famoso “Error 500 Internal Server Error” (ou até um “503 Service Unavailable”). O Varnish pode ser usado para mascarar esse tipo de erro e funciona de uma forma bem simples: se o servidor der algum erro, o Varnish serve a página que está em cache, ao invés do erro. O usuário vai pegar um conteúdo possivelmente antigo, mas em muitos casos é melhor do que ele ser presenteado com um erro.
Conheçam agora os mecanismos responsáveis por isso: varnish grace e saintmode!
Usando o Grace
No Varnish, o grace é um recurso que marca um objeto, dizendo que ele pode ser usado mesmo que seu tempo de vida (TTL) expirou. Quando dizemos: “este objeto está em grace”, significa que o tempo de cache que você configurou para ele já passou, mas ele continue armazenado lá no cache para ser usado caso precise.
Existem duas formas de se usar definir o grace de um objeto:
- O req.grace, geralmente definido no vcl_recv, significa quão velho um objeto pode ser servido diretamente do cache, sem pedir pro backend.
- Já o beresp.grace, definido no vcl_fetch, significa por quanto tempo guardar o objeto no cache depois do seu tempo de vida (TTL) acabar.
Vejamos um exemplo prático de configuração no VCL:
sub vcl_recv { set req.backend = default; if (req.backend.healthy) { set req.grace = 5m; } else { set req.grace = 2h; } } sub vcl_fetch { set beresp.ttl = 1m; set beresp.grace = 3h; }
O que podemos notar nessa configuração é o seguinte:
- Os objetos tem um tempo de vida (TTL) de 1 minuto. Passado esse 1 minuto, o Varnish vai buscar no backend novamente;
- Cada vez que o Varnish busca um item no backend, ele também configura o grace do item para 3 horas. Então mesmo que passou-se 1 minuto, o item velho pode ficar armazenado no cache por até essas 3 horas;
- Para todos as novas requisições que passam no vcl_recv, se o backend estiver bom (healthy), o varnish (não obrigatoriamente) pode servir um item velho em até 5 minutos. Isso significa que se der algum problema no backend (ele saiu do ar por algum motivo), o item vai ser servido mesmo assim pro usuário;
- Se ocorreu um erro muito grande no backend e ele esteja em estado doente (sick), o varnish então pode servir um item velho em até 2 horas.
- Seguindo essa linha, é importante notar que é completamente inútil configurar um req.grace maior que o beresp.grace, pois ele nunca conseguiria servir um item com grace se o tempo que você especificou foi maior do que poderia ser armazenado no cache.
E exemplos de requisições:
- Requisição aos 30s, entrega normal – hit (tá dentro do TTL);
- Requisição aos 1m30s, entrega normal – miss (caso ocorra problema no backend, pode servir o objeto em grace);
- Requisição aos 4m, entrega normal – miss (caso ocorra problema no backend, pode servir o objeto em grace);
- Requisição aos 8m, entrega normal – miss (o objeto em grace só é servido se o backend estiver completamente doente (sick);
- Requisição aos 2h30m, entrega normal – miss (não é possível servir o objeto em grace, se o backend der erro o usuário recebe o erro);
- Requisição aos 3h1m, o objeto é removido do cache.
Lembrando que a cada requisição feita, se o objeto foi requisitado com sucesso de um backend bom, todos os tempos dele (TTL e grace) são renovados.
Quando o grace pode ocorrer?
Conseguimos configurar o grace mode nos objetos, mas não sabemos ainda direito quando ele pode realmente ocorrer. O grace é ativado pelo próprip varnish quando ocorre algum problema no backend. Estes problemas incluem:
- Um conteúdo novo já está sendo obtido do backend, ou seja, ele serve o conteúdo mais antigo enquanto não vem o novo, diminuindo o tempo de resposta);
- O probe do backend falha algumas vezes e o backend entra em modo doente (sick);
- Não existe nenhum backend usável na configuração, nenhum backend bom (healthy).
Então neste caso, é sempre bom utilizar backends com probes, assim o Varnish sabe exatamente como se comportar quando um backend falha. Os probes também tem que ser confiáveis e refletirem o sistema como um todo. Não adianta colocar um probe de um HTML estático, se o seu sistema todo depende de um banco de de dados e várias outras coisas. Um bom health check usado no probe testa tudo dentro de um sistema para garantir que tudo deve estar funcionando. Se algo não funciona, o sistema pode dar erro, o Varnish marca o backend como doente e começa a servir um conteúdo mais antigo até você arrumar o sistema (o que é bem melhor que o usuário receber erros).
Se ainda não viu, veja mais sobre backends neste artigo Uma introdução ao Varnish – Backends.
Controle melhor: saintmode
Entendido como funciona o grace, agora podemos usar o saintmode. O saintmode é um recurso do Varnish que permite você definir itens à uma blacklist de backends. Se uma URL está com defeito no backend, você pode dizer para ela entrar em saintmode por algum tempo. Durante esse tempo que ela estiver em saintmode, o Varnish não vai mais pedir pela URL, e por isso o saintmode é realmente uma blacklist.
Exemplo:
- Usuário acessa a URL https://www.devin.com.br/sou-uma-pagina-problematica/;
- O Varnish tenta buscar essa página no backend, mas ganha um belo de um erro 500;
- Então o Varnish coloca essa URL em saintmode por 1 minuto e reinicia a requisição;
- Ao invés de buscar o item novamente, pela URL estar na blacklist, ele serve através dos mecanismos de grace.
- Passado 1 minuto, a próxima requisição que vier, o Varnish tenta buscar no backend novamente.
Em outras palavras, dessa forma a gente consegue controlar exatamente quando usar o grace ou não!
Note também que este é um recurso muito útil para servidores sobrecarregados. Imagine que um servidor está tão sobrecarregado que as páginas estão sempre dando erro e gerando mais tráfego e mais sobrecarga. Se um servidor está sobrecarregado, a última coisa que ele quer é muito mais requisições! Você pode usar o saintmode para que, quando der erro, o Varnish tente servir do cache ao invés de ficar tentando pegar as páginas.
Seguindo o mesmo exemplo anterior do grace, mas adicionando o saintmode, olha só como pode ficar:
sub vcl_recv { set req.backend = default; if (req.backend.healthy) { set req.grace = 5m; } else { set req.grace = 2h; } } sub vcl_fetch { set beresp.ttl = 1m; set beresp.grace = 3h; if (req.url == "/" && beresp.status != 200) { set beresp.saintmode = 30s; return (restart); } if (beresp.status >= 500) { set beresp.saintmode = 1m; return (restart); } }
Nossas novas linhas são as 16-24:
- Se requisição pra home do meu site (req.url == /) não retornar 200 (beresp.status != 200), então está errado. Por isso, coloco a URL em saintmode por 30 segundos e reinicio a requisição. Pelos próximos 30 segundos, todas as requisições que chegarem para a minha home será servido diretamente do cache através do grace;
- Se uma página retornar status maior ou igual a 500 (beresp.status >= 500), então tem algo errado com uma configuração ou com o sistema. Então coloco a URL em saintmode por 1 minuto e reinicio a requisição. Pelos próximos 1 minuto, todas as requisições que chegarem para esta página que deu erro 500+ serão servidas diretamente do cache através do grace.
Lembrando que não existe mágica: se o tempo de armazenamento do grace expirar (beresp.grace) ou o item não estiver no cache, o usuário vai receber o erro. É importante que além dessas seguranças adicionais e fallbacks no Varnish, você monitore seu sistema e caso dê erro, conserte rapidamente antes que o grace falhe.
O saintmode tem um recurso por padrão: se 10 URLs forem incluídas na blacklist do saintmode, o Varnish considera TODO o backend como doente. Em muitos casos, se 10 URLs de um sistema falharem, provavelmente o sistema todo está ruim mesmo. Mas em muitos casos isso pode gerar problemas. Para evitar que isso aconteça, você pode mexer nessa quantidade de URLs através do parâmetro de linha de comando “-p saintmode_threshold=<NUMERO>”. Se configurado como 0, ele não torna o backend doente não importa a quantidade de URLs em saintmode.
Backends com TPM, ou seja, instáveis
Às vezes pode acontecer de um backend estar instável e gerar problemas que nem o grace e nem o saintmode resolvem. Exemplos dessa instabilidade são: problemas na rede que matam uma (mas não todas) requisição no meio, um backend que falha de vez em quando, um erro desconhecido gerado pelo sistema no backend, entre outros. Quando um problema cabeludo como esse ocorre, o Varnish não consegue lidar com ele no vcl_recv e nem no vcl_fetch. A requisição cai direto no vcl_error.
O vcl_error não tem nenhum controle de grace ou saintmode. Neste caso há uma “solução de contorno” (também conhecido como gambiarra que funciona) para remediar esses problemas.
A idéia é configurar um backend completamente falso, que sempre está doente. Então se a requisição der errado e cair no vcl_error, reinicia-se ela, só que configurando para o backend falso. Como o backend falso vai estar sempre sick, os mecanismos de grace são ativados e o usuário recebe a página diretamente do cache.
Eis o exemplo, adicionando aos mesmos de antes:
backend default { .host = "127.0.0.1"; .port = "81"; .probe = { .url = "/"; .interval = 5s; .timeout = 2s; .window = 5; .threshold = 3; } } backend falhou { .host = "127.0.0.1"; .port = "666"; .probe = { .url = "/voufalharmiseravelmenteporquenaoexisto.xyz"; .interval = 5s; .timeout = 2s; .window = 5; .threshold = 3; } } sub vcl_recv { set req.backend = default; if (req.http.X-Fail == "True") { unset req.http.X-Fail; set req.backend = falhou; } if (req.backend.healthy) { set req.grace = 5m; } else { set req.grace = 2h; } } sub vcl_fetch { set beresp.ttl = 1m; set beresp.grace = 3h; if (req.url == "/" && beresp.status != 200) { set beresp.saintmode = 30s; return (restart); } if (beresp.status >= 500) { set beresp.saintmode = 1m; return (restart); } } sub vcl_error { if (req.restarts == 0) { set req.http.X-Fail = "True"; return (restart); } }
O que fizemos:
- Se ocorrer qualquer problema na requisição e cair no vcl_error, ele configura um cabeçalho “X-Fail” e reinicia a requisição. Isso só pode ocorrer uma vez, se falhar duas vezes, dá erro. (if req.restarts == 0);
- Com a requisição reiniciada, no vcl_recv o Varnish vê que tem o cabeçalho X-Fail e manda pro backend falhou, ao invés do normal;
- Com o backend sempre doente, o Varnish ativa os mecanismos de grace e serve a página diretamente do cache.
Troubleshooting
FetchError c no backend connection
Aparece durante uma análise de log com o varnishlog. Isso ocorre porque ao tentar entregar o objeto, o varnish não encontrou nenhum backend válido. Pode ocorrer:
- Você forçou o objeto a ser entregue pelo cache porque um backend está doente, mas o item não está no cache.
- O saintmode_threshold chegou ao seu limite (padrão 10) e todas as requisições pro backend são rejeitadas (mesmo que o probe esteja healthy).
- Todos os backends simplesmente estão doentes :(
A solução é sempre:
- Olhar se o backend e os probes estão funcionando corretamente (varnishlog | grep Backend)
- Conferir se você não está usando o saintmode de forma errada e verificar o parâmetro de linha de comando saintmode_threshold.
Se tiver dúvidas na hora de olhar os logs, consulte este outro artigo: Dissecando logs no Varnish.
Referências
Obrigado ao Fabio Gomes dos Santos por ter me dado a ideia do artigo enquanto me perguntava sobre algo relacionado a este assunto.