Zapewne nie raz spotkałeś się z sytuacją w której przez cache nie mogłeś zmienić szybko danych widocznych dla użytkownika. W ramach tego wpisu dowiesz się w jaki sposób usunąć cache dla jednego jak i wielu adresów.
Całą użytą konfiguracje możesz znaleźć [TUTAJ].
Wpis jest częścią serii Varnish’u
Konfiguracja PURGE
Zacznijmy od skonfigurowania obsługi metody PURGE. Żeby doszło do wyczyszczenia cache wystarczy że w vcl_revc wykonasz return(purge). Po zwróceniu purge wykonywany jest podprogram vcl_hash. Niczym się to nie różni od zwykłego requestu. Kiedy vcl_hash wywoła return(lookup) Varnish wyczyści obiekt, a następnie wywoła vcl_purge.
vcl 4.1;
backend default {
.host = "nginx";
.port = "80";
.probe = {
.url = "/health";
.timeout = 3s;
.interval = 10s;
.window = 5;
.threshold = 3;
}
}
acl purge {
"localhost";
"fpm";
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return(synth(405,"Not allowed."));
}
return (purge);
}
}
Jak się pewnie domyśliłeś w pierwszej kolejności musimy zabezpieczyć się przed sytuacją w której użytkownik ma prawo wyczyścić nam cache. Służy do tego lista acl (PS fpm to nazwa hosta w dockerze). W liście znajdują się adresy z których możemy wywoływać metody PURGE oraz później BAN.
W jaki sposób usuwać pojedynczy adres
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsCommand(
name: 'purge:url',
description: 'Purge in varnish url',
)]
class PurgeUrlCommand extends Command
{
private const URL = 'url';
private const VARNISH_URL = 'http://varnish';
private const DATA = [
[
'method' => 'GET',
'label' => 'Create cache',
],
[
'method' => 'GET',
'label' => 'Check cache is working',
],
[
'method' => 'PURGE',
'label' => 'Purge',
],
[
'method' => 'GET',
'label' => 'Check purge is working',
],
[
'method' => 'GET',
'label' => 'Check after purge cache is working',
],
];
public function __construct(private readonly HttpClientInterface $httpClient)
{
parent::__construct(null);
}
protected function configure(): void
{
$this
->addArgument(self::URL, InputArgument::OPTIONAL, 'Url for purge')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$url = sprintf(
'%s%s',
self::VARNISH_URL,
$input->hasArgument(self::URL) ? $input->getArgument(self::URL) : ''
);
foreach (self::DATA as $data) {
$output->writeln($data['label']);
$response = $this->httpClient->request($data['method'], $url);
$output->writeln($response->getContent());
sleep(1);
}
return Command::SUCCESS;
}
}
Teraz przyszła najprzyjemniejsza część nauki tj. testy konfiguracji, wystarczy że odpalisz command (jeżeli korzystasz z repozytorium wskazanego na samym początku wpisu) php bin/console purge:url i możesz zobaczyć efekt konfiguracji Varnisha. Poniżej możesz zobaczyć rezultat wykonanej komendy.
Create cache
2022-09-20 19:47:54:002
Check cache is working
2022-09-20 19:47:54:002
Purge
<!DOCTYPE html>
<html>
<head>
<title>200 Purged</title>
</head>
<body>
<h1>Error 200 Purged</h1>
<p>Purged</p>
<h3>Guru Meditation:</h3>
<p>XID: 11</p>
<hr>
<p>Varnish cache server</p>
</body>
</html>
Check purge is working
2022-09-20 19:48:37:207
Check after purge cache is working
2022-09-20 19:48:37:207
A co gdy chcemy usunąć wiele podobnych adresów
Do czyszczenia wielu adresów służą bany. Funkcja ta przeszukuje pamięć zgodnie z podanymi warunkami np. tak jak w naszej konfiguracji tj. po wyrażeniu regularnych oraz hoście. Możesz dodać tutaj również swoje inne warunki.
Jednym z ważnych aspektów optymalizacyjnych dla banów jest świadomość że bany starsze niż najstarsze obiekty w pamięci podręcznej są odrzucane bez oceny. Jeśli masz dużo obiektów z długim czasem TTL, możesz nagromadzić wiele banów. Skutkiem tego będzie spadek wydajności (tutaj warto wspomnieć o ban lurker ale w tej kwestii odsyłam Cię do dokumentacji :)). Ilość nałożonych banów możesz sprawdzić za pomocą commanda varnishadm ban.list.
Present bans: 1663696139.700177 0 - req.http.host == varnish && req.url ~ ^\/many\/test
Pora na konfiguracje, będziemy bazować na konfiguracji z poprzednich wpisów. 🙂
vcl 4.1;
backend default {
.host = "nginx";
.port = "80";
.probe = {
.url = "/health";
.timeout = 3s;
.interval = 10s;
.window = 5;
.threshold = 3;
}
}
acl purge {
"localhost";
"fpm";
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return(synth(405,"Not allowed."));
}
return (purge);
}
if (req.method == "BAN") {
if (!client.ip ~ purge) {
return (synth(405));
}
if (!req.http.X-Purge-Regex) {
return (purge);
}
ban("req.http.host == " + req.http.host + " && req.url ~ " + req.http.X-Purge-Regex);
return(synth(200, "Ban added"));
}
}
W porównaniu do PURGE bany możesz zakładać również z poziomu CLI. Służy do tego command varnishadm ban.
Pora wyczyścić cache
<?php
namespace App\Command;
use Exception;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsCommand(
name: 'ban',
description: 'Ban urls by regex',
)]
class BanCommand extends Command
{
private const URLS = 'urls';
private const REGEX = 'regex';
private const VARNISH_URL = 'http://varnish';
public function __construct(private readonly HttpClientInterface $httpClient)
{
parent::__construct(null);
}
protected function configure(): void
{
$this
->addArgument(self::REGEX,InputArgument::REQUIRED, 'Regex')
->addArgument(self::URLS, InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Urls')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$urls = $input->getArgument(self::URLS);
$output->writeln('Create cache');
$this->callToUrls($urls, $output);
$output->writeln('Test is working cache');
$this->callToUrls($urls, $output);
$output->writeln('Create ban');
$this->ban($urls, $input->getArgument(self::REGEX), $output);
$output->writeln('Check if ban working');
$this->callToUrls($urls, $output);
$output->writeln('Check if cache working after ban');
$this->callToUrls($urls, $output);
return Command::SUCCESS;
}
private function callToUrls(array $urls, OutputInterface $output): void
{
foreach ($urls as $url) {
$response = $this->httpClient->request(
'GET',
sprintf('%s%s', self::VARNISH_URL, $url)
);
$output->writeln($response->getContent());
sleep(1);
}
}
private function ban(mixed $urls, string $regex, OutputInterface $output): void
{
$response = $this->httpClient->request(
'BAN',
sprintf('%s%s', self::VARNISH_URL, current($urls)),
[
'headers' => [
'X-Purge-Regex' => $regex,
],
],
);
$output->writeln($response->getContent());
}
}
php bin/console ban "^\/many\/test-" /many/test-1 /many/test-2 /many/foo / /many/bar
Przyszła pora sprawdzić również jak działa metoda BAN. Poniżej możesz zobaczyć rezultat wykonanej komendy.
Create cache
test-1 2022-09-20 19:51:58:333
test-2 2022-09-20 19:51:48:600
foo 2022-09-20 19:51:49:800
2022-09-20 19:51:51:015
bar 2022-09-20 19:51:52:160
Test is working cache
test-1 2022-09-20 19:51:58:333
test-2 2022-09-20 19:51:48:600
foo 2022-09-20 19:51:49:800
2022-09-20 19:51:51:015
bar 2022-09-20 19:51:52:160
Create ban
<!DOCTYPE html>
<html>
<head>
<title>200 Ban added</title>
</head>
<body>
<h1>Error 200 Ban added</h1>
<p>Ban added</p>
<h3>Guru Meditation:</h3>
<p>XID: 32808</p>
<hr>
<p>Varnish cache server</p>
</body>
</html>
Check if ban working
test-1 2022-09-20 19:51:58:333
test-2 2022-09-20 19:53:48:844
foo 2022-09-20 19:51:49:800
2022-09-20 19:51:51:015
bar 2022-09-20 19:51:52:160
Check if cache working after ban
test-1 2022-09-20 19:51:58:333
test-2 2022-09-20 19:53:48:844
foo 2022-09-20 19:53:49:994
2022-09-20 19:51:51:015
bar 2022-09-20 19:51:52:160
Podsumowanie
Mam nadzieję że ten wpis pokazał Ci jak możesz czyścić cache w Varnish. Pominąłem tutaj kwestie optymalizacyjne ponieważ ta seria ma na celu przedstawić różne aspekty podstaw Varnisha a nie wszystkich aspektów jednak zachęcam Cię do poświęcenia jeszcze 10 minut na przestudiowanie dokumentacji 🙂
Źródła: