DataStore Bridge
The DataStore Bridge seamlessly connects Raddish's in-memory cache with Roblox's persistent DataStore, giving you the best of both worlds: speed and persistence.
Overview
Problem: DataStore is slow (100-300ms per call) and rate-limited.
Solution: Raddish caches everything in memory, then syncs with DataStore in the background.
Game Code → Raddish Cache (instant) → DataStore (background sync)Write Modes
Write-Through (Default)
Data is written to both cache and DataStore immediately.
Pros:
- Data is always persisted
- Safe against server crashes
Cons:
- Slightly slower writes (still faster than pure DataStore)
lua
-- Configured in DataStoreBridge.lua
local USE_WRITE_THROUGH = trueWrite-Back
Data is written to cache immediately, then DataStore in batches.
Pros:
- Fastest writes
- Reduces DataStore API calls
Cons:
- Risk of data loss if server crashes before flush
lua
-- Configured in DataStoreBridge.lua
local USE_WRITE_THROUGH = false
local WRITE_BACK_INTERVAL = 30 -- Flush every 30 secondsBasic Usage
Get with Cache
lua
local Raddish = require(game.ReplicatedStorage.Raddish)
game.Players.PlayerAdded:Connect(function(player)
-- First call: Loads from DataStore (slow)
local coins = Raddish.GetWithCache(player, "Coins", 0, 300)
-- Subsequent calls: From cache (instant!)
local coins2 = Raddish.GetWithCache(player, "Coins", 0, 300)
print(player.Name, "has", coins, "coins")
end)Set with Cache
lua
-- Update player coins
function giveCoins(player, amount)
local current = Raddish.GetWithCache(player, "Coins", 0)
local newAmount = current + amount
-- Updates cache + DataStore
Raddish.SetWithCache(player, "Coins", newAmount, 300)
return newAmount
endIncrement with Cache
lua
-- Atomic increment
local newValue = Raddish.IncrementWithCache(player, "Wins", 1, 0, 300)
print("Player now has", newValue, "wins")
-- Decrement (negative delta)
local newLives = Raddish.IncrementWithCache(player, "Lives", -1, 3, 300)Complete Player Data Example
lua
local Raddish = require(game.ReplicatedStorage.Raddish)
-- Player join: Load all data
game.Players.PlayerAdded:Connect(function(player)
local profile = {
coins = Raddish.GetWithCache(player, "Coins", 0, 300),
level = Raddish.GetWithCache(player, "Level", 1, 300),
experience = Raddish.GetWithCache(player, "Experience", 0, 300),
inventory = Raddish.GetWithCache(player, "Inventory", {}, 300),
settings = Raddish.GetWithCache(player, "Settings", {
music = true,
sfx = true,
graphics = "high"
}, 300)
}
print("Loaded profile for", player.Name)
print(profile)
-- Store in player object for easy access
player:SetAttribute("Coins", profile.coins)
player:SetAttribute("Level", profile.level)
end)
-- Update data
function updatePlayerData(player, key, value)
Raddish.SetWithCache(player, key, value, 300)
player:SetAttribute(key, value)
end
-- Give coins
function giveCoins(player, amount)
local newCoins = Raddish.IncrementWithCache(player, "Coins", amount, 0, 300)
player:SetAttribute("Coins", newCoins)
return newCoins
end
-- Save on leave
game.Players.PlayerRemoving:Connect(function(player)
-- Flush player's data to DataStore
local flushed = Raddish.FlushPlayer(player)
print("Saved", flushed, "keys for", player.Name)
end)Cache Management
Invalidate Cache
Force reload from DataStore:
lua
-- Player reports incorrect coins
function resetPlayerCache(player)
Raddish.InvalidateCache(player, "Coins")
-- Next GetWithCache will reload from DataStore
local coins = Raddish.GetWithCache(player, "Coins", 0)
print("Reloaded coins:", coins)
endManual Flush
Force save to DataStore:
lua
-- Flush all pending writes
local count = Raddish.Flush()
print("Flushed", count, "pending writes")
-- Flush specific player
local count = Raddish.FlushPlayer(player)
print("Flushed", count, "keys for", player.Name)Advanced Patterns
Session-Based Data
lua
-- Temporary session data with auto-save
function createPlayerSession(player)
local sessionData = {
joinTime = os.time(),
serverHops = 0,
activeQuests = {},
buffs = {}
}
-- Store in cache only (no DataStore)
Raddish.Set("session:" .. player.UserId, sessionData, 3600)
-- Persistent data from DataStore
local coins = Raddish.GetWithCache(player, "Coins", 0)
return {
session = sessionData,
persistent = {
coins = coins
}
}
endComplex Data Structures
lua
-- Store table data
function saveInventory(player, inventory)
-- Raddish handles JSON encoding/decoding automatically
Raddish.SetWithCache(player, "Inventory", inventory, 300)
end
function loadInventory(player)
return Raddish.GetWithCache(player, "Inventory", {
weapons = {},
armor = {},
consumables = {}
}, 300)
end
-- Usage
local inventory = loadInventory(player)
table.insert(inventory.weapons, "Legendary Sword")
saveInventory(player, inventory)Versioned Data
lua
-- Handle data version changes
function loadPlayerData(player)
local data = Raddish.GetWithCache(player, "Profile", nil, 300)
if not data then
-- New player
data = {
version = 2,
coins = 0,
level = 1,
inventory = {}
}
Raddish.SetWithCache(player, "Profile", data, 300)
elseif data.version == 1 then
-- Migrate from version 1 to 2
data.version = 2
data.inventory = data.items or {} -- Renamed field
data.items = nil
Raddish.SetWithCache(player, "Profile", data, 300)
print("Migrated", player.Name, "to version 2")
end
return data
endAuto-Save System
lua
-- Automatically save player data every 5 minutes
local autoSavePlayers = {}
game.Players.PlayerAdded:Connect(function(player)
autoSavePlayers[player.UserId] = true
end)
game.Players.PlayerRemoving:Connect(function(player)
autoSavePlayers[player.UserId] = nil
Raddish.FlushPlayer(player)
end)
-- Auto-save loop
task.spawn(function()
while true do
task.wait(300) -- 5 minutes
for userId in pairs(autoSavePlayers) do
local player = game.Players:GetPlayerByUserId(userId)
if player then
local saved = Raddish.FlushPlayer(player)
if saved > 0 then
print("Auto-saved", saved, "keys for", player.Name)
end
end
end
end
end)Backup & Recovery
Create Backup
lua
function createBackup(player)
local backup = {
coins = Raddish.GetWithCache(player, "Coins", 0),
level = Raddish.GetWithCache(player, "Level", 1),
inventory = Raddish.GetWithCache(player, "Inventory", {}),
timestamp = os.time()
}
-- Store backup
Raddish.SetWithCache(player, "Backup_" .. os.time(), backup, 86400) -- 24 hour TTL
print("Created backup for", player.Name)
end
-- Auto-backup on level up
function levelUp(player)
createBackup(player) -- Backup before major change
local newLevel = Raddish.IncrementWithCache(player, "Level", 1, 1)
print(player.Name, "leveled up to", newLevel)
endRestore from Backup
lua
function restoreBackup(player, timestamp)
local backup = Raddish.GetWithCache(player, "Backup_" .. timestamp, nil)
if backup then
Raddish.SetWithCache(player, "Coins", backup.coins)
Raddish.SetWithCache(player, "Level", backup.level)
Raddish.SetWithCache(player, "Inventory", backup.inventory)
print("Restored", player.Name, "to backup from", os.date("%c", backup.timestamp))
else
warn("Backup not found!")
end
endDataStore Keys
Raddish uses this key format for DataStore:
lua
-- Cache key: "userId_DataStoreKey"
-- Example: "123456789_Coins"
-- DataStore name: "Coins"
-- DataStore key: "123456789"You can access the DataStore directly if needed:
lua
local DataStoreService = game:GetService("DataStoreService")
-- Same DataStore that Raddish uses
local coinsStore = DataStoreService:GetDataStore("Coins")
local coins = coinsStore:GetAsync(tostring(player.UserId))Performance Comparison
lua
-- Traditional DataStore (slow)
local function traditionalLoad(player)
local coins = DataStoreService:GetDataStore("Coins"):GetAsync(tostring(player.UserId))
local level = DataStoreService:GetDataStore("Level"):GetAsync(tostring(player.UserId))
local inventory = DataStoreService:GetDataStore("Inventory"):GetAsync(tostring(player.UserId))
-- 3 separate DataStore calls = 300-900ms total
end
-- With Raddish (fast)
local function raddishLoad(player)
local coins = Raddish.GetWithCache(player, "Coins", 0)
local level = Raddish.GetWithCache(player, "Level", 1)
local inventory = Raddish.GetWithCache(player, "Inventory", {})
-- First load: 300-900ms (same as traditional)
-- Subsequent loads: <3ms (300x faster!)
endError Handling
lua
-- Raddish handles errors gracefully
local coins = Raddish.GetWithCache(player, "Coins", 0)
-- If DataStore fails: Returns default value (0)
-- If cache fails: Attempts DataStore
-- If both fail: Returns default value
-- Check for errors in logs
local success, coins = pcall(function()
return Raddish.GetWithCache(player, "Coins", 0)
end)
if not success then
warn("Failed to load coins:", coins)
-- Fallback behavior
endBest Practices
Always set TTL on cached data
luaRaddish.GetWithCache(player, "Coins", 0, 300) -- 5 min TTLUse default values
lua-- Good: Provides default Raddish.GetWithCache(player, "Coins", 0) -- Bad: No default, might return nil Raddish.GetWithCache(player, "Coins")Flush on player leave
luagame.Players.PlayerRemoving:Connect(function(player) Raddish.FlushPlayer(player) end)Use consistent keys
lua-- Good: Clear naming "Coins", "Level", "Inventory" -- Bad: Inconsistent "coins", "playerLevel", "inv_data"Handle server shutdown
luagame:BindToClose(function() -- Flush all pending writes Raddish.Flush() task.wait(5) -- Give time to save end)