Caching
Overview
Caching is optional application infrastructure for avoiding repeated Discord lookups. Attach a cache with bot.WithCache; the bot can hydrate it from gateway events and the high-level Cached* helpers can read typed resources. Matching Fetch* helpers make an authenticated REST request and populate the cache after a successful response.
Architecture
The cache.Cache interface stores any values by string key. Typed subinterfaces such as GuildCache, MemberCache, and MessageCache add resource-specific methods. cache.MemoryCache implements all built-in typed interfaces and can be configured with TTL and maximum size options.
The bot performs type assertions before each typed lookup. If no cache is configured, or the configured implementation does not implement the requested typed interface, Cached* returns (nil, false). Fetch methods always use Bot.Rest; cache hydration is a successful-result side effect, not a replacement for error handling.
Quick Start
This complete program uses a bounded memory cache and reports whether the invoking user's fresh resource was already cached.
package main
import (
"log"
"os"
"time"
"github.com/discord-go/discord.go/bot"
"github.com/discord-go/discord.go/cache"
"github.com/discord-go/discord.go/intents"
"github.com/discord-go/discord.go/snowflake"
)
func main() {
token := os.Getenv("DISCORD_TOKEN")
if token == "" {
log.Fatal("DISCORD_TOKEN is required")
}
router := bot.NewRouter()
router.Command("user-cache", "Inspect the invoking user cache", func(ctx *bot.InteractionContext) {
var id snowflake.ID
if ctx.User != nil {
id = ctx.User.ID
} else if ctx.Member != nil && ctx.Member.User != nil {
id = ctx.Member.User.ID
}
if id == 0 {
_ = ctx.Reply("No invoking user was present")
return
}
if _, ok := ctx.Bot.CachedUser(id); ok {
_ = ctx.Reply("The user was in cache")
return
}
user, err := ctx.Bot.FetchUser(ctx.Context(), id)
if err != nil {
_ = ctx.Reply("Could not fetch the user")
return
}
_ = ctx.Reply("Fetched " + user.Username)
})
store := cache.NewMemoryCache(cache.WithTTL(10*time.Minute), cache.WithMaxSize(10000))
b := bot.New(token, bot.WithIntents(intents.Guilds), bot.WithCache(store), bot.WithRouter(router))
if err := b.Run(); err != nil {
log.Fatal(err)
}
}Creating/Configuration
cache.NewMemoryCache(opts ...cache.Option) *cache.MemoryCache creates the built-in store. cache.WithTTL sets entry expiration and WithMaxSize limits the number of keys; zero means no limit. Call WithCache(store) when creating the bot.
For a database or distributed cache, implement cache.Cache plus the typed interfaces needed by the high-level helpers. Values may be pointers or values; the bot accepts either when the concrete type matches.
Using
Basic: cached read
Call CachedGuild, CachedChannel, CachedUser, CachedMember, or CachedMessage. Each returns a typed pointer and a boolean hit indicator.
Intermediate: cache-aside fetch
Try a cache lookup, then call the matching Fetch* method on a miss. Successful fetches call the typed cache setter automatically.
Advanced: direct cache maintenance
Use the cache interface's Delete, Clear, and typed delete methods when an application mutation makes a value stale. Use MemoryCache.CleanUp if a long-running process wants to remove expired entries proactively.
Common Patterns
- Use cache keys only through typed methods where possible.
- Treat a cache hit as a snapshot and a miss as normal control flow.
- Fetch after a mutation when the fresh representation is needed immediately.
- Set TTLs for users, channels, and messages whose values change frequently.
- Keep cache size and gateway intents aligned; caching events you did not request is impossible.
Best Practices
Use cache-aside reads
Why: the application decides when stale data is acceptable.
Pros: simple, explicit consistency policy and fewer REST calls.
Cons: the first request is slower and concurrent misses can stampede the API.
Bound memory
Why: gateway-driven caches can grow with guild and message volume.
Pros: predictable resource use.
Cons: eviction or expiry causes more REST fetches and can reduce hit rate.
Invalidate after writes
Why: a cached resource can immediately become outdated after a REST mutation.
Pros: subsequent reads do not return a known stale value.
Cons: manual invalidation is easy to forget; prefer fetching the returned object when the endpoint provides one.
Common Mistakes
Incorrect: assuming a cache exists because CachedUser has a simple signature.
user, _ := b.CachedUser(id)
fmt.Println(user.Username)Correct: check the boolean and fetch on a miss.
user, ok := b.CachedUser(id)
if !ok {
var err error
user, err = b.FetchUser(ctx, id)
if err != nil {
return err
}
}
fmt.Println(user.Username)Incorrect: using WithMaxSize(0) as a safety limit.
cache.NewMemoryCache(cache.WithMaxSize(0))Correct: supply an explicit bound for high-volume processes.
cache.NewMemoryCache(cache.WithMaxSize(10000))API Walkthrough
cache.CacherequiresGet,Set,Delete, andClear.GuildCache,ChannelCache,UserCache,RoleCache,MessageCache, andMemberCacheadd typed-key methods toCache.OptionshasTTLandMaxSize;Optionisfunc(*Options).DefaultOptions,WithTTL, andWithMaxSizeconfigure caches.NewMemoryCache(opts ...Option) *MemoryCachecreates a thread-safe in-memory cache.MemoryCache.Get,Set,Delete,Clear, andCleanUpmanage it; typedGetGuild,SetGuild, and matching methods exist for every supported resource.WithCache(cache.Cache) bot.Optionattaches a cache and enables gateway hydration.CachedGuild,CachedChannel,CachedUser,CachedMember, andCachedMessagereturn typed pointers and hit booleans.FetchGuild,FetchChannel,FetchUser,FetchMember, andFetchMessageacceptcontext.Contextand IDs, return typed pointers and errors, and set the corresponding cache entry after a successful REST response.
Examples
Related APIs
resources.mdfor cache and REST resource helpers.permissions.mdfor prefix permission checks using members.../low-level/cache/README.mdfor cache interfaces.