[2009–12–03] Caching\FileStorage a SQLite pro ukládání tagů a priorit
- David Grudl
- Nette Core | 8227
Zkusil jsem upravit FileStorage tak, aby metadata Cache::TAGS a Cache::PRIORITY, podle kterých lze mazat metodou clean(), ukládalo žurnálu v podobě SQLite databáze.
Dosud bylo nutné při mazání určitého tagu projít všechny soubory v cache a zjistit, které mají daný štítek. Sice se z každého souboru načetla jen hlavička, ale i tak šlo o operaci, jejíž doba trvání narůstala přímou úměrou s počtem položek v cache. Použití žurnálu by mělo tento problém eliminovat, které soubory smazat se ví i bez procházení.
Sice by to mělo fungovat, ale je možné, že to má chyby, ať už z pohledu atomicity nebo výkonu. To bude chtít otestovat.
- Petr Motejlek
- Člen | 293
To se mi líbí, zní to super. Přesně to jsem chtěl začít bastlit sám, protože někdy je těch věcí moc, a SQLite bude určitě to pravé.
Nevím, jestli se mám ptát tady, nebo bych měl založit nové vlákno, ale radši se zeptám tady: napsal jsem si vlastní session_handler (slouží k ukládání a načítání session dat) tak, aby používal SQLite (musel jsem použít PDO, protože mi to s Dibi blblo, ale nedávno jsem zjistil, že to asi byla moje blbost…).
Výhodu to má takovou, že pokud nastavíte timeout pro session například na 14 dní (to třeba zajístí, že zákazník uvidí obsah svého košíku i po 14 dnech neaktivity), tak v normální situaci se pro každou session vytváří samostatný soubor – po 14 dnech je to obvykle několik desítek tisíc souborů a výkon serveru začíná klesat, protože jsou defaultně všechny v jednom adresáři. Když se použije SQLite, tak se dá zařídit, aby těch souborů bylo vážně jen minimum a ztráta výkonu není téměř patrná (EDIT: no, záleží na tom, co do té session ukládáte :D).
Zajímalo by mě následující: je zájem o zveřejnění té třídy, která ukládá ta data session do SQLite (díky tomu, že by to bylo v Dibi, tak by byl skutečně minimální problém to převést na nějakou jinou databázi)?
Editoval Petr Motejlek (3. 12. 2009 15:39)
- Honza Kuchař
- Člen | 1662
je zájem o zveřejnění té třídy
Já bych měl, zní to zajímavě.
která ukládá ta data session do SQLite (díky tomu, že by to bylo v Dibi, tak by byl skutečně minimální problém to převést na nějakou jinou databázi)?
Určitě. Teď jsem přecházel z MySQL na PgSQL a maká to perfektně.
- Petr Motejlek
- Člen | 293
Tady je ta třída. Neříkám, že je to to nejlepší, co se pro dané zadání dá udělat, ale mě zatím stačí. Taky ale uvažuju, že vytvořím nějakou PgSQL databázi pro dočasné věci a budu to do cpát do ní a ne do SQLite. Pro použití s jinými databázemi by mělo stačit změnit ty dva řádky, co vytváří tabulku [session] a do self::$conn si uložit DibiConnection na tu vybranou databázi.
Pro použití této třídy je nutné si třeba v $application->onStartup[] zavolat následující (musí to být zavoláno ještě před vlastním nastartováním session):
<?php
session_set_save_handler(array('DBSession', 'open'), array('DBSession', 'close'),
array('DBSession', 'read'), array('DBSession', 'write'),
array('DBSession', 'destroy'), array('DBSession', 'clean'));
?>
A tady už je samotný kód třídy:
<?php
class DBSession {
/**
* Holds the database connection
* @var DibiConnection
*/
private static $conn = null;
public static function open() {
if (is_null(self::$conn)) {
self::$conn = dibi::connect(array('driver' => 'pdo', 'dsn' => 'sqlite://' . APP_DIR . '/sessions/sessions.sqlite', ), __CLASS__);
dibi::activate(MainConnectionName);
};
}
public static function read($id) {
if (is_null(self::$conn)) {
throw new InvalidStateException("The connection to database for session storage is not open!");
};
$query = '
SELECT
[data]
FROM [session]
WHERE
[id] = %s';
try {
$result = self::$conn->query($query, $id);
return $result->fetchSingle();
} catch (Exception $e) {
Debug::processException($e);
self::$conn->query('CREATE TABLE [session] ([id] varchar(32) not null primary key, [timestamp] timestamp not null, [data] text)');
self::$conn->query('CREATE INDEX [session_by_timestamp] ON [session] ([timestamp])');
return '';
};
}
public static function write($id, $data) {
if (is_null(self::$conn)) {
throw new InvalidStateException("The connection to database for session storage is not open!");
};
self::$conn->begin();
self::$conn->query('DELETE FROM [session] WHERE [id] = %s', $id);
self::$conn->query('INSERT INTO [session] VALUES(%s, %s, %s)', $id, time(), $data);
self::$conn->commit();
}
public static function destroy($id) {
if (is_null(self::$conn)) {
throw new InvalidStateException("The connection to database for session storage is not open!");
};
self::$conn->query('DELETE FROM [session] WHERE [id] = %s', $id);
}
public static function clean($max) {
if (is_null(self::$conn)) {
throw new InvalidStateException("The connection to database for session storage is not open!");
};
$old = (time() - $max);
self::$conn->query('DELETE FROM [session] WHERE [timestamp] < %s', $old);
}
public static function close() {
self::$conn = null;
}
}
?>
P. S.: Budu rád, když se mi tady objeví ohlasy, abych případně věděl, jestli nedělám něco složitě nebo podivně a pro příště to mohl opravit ;).
Editoval Petr Motejlek (4. 12. 2009 22:33)
- Jan Tvrdík
- Nette guru | 2595
Znamená použití SQLite, že nově může mít jeden záznam v cache řádově desítky tagů bez znatelné ztráty výkonu?
- Panda
- Člen | 569
S žurnálem na tagy se pracuje jen při mazání, takže při čtení není ztráta výkonu vůbec žádná. Teda vlastně je, PHP musí parsovat o pár bajtů větší soubor. :-)
Při mazání/ukládání to může zabrat několik desítek (u velkého množství záznamů v cache až stovek) milisekund, v závislosti na počtu souborů a použitém filesystému.