Persistence: cache + write-back
Reading and writing the database on every high-frequency action adds avoidable load. The cache plus write-back pattern keeps working state in RAM, marks it dirty when it changes, and flushes snapshots on a timer and at graceful lifecycle points. It reduces writes in exchange for a bounded crash-loss window, so it is a deliberate tradeoff rather than a guarantee that no data can ever be lost.
Build it
Make the resource folder
Inside your server's resources folder, create this folder:
resources/qu_persistence
Create the files
Create this exact file layout:
resources/qu_persistence/
fxmanifest.lua
server.lua
Write fxmanifest.lua
Open fxmanifest.lua and paste this:
fx_version 'cerulean'
game 'gta5'
server_script '@oxmysql/lib/MySQL.lua'
server_script 'server.lua'
dependencies {
'oxmysql'
}
oxmysql is the database library this lesson talks to. It is an Overextended (community) resource, not something Cfx.re ships in the box, so you install and start it yourself. The dependencies { 'oxmysql' } block tells FiveM to load oxmysql before this resource, so the MySQL global exists by the time your server.lua runs. You will see the singular dependency 'oxmysql' in older tutorials. It still works; the docs list both forms, and the plural block is the one to reach for when a resource has more than one dependency.
Now create the table. This lesson assumes MariaDB, which is the 2026-preferred database for FiveM servers. oxmysql speaks to MariaDB and MySQL identically, so the SQL below is the same either way. Run it once in a database client that is connected to the same database your mysql_connection_string points at (for example HeidiSQL or DBeaver), before you start the resource:
CREATE TABLE IF NOT EXISTS qu_persistence_rows (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(80) NOT NULL,
amount INT NOT NULL DEFAULT 0,
UNIQUE KEY uniq_name (name)
);
The UNIQUE KEY uniq_name (name) line is load-bearing and the next section explains why. Without it, the write-back silently breaks.
Write the lesson code
Open server.lua and paste this:
local cache = {
cash = 0,
dirty = false,
version = 0,
loaded = false,
flushing = false,
}
local function flush()
if not cache.loaded or not cache.dirty or cache.flushing then return false end
cache.flushing = true
local amount = cache.cash
local version = cache.version
local ok, err = pcall(MySQL.query.await,
'INSERT INTO qu_persistence_rows (name, amount) VALUES (?, ?) ON DUPLICATE KEY UPDATE amount = VALUES(amount)',
{ 'wallet', amount }
)
cache.flushing = false
if not ok then
print('[qu_persistence] flush failed: ' .. tostring(err))
return false
end
-- Do not clear a newer change that happened while the query was awaiting.
if cache.version == version then cache.dirty = false end
print('[qu_persistence] flushed ' .. amount)
return true
end
RegisterCommand('cashadd', function(_, args)
if not cache.loaded then
print('[qu_persistence] wallet is still loading, try again')
return
end
local amount = tonumber(args[1])
if not amount then
print('[qu_persistence] usage: cashadd <number>')
return
end
cache.cash = cache.cash + amount
cache.version = cache.version + 1
cache.dirty = true
print('[qu_persistence] cached ' .. cache.cash)
end, true)
CreateThread(function()
local saved = MySQL.scalar.await(
'SELECT amount FROM qu_persistence_rows WHERE name = ?',
{ 'wallet' }
)
cache.cash = tonumber(saved) or 0
cache.loaded = true
print('[qu_persistence] loaded ' .. cache.cash)
while true do
Wait(30000)
flush()
end
end)
AddEventHandler('playerDropped', function()
flush()
end)
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
flush()
end)
Start and test it
Open server.cfg and add this line:
ensure qu_persistence
Save the file. The ensure qu_persistence line in server.cfg makes the server start this resource on boot. To apply your change now without rebooting, type the next command into your live server console (the txAdmin Live Console, or the black FXServer console window if you run it directly):
restart qu_persistence
Run this test. In the same server console, type just this command and press Enter:
cashadd 25
Then leave the server running and wait about 30 seconds for the timer to flush.
Now run restart qu_persistence again and wait for [qu_persistence] loaded 25. That line proves the new Lua cache hydrated from the saved row instead of resetting to zero. Run SELECT amount FROM qu_persistence_rows WHERE name = 'wallet' in your database tool and confirm there is exactly one row, not a new one for every flush.
Keep reading the full lesson
Sign in to start, then unlock every step of this lesson and the full FiveM School with a membership.
- How it works
- If something went wrong
- What you can do now
- Try it yourself
The remainder of Persistence: cache and write-back is available to FiveM School members.