Caches¶
Choose by who needs the value.
In the process: LocalCache¶
Use this for what no other process needs. Its methods are synchronous, and values stay Python objects:
from myxo.cache import LocalCache
thumbnails = LocalCache(ttl=60.0, size=10_000)
thumbnails.set("item:1", data)
data = thumbnails.get("item:1") # None if absent or expired
thumbnails.delete("item:1")
Past size, the oldest values go.
Shared by the installation: Cache¶
Use this for what processes share, such as permissions that a revocation must reach in every replica. Declare cache = Cache(), and give get the type to decode:
from myxo.cache import Cache
class Empty(msgspec.Struct, frozen=True):
pass
class Stats(msgspec.Struct, frozen=True):
seen: int
class Notes(Service):
name = "notes"
cache = Cache()
async def stats(self, _: Empty) -> Stats:
return await self.cache.get("seen", Stats) or Stats(0)
async def record(self, seen: int) -> None:
await self.cache.set("seen", Stats(seen), ttl=300.0)
Values are anything msgspec encodes, and get returns None for a key that is absent or expired. The cache lives in the layout's database, so the layout needs a database, and myxo migrate creates the table. Every service and process of the installation reads the same keys, so prefix yours with the service's name, as in notes:seen.
What a shared cache promises¶
On PostgreSQL its table is unlogged, and writes do not wait for the drive. A crash of the database may empty it, which only costs misses. So:
- Keep nothing in it that you can't compute again.
setis fast, and may be lost in a crash.deleteis durable, so that a revocation stays revoked.
Each process removes expired entries now and then, every 60 s.