Skip to main content
Version: 1.2.1

Cache

The cache stores query results in memory to avoid unnecessary database calls. If you look up the same player 10 times in 1 second, the database is only queried once.

Enabling the cache

By default, the cache is disabled. To enable it:

-- Enable with a 60-second TTL (time-to-live for cached data)
OrvexORM.enableCache({ ttl = 60 })

The TTL (Time To Live) is the duration in seconds that data remains in the cache before being considered stale.

How it works

Without cache

-- Each call queries the database
local p1 = User.find({ identifier = "license:abc" }) -- → SQL query
local p2 = User.find({ identifier = "license:abc" }) -- → SQL query (again)
local p3 = User.find({ identifier = "license:abc" }) -- → SQL query (again)
-- = 3 SQL queries

With cache

OrvexORM.enableCache({ ttl = 30 })

local p1 = User.find({ identifier = "license:abc" }) -- → SQL query (cache miss)
local p2 = User.find({ identifier = "license:abc" }) -- → cache (no SQL!)
local p3 = User.find({ identifier = "license:abc" }) -- → cache (no SQL!)
-- = 1 single SQL query

Automatic invalidation

The cache is automatically invalidated when you modify data. You don't have to worry about it.

local player = User.find({ identifier = "license:abc" }) -- cache miss → SQL
local player = User.find({ identifier = "license:abc" }) -- cache hit

-- Modification → the "users" table cache is cleared
User.update({ money = 1000 }, { identifier = "license:abc" })

local player = User.find({ identifier = "license:abc" }) -- cache miss → SQL (fresh data)

Operations that invalidate the cache:

OperationInvalidates cache?
create()Yes
upsert()Yes
update() (class and instance)Yes
delete() (class and instance)Yes
attach() / detach()Yes (pivot table)
find() / findAll()No (read-only)
query():get()No (read-only)

Managing the cache manually

Flush the cache for a model

User.flushCache()

Flush all caches

OrvexORM.flushCache()

Disable the cache

OrvexORM.disableCache()

Monitoring the cache

OrvexORM.cacheStats() returns global cache statistics, useful for checking that the cache is actually helping:

local stats = OrvexORM.cacheStats()

print(stats.hits) -- number of cache hits
print(stats.misses) -- number of cache misses
print(stats.hitRate) -- hit ratio (hits / (hits + misses))
print(stats.entries) -- number of entries currently cached
tip

A low hitRate means most queries never hit the cache — try increasing the TTL, or check that you're not caching data that changes on every query.

Choosing the right TTL

SituationRecommended TTL
Data that rarely changes (configs, roles)300 (5 minutes)
Data that changes often (money, inventory)10 - 30 seconds
Real-time data (position, health)Don't use the cache

:::caution Beware of caching critical data The cache may return slightly stale data (up to the TTL). For critical operations like money transfers, it's safer to not cache this data or to use a very short TTL. :::

Cache with the Query Builder

The cache also works with the Query Builder:

-- This query is cached
local cops = User.query()
:where({ job = "police" })
:orderBy("money", "DESC")
:limit(10)
:get()

-- If the same query is run again within the TTL, the cache is used
local cops2 = User.query()
:where({ job = "police" })
:orderBy("money", "DESC")
:limit(10)
:get()
-- → no SQL query, identical result from the cache

Aggregations (count, sum, avg, min, max) are also cached.