Skip to content

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 = true

Write-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 seconds

Basic 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
end

Increment 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)
end

Manual 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
        }
    }
end

Complex 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
end

Auto-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)
end

Restore 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
end

DataStore 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!)
end

Error 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
end

Best Practices

  1. Always set TTL on cached data

    lua
    Raddish.GetWithCache(player, "Coins", 0, 300) -- 5 min TTL
  2. Use default values

    lua
    -- Good: Provides default
    Raddish.GetWithCache(player, "Coins", 0)
    
    -- Bad: No default, might return nil
    Raddish.GetWithCache(player, "Coins")
  3. Flush on player leave

    lua
    game.Players.PlayerRemoving:Connect(function(player)
        Raddish.FlushPlayer(player)
    end)
  4. Use consistent keys

    lua
    -- Good: Clear naming
    "Coins", "Level", "Inventory"
    
    -- Bad: Inconsistent
    "coins", "playerLevel", "inv_data"
  5. Handle server shutdown

    lua
    game:BindToClose(function()
        -- Flush all pending writes
        Raddish.Flush()
        task.wait(5) -- Give time to save
    end)

"Beyond Boundaries. Beyond Imagination."