Raddish - Redis-like Cache & Networking for Roblox

Raddish - Redis-like Cache & Networking for Roblox

Wally Creator Store GitHub License
Documentation

TL;DR: Raddish is a high-performance, Redis-inspired caching and networking system for Roblox that’s 100-1000x faster than DataStore for frequently accessed data.


:rocket: What is Raddish?

Raddish brings Redis-like functionality to Roblox, providing blazing-fast in-memory caching, cross-server pub/sub, and powerful networking utilities - all in one lightweight module.

Key Features

  • :high_voltage: Lightning-Fast Cache - 100-1000x faster than DataStore with automatic TTL expiration
  • :counterclockwise_arrows_button: Cross-Server Pub/Sub - Real-time event broadcasting via MessagingService
  • :bar_chart: Redis Data Structures - Hashes, Lists, Sets, and Sorted Sets
  • :floppy_disk: DataStore Bridge - Automatic syncing with write-through/write-back modes
  • :globe_with_meridians: HTTP Client - Built-in external API support with caching and retry logic
  • :shield: Rate Limiting - Automatic protection for all services
  • :broom: LRU Eviction - Smart memory management (configurable max capacity)

:package: Installation

Via Roblox Creator Store

:inbox_tray: Get Raddish on Creator Store

Click the link above, then insert into ReplicatedStorage in Studio.

Via Wally (Recommended for Developers)

[dependencies]
Raddish = "verifiedhawaii/raddish@1.0.1"

Then run:

wally install

Manual Download

Download from GitHub Releases


:fire: Quick Examples

Basic Caching
local Raddish = require(game.ReplicatedStorage.Raddish)

-- Cache player data with 5-minute TTL
Raddish.Set("player:123:coins", 1000, 300)

-- Retrieve from cache
local coins = Raddish.Get("player:123:coins")
print(coins) -- 1000

-- Atomic increment
Raddish.Incr("player:123:coins", 50)
print(Raddish.Get("player:123:coins")) -- 1050
Cross-Server Events (Pub/Sub)
-- Subscribe to global events
Raddish.Subscribe("server:shutdown", function(data)
    print("Server shutting down:", data.reason)
    -- Gracefully save player data
end)

-- Publish to all servers
Raddish.Publish("server:shutdown", {
    reason = "Maintenance",
    duration = 30
})
Leaderboards with Sorted Sets
-- Add player scores
Raddish.ZAdd("global:leaderboard", 1500, "Player1")
Raddish.ZAdd("global:leaderboard", 2300, "Player2")
Raddish.ZAdd("global:leaderboard", 1800, "Player3")

-- Get top 10 players
local top10 = Raddish.ZRange("global:leaderboard", 1, 10, true)

for rank, entry in ipairs(top10) do
    print(rank, entry.member, entry.score)
end
-- Output:
-- 1 Player2 2300
-- 2 Player3 1800
-- 3 Player1 1500
DataStore Integration
-- Automatically cache DataStore reads
local playerData = Raddish.GetWithCache(
    myDataStore,
    "player_123",
    300  -- Cache for 5 minutes
)

-- Write-through cache (updates both cache and DataStore)
Raddish.SetWithCache(
    myDataStore,
    "player_123",
    playerData,
    300
)
HTTP Requests with Caching
-- Fetch external API with automatic caching
local response = Raddish.HttpGet("https://api.example.com/data", {
    cache = true,
    cacheTTL = 600,  -- Cache for 10 minutes
    headers = {
        ["Authorization"] = "Bearer token123"
    }
})

if response.Success then
    print(response.Body)
else
    warn("Request failed:", response.StatusCode)
end

:bullseye: Use Cases

1. Session Data

Store temporary player data that doesn’t need DataStore persistence:

-- Store active session
Raddish.HSet("session:" .. player.UserId, "joinTime", os.time())
Raddish.HSet("session:" .. player.UserId, "lastAction", "Lobby")
Raddish.Expire("session:" .. player.UserId, 3600) -- 1 hour TTL
2. Rate Limiting

Prevent spam and abuse:

local key = "ratelimit:chat:" .. player.UserId
local count = Raddish.Incr(key)

if count == 1 then
    Raddish.Expire(key, 60) -- Reset every minute
end

if count > 10 then
    return -- Player exceeded 10 messages per minute
end
3. Real-time Leaderboards

Update and query leaderboards instantly:

-- Update score
Raddish.ZAdd("weekly:kills", kills, player.Name)

-- Get player rank
local rank = Raddish.ZRank("weekly:kills", player.Name, true)
print(player.Name .. " is rank #" .. rank)
4. Cross-Server Coordination

Synchronize events across all servers:

-- Subscribe in all servers
Raddish.Subscribe("game:event:started", function(data)
    -- Start event in this server
    startEvent(data.eventType)
end)

-- Trigger from one server
Raddish.Publish("game:event:started", {
    eventType = "Boss Fight"
})

:bar_chart: Performance Comparison

Operation DataStore Raddish Cache Speedup
Get ~50-200ms ~0.1-1ms 100-2000x
Set ~50-200ms ~0.1-1ms 100-2000x
Increment ~100-300ms ~0.1-1ms 200-3000x
Sorted Set Query N/A ~1-5ms ∞ (not possible)

Note: Cache performance is for frequently accessed data. DataStore is still required for persistence.


:hammer_and_wrench: API Overview

Cache Operations
  • Set(key, value, ttl?) - Store with optional TTL
  • Get(key) - Retrieve value
  • Del(key) - Delete key
  • Exists(key) - Check if key exists
  • Expire(key, ttl) - Set expiration
  • TTL(key) - Get remaining TTL
  • Incr/Decr(key, amount?) - Atomic increment/decrement
Data Structures
  • Hashes: HSet, HGet, HGetAll, HDel
  • Lists: LPush, RPush, LPop, RPop, LRange
  • Sets: SAdd, SRem, SMembers, SIsMember
  • Sorted Sets: ZAdd, ZRem, ZRange, ZRank, ZScore
Pub/Sub
  • Subscribe(channel, callback) - Listen for events
  • Publish(channel, data) - Broadcast event
  • Unsubscribe(channel, callback?) - Stop listening
  • GetHistory(channel, limit?) - Get recent messages
Networking
  • HttpGet/Post/Put/Delete/Patch(url, options?) - HTTP requests
  • HttpBatch(requests) - Batch multiple requests
  • SendDiscordWebhook(url, data) - Discord integration
DataStore Bridge
  • GetWithCache(dataStore, key, ttl?) - Cached reads
  • SetWithCache(dataStore, key, value, ttl?) - Write-through
  • IncrementWithCache(dataStore, key, delta, ttl?) - Cached increment

:books: Documentation

Full documentation available at: Getting Started | hawaiian.gg Docs

GitHub Repository: GitHub - VerifiedHawaii/Raddish: A roblox version of Redis · GitHub


:handshake: Contributing

Contributions are welcome! Feel free to:

  • Report bugs
  • Suggest features
  • Submit pull requests
  • Improve documentation

:page_facing_up: License

MIT License - Free to use in commercial and personal projects.


Made with :heart: by @VerifiedHawaii

14 Likes

Just a quick question before I implement this for time trials and ranked, etc.,

Does this work in tandem with other libraries such as ProfileStore, ProfileService, etc., or is it in a sense isolated and its own system?

While writing this, I’ve made shallow research into the different ways to set up your library, and I am also unfamiliar with Redis so I’ll look into that too - with this in mind, please excuse the potential intelligence level of this question :sweat_smile: :folded_hands:

for datastores its partially compatible, but its not recommended to use it with ProfileService, ProfileStore, & etc.. unless your using native datastore.
and the rest should be fine.

So I couldn’t obtain tables, integers, strings, etc. from ProfileStore then just send them through Raddish? Or would this be considered redundant as the library has its own data handling system?

u do

updating/setting the data using raddish is not recommended.

But I thought that you can work with real time leaderboards..? If a player, for example, clears a time trial in record time, how would Raddish be used if not to just put the data you give it and sort it to apply to all leaderboards on all devices?

Please excuse my lack of knowledge - this misunderstanding may be due to the fact that I haven’t used Data Stores properly and went straight to Profile Store as a be all end all.

Hi,
I haven’t tried this module yet, but if it is supposed to work like Redis, it shouldn’t really block you from doing anything like that, but it depends on how you implement its usage.

For global data, it could be useful, but they should not be the same as the individual player data (should not update the player data based on the cache). For example, you could use it for a global leaderboard, like you could with memory stores as well. Now player data caching should not be necessary when you’re using something like profile store (or profile service), as they already handle that for you per server instance.

Your datastore is your source of truth, your cache is there to make it faster to read data without needing to request it to the datastore. Which means, you should not be updating your data from what is cached, but updating your cache based on what is in your datastore.

Feel free to ask questions.

Obs: I have not used Redis in a production system, and neither have I interacted much with it as today, so I could be wrong.

im kidding this is super cool <3

So, when it comes to real-time leaderboards, are they simply updated periodically with Raddish? Since otherwise caching would become less useful…
I’m trying to understand this more due to the nature of Profile Store and how it cannot support leaderboards as you’d need Ordered Data Stores (which, to reiterate, I’ve not learned much about).
And to further the point, what I am understanding is the server could check if a player sets a new record and if they have, it is saved to Profile Store and can then be pinged through to Raddish which allows leaderboards to update almost immediately?

From my understanding, this resource is a distributed cache, which means, it can be used for the leaderboard in the sense that it would update globally on change (I suppose, I’ve been too busy to touch studio recently).

The cache main purpose is to reduce the amount of requests needed to fetch information to the datastore, which is very useful for frequently accessed and modified data that is likely to be requested from multiple points (more than a single server instance).

How you will use it and is completely up to you though.

From my understanding, you should not put the cache and your datastore manipulator (profile store) to manipulate the same data.
Just have the cache handle the global data, and profile store the individual data (as it was meant to be).

Use cache for replicating primitive global data that doesn’t not necessarily need to be precise, and have your player data be handled by your profile store, without needing a cache for it. They should be separated. Just update the global data once a dependant player data is altered, like coins, for example.

Extra: you can also take a look at memory store service, as it looks like a distributed cache to me.

1 Like

Okay, thank you for your aid in my understanding, it means a lot! Have a good rest of your day!

1 Like