Cache, invalidace podle patternu

Damo
Člen | 55
+
0
-

Ahoj,

řeším jednu věc ohledně cache

mám skupinu formulářů, jejich obsah si nacachuji za každý den, který si uživatel vybere z kalendáře.
Cache klíč vypadá takto:

"form_{$formId}_{$date->format('Y-m-j')}"

formuláře zobrazují obsah závislý na obsahu některých dat v předchozích dnech stejného formuláře (je tam určitý algoritumus, který dle dat z předchozích dnů generuje nějaké hodnoty). Pokud ve formuláři v minulosti něco změním, pak formulář s datem v budoucnosti, zobrazí jiný obsah.

Proto potřebuji, když někdo změní určitý obsah formuláře v minulosti, aby se mi invalidovala cache daného formuláře od daného dne do budoucnosti, ale podle patternu. Tzn. aby se invalidovali podle toho že začínají na

"form_{$formId}_"

s nějakou možností, dateFrom.

Nemůžu to iterovat den pod dni, pač neznam konečný datum.

Existuje taková možnost invalidovat cache podle patternu a jakého?

Popř. zda existuje jiná možnost jak cachovat jednu věc s časovou známkou, a poté invalidovat cache do budoucnosti od určitého data.

Tu cache tam potřebuji, protože ten algoritmus nějakou dobu trvá, když těch formulářů jsou desítky. Proto to cachuji, pak je to načtený hned.

Díky

m.brecher
Generous Backer | 761
+
0
-

@Damo

formuláře zobrazují obsah závislý na obsahu některých dat v předchozích dnech stejného formuláře (je tam určitý algoritumus, který dle dat z předchozích dnů generuje nějaké hodnoty).

Pokud ve formuláři v minulosti něco změním, pak formulář s datem v budoucnosti, zobrazí jiný obsah.

Nebylo by lepší závislost dat v budoucnosti na datech v minulosti, kdy se budoucí data počítají komplikovaným algoritmem z minulých dat řešit přímo v databázi ??

Databáze jsou díky SQL vybaveny i na takovéto komplikované operace, a souborový systém cachování formulářů jim po této stránce nemůže konkurovat.

Damo
Člen | 55
+
0
-

Ten algoritmus mi pocita DB, ale trva to 0,1s za formulář, ale když těch fomrulářů je 200, tak proste každým zobrazením stránky mi to trvá 20s než dostanu výsledek. Když ty výsledky zacachuju (spustim cronem po pulnoci), tak ma pak uživatel vysledek do 2s za všechny formuláře (to je akceptovatelné). Proto hledám variantu, jak invalidovat jeden formulář a jeho cache od určitého data do budoucnosti, pokud nějaký uživatel v daném formulaři, třeba v minulém týdnu něco změní.

kminekmatej
Generous Backer | 34
+
0
-

Co zneužít k tomuto Prioritu? S tou pak můžeš mazat všechny položky MENŠÍ nebo rovno určité hodnotě.

1. Spočítej si u formuláře o jaký den se jedná:

$priority = 0 - intval((new DateTime())->diff(new DateTime('0001-01-01'))->format('%a')); // pro dnešek to bude -738851, každý další den to o 1 klesne

2. Při invalidaci formuláře si zjisti stejným algoritmem o který den se jednalo:

$historicalPriority = 0 - intval(($form->getDateTime())->diff(new DateTime('0001-01-01'))->format('%a')); // třeba týden zpátky to bude -738844

3. Smaž všechny novější záznamy:

$cache->clean([
	$cache::Priority => $historicalPriority,
]);
Damo
Člen | 55
+
0
-

Ta priorita zní velmi dobře, vyzkouším. Díky

Damo
Člen | 55
+
0
-

@kminekmatej

Tak to funguje, ale nedokonale. Ono mi to invaliduje všechny formuláře od editovaného dne do budoucnosti, nikoliv ten jeden.
Zkousel jsem přidat do cache i tag jako $formId, ale invalidace neprobíha pod logickým AND, ale OR. Takze nemůžu invalidovat cache s tag AND priority.

Damo
Člen | 55
+
+1
-

Tak jsem to vyřešil tím, že jsme si udělal vlastní Journal, který přidává možnost AND , je to kopie Nette SQLiteJournal, ale obohacena o novy SQL dotaz.

Pokud budou v clean uvedeny jak priority, tak tag, tak se vytvori SQL s logickym AND

a ted to funguje perfektne. Díky!!

if (!empty($conditions[Cache::Tags]) && !empty($conditions[Cache::Priority])) {
            $tags = (array)$conditions[Cache::Tags];
            // If both are specified, we will combine the conditions into a single query with AND
            $unions[] = 'SELECT DISTINCT key FROM tags WHERE tag IN (?' . str_repeat(', ?', count($tags) - 1) . ') AND key IN (SELECT DISTINCT key FROM priorities WHERE priority <= ?)';
            $args = array_merge((array) $conditions[Cache::Tags], [(int) $conditions[Cache::Priority]]);
        } else {
            if (!empty($conditions[Cache::Tags])) {
                $tags = (array)$conditions[Cache::Tags];
                $unions[] = 'SELECT DISTINCT key FROM tags WHERE tag IN (?' . str_repeat(', ?', count($tags) - 1) . ')';
                $args = $tags;
            }

            if (!empty($conditions[Cache::Priority])) {
                $unions[] = 'SELECT DISTINCT key FROM priorities WHERE priority <= ?';
                $args[] = (int)$conditions[Cache::Priority];
            }
        }

Doplním tedy, že cache má tyto dependecnies

$dependencies[Cache::Expire] = CacheService::PLANNED_REPORTS_EXPIRE;
$dependencies[Cache::Priority] = CacheService::getPriority($dateTime);
$dependencies[Cache::Tags] = ["form/$form->id"];

a invalidace probehne takto

$cache->clean([
	$cache::Priority => self::getPriority($date),
	$cache::Tags => ["form/$formId"],
]);

clean methoda je upravena ve vlastni Journalu, viz výše

a priorita se pocítat takto

    public static function getPriority(?DateTime $date = null): int {
        $date = $date ?: new DateTime();
        return 0 - (int) (($date)->diff(new DateTime('0001-01-01'))->format('%a'));
    }

Editoval Damo (29. 11. 2023 15:35)

kminekmatej
Generous Backer | 34
+
0
-

Tak si vytvoř pro každý formulář vlastní instanci Cache. A nemusíš to tagovat a tím pádem přetěžovat Journal.

$cache = new Cache($this->getStorage(), $form->getId());

Takhle bych se bál že na to za pět let zapomenu a budu se divit proč mám při zadání Priority i Tagu logiku AND namísto OR jak stojí v dokumentaci :-)

Damo
Člen | 55
+
0
-

Zkouším, ale pokud si do namespace pošlu FormId, tak mi invalidace porad vymaze cache vsech formularu, vubec nereflektuje namespace.

Zjednodusene

$cache1 = new Cache($this->getStorage(), "1");
$cache1->load(...

$cache2 = new Cache($this->getStorage(), "2");
$cache2->load(...

a kdyz dam

$cache1->clean([
	$cache1::Priority => self::getPriority($date),
]);

Tak mi invaliduje i cache2.

kminekmatej
Generous Backer | 34
+
+1
-

No, to bych skoro řek že jsi možná náhodou objevil nějaký bug. Teď jsem si na to zkusil napsat test a je to přesně jak říkáš.

Problém je v tom že obě cache sdílejí stejný Storage. V průběhu mazání se z toho storage načtou klíče které se mají smazat a ty se smažou. A jelikož je ten storage sdílený, tak to vrátí klíče z obou cachí.

Založil sem na to bug: https://github.com/…ng/issues/74

Editoval kminekmatej (29. 11. 2023 21:39)