8.3 KiB
SQLmem — Project Specification
Cíl
Python modul fungující jako transparentní cache vrstva mezi SQLAlchemy a databází. Aplikace volá SQLAlchemy stejně jako dosud — SQLmem sedí mezi nimi, zachytává SELECT dotazy a vrací výsledky z in-memory SQLite cache. Zápisy (INSERT/UPDATE/DELETE) jsou přepouštěny přímo do databáze bez zásahu.
Architektura
Aplikace (SQLAlchemy)
│
▼
[ SQLmem Proxy ]
┌───────────────────────────────┐
│ SQL Parser │ → rozezná SELECT vs. zápis
│ Cache Manager (SQLite RAM) │ → drží data v paměti
│ Query Executor │ → cache hit / miss logika
└───────────────────────────────┘
│
▼
Databáze (přes původní SQLAlchemy engine)
API
Modul implementuje SQLAlchemy event hooks / custom engine wrapper — aplikace nemusí měnit způsob volání ani formát vrácených dat.
from sqlmem import CachingEngine
from sqlalchemy import create_engine
base_engine = create_engine("postgresql://...")
engine = CachingEngine(base_engine)
# Dál se pracuje stejně jako s běžným SQLAlchemy enginem:
with engine.connect() as conn:
result = conn.execute(text("SELECT * FROM users WHERE status = 'active'"))
CachingEngine vrací objekty kompatibilní s CursorResult — fetchall(), fetchone(), keys() atd. fungují stejně.
Cache backend
- SQLite in-memory jako primární úložiště — veškeré dotazy běží v RAM.
- Persistence na disk (
cache.db) ve třech situacích:- Při startu: pokud soubor existuje, načte se do paměti (
ATTACH+ kopie). - Periodicky každou hodinu: snapshot in-memory SQLite se zapíše na disk (backup API).
- Při vypnutí: finální zápis na disk před ukončením (signal handler + context manager).
- Při startu: pokud soubor existuje, načte se do paměti (
- Celé tabulky se při cache miss načtou z databáze a drží v paměti.
Komponenty
1. SQL Parser
- Detekuje typ dotazu (SELECT / zápis); zápisy vyhodí
ReadOnlyError. - Extrahuje názvy tabulek z FROM a JOIN klauzulí (podpora více tabulek).
- Mapuje požadované sloupce na tabulky přes aliasy (
columns_by_table). - Detekuje
SELECT *aalias.*→ tabulka se načte celá (wildcard_tables). - Parsuje přes dialekt
SQLMEM_SQL_DIALECT(defaulttsql) a renderuje in-memory dotaz do SQLite (stripuje catalog/schema prefixy). - Parametry (
?/:name) předává beze změny do in-memory SQLite.
2. Column Registry
Modul se za běhu učí, jaké sloupce z každé tabulky aplikace potřebuje — nevyžaduje žádnou předem danou konfiguraci.
Logika při každém příchozím dotazu:
- Parser extrahuje
(tabulka, sloupce)pro každou tabulku v dotazu (i přes JOIN). - Registry provede union nově požadovaných sloupců s již známými.
- Cache Manager zkontroluje, zda cache pro danou tabulku obsahuje všechny potřebné sloupce:
- Ano → dotaz jde přímo do SQLite RAM (cache hit).
- Ne → re-fetch tabulky z DB s rozšířenou sadou sloupců → přepíše cache → dotaz do SQLite RAM.
SELECT *načte celou tabulku a označí ji jakois_full→ další dotazy na libovolný sloupec jsou cache hit.
Příklad akumulace sloupců:
Dotaz 1: SELECT A, B FROM T3 → Registry: T3 = {A, B} → fetch T3(A,B) z DB
Dotaz 2: SELECT A, D FROM T3 → Registry: T3 = {A, B, D} → re-fetch T3(A,B,D) z DB
Dotaz 3: SELECT B FROM T3 → cache hit, žádný DB dotaz
Dotaz 4: SELECT * FROM T3 → full load všech sloupců, tabulka označena is_full
Dotaz 5: SELECT A FROM T3 JOIN T4 ON … → každá tabulka cachována zvlášť, JOIN běží v RAM
Metadata tabulka _sqlmem_columns (uložena v SQLite):
CREATE TABLE _sqlmem_columns (
table_name TEXT NOT NULL,
column_name TEXT NOT NULL,
PRIMARY KEY (table_name, column_name)
);
- Při startu se načte z
cache.db— Registry ví, co bylo kešováno v minulé session. - Při každém rozšíření sady sloupců se záznam aktualizuje.
3. Cache Manager
- Drží in-memory SQLite instanci.
- Sleduje, které tabulky jsou již načteny.
- Podporuje TTL (Time-to-Live) pro automatické vypršení cache.
- Umožňuje manuální invalidaci konkrétní tabulky.
- Persistence:
- Při inicializaci načte
cache.dbze disku (pokud existuje) do paměti. - Spustí background vlákno, které každou hodinu provede
sqlite3backup API (conn.backup(disk_conn)). - Zaregistruje
atexithandler aSIGTERMhandler pro finální zápis při vypnutí.
- Při inicializaci načte
- Metadata tabulky (
_sqlmem_meta) uložená přímo v SQLite cache:
CREATE TABLE _sqlmem_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
-- Záznamy:
-- app_version verze sqlmem balíčku (např. "0.1.0")
-- schema_version verze schématu cache.db (integer, pro migrace)
-- created_at ISO timestamp prvního vytvoření cache.db
CREATE TABLE _sqlmem_tables (
table_name TEXT PRIMARY KEY,
last_refresh_at TEXT NOT NULL, -- ISO 8601 UTC timestamp
row_count INTEGER
);
last_refresh_atse aktualizuje pokaždé, když se tabulka znovu načte z databáze.- Při načtení
cache.dbze disku se zkontrolujeschema_version— pokud nesedí, cache se zahodí a načte znovu z DB.
3. Query Executor
- Cache hit: Spustí dotaz přímo v in-memory SQLite, vrátí výsledek.
- Cache miss: Načte potřebné tabulky z databáze → uloží do cache → spustí dotaz.
- Zápisy (INSERT / UPDATE / DELETE): Vyhodí výjimku
ReadOnlyErrora dotaz zablokuje. Modul je striktně read-only.
Rozsah MVP
CachingEnginewrapper kompatibilní se SQLAlchemy- Načtení
cache.dbpři startu do in-memory SQLite - Periodický zápis na disk každou hodinu (background vlákno)
- Zápis na disk při vypnutí (
atexit+SIGTERM) - Parser pro detekci tabulek, sloupců a typu dotazu
- Column Registry s akumulační logikou (union sloupců, wildcard detekce)
- Re-fetch cache při rozšíření sady sloupců
- Cache Manager s SQLite in-memory backendem
- Cache hit/miss logika v Query Executoru
- TTL podpora na úrovni tabulky
- Manuální invalidace cache (
engine.cache.invalidate("tabulka")) - Testy pokrývající cache hit, cache miss a blokování zápisů (
ReadOnlyError)
Co modul NEŘEŠÍ (mimo scope)
- INSERT/UPDATE/DELETE — tyto operace jsou zakázány a vyhodí
ReadOnlyError - Redis nebo jiný distribuovaný backend
- Transakční konzistence
Logování
Řízeno přes loguru. Úroveň se nastavuje v .env:
SQLMEM_DEBUG=true # DEBUG level — podrobný výpis každého dotazu, cache operace, backup
# false (výchozí) — INFO a výše
| Level | Kdy |
|---|---|
DEBUG |
Každý příchozí dotaz, extrahované tabulky/sloupce, cache hit/miss/re-fetch, backup start/konec |
INFO |
Start/stop modulu, načtení cache.db, periodický backup, refresh tabulky |
WARNING |
Re-fetch tabulky kvůli novým sloupcům, blížící se TTL expirace |
ERROR |
ReadOnlyError, UnsupportedQueryError, selhání připojení k DB, selhání zápisu cache.db |
Hotové funkce (dříve TODO)
- Parametrizované dotazy:
execute(sql, params)— poziční?i pojmenované:name. - Podpora
SELECT *(wildcard): Načte celou tabulku do cache, označí ji jakois_full— další dotazy na libovolný sloupec jsou vždy cache hit bez re-fetch. - Podpora JOIN: Parser extrahuje sloupce z každé joinované tabulky zvlášť, Column Registry je sleduje nezávisle. Cache Manager zajistí, že všechny potřebné tabulky jsou v paměti před spuštěním dotazu.
- Třídílné názvy tabulek:
[catalog].[schema].[table]se cachuje pod base name, in-memory dotaz prefix stripuje.
TODO — budoucí funkce
- TTL na úrovni tabulky: automatické vypršení cache po nastaveném čase.
Technologie
| Vrstva | Knihovna |
|---|---|
| SQL parsing | sqlglot |
| Cache úložiště | sqlite3 (stdlib) |
| Integrace | SQLAlchemy events / engine wrapper |
| Logování | loguru, python-dotenv |
| Testování | pytest |