Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/disgoorg/disgo/llms.txt

Use this file to discover all available pages before exploring further.

DisGo’s caching system is highly extensible, allowing you to implement custom cache backends, policies, and strategies to fit your application’s needs.

Understanding the cache system

DisGo uses a multi-layered cache system:
  • Cache - Simple key-value store for single entities (guilds, channels, etc.)
  • GroupedCache - Two-level cache for entities grouped by ID (members by guild, messages by channel)
  • Flags - Control which entity types to cache
  • Policies - Control which specific entities to cache

Cache interfaces

Cache interface

The basic Cache[T] interface for single entities:
type Cache[T any] interface {
    // Get returns a copy of the entity with the given ID
    Get(id snowflake.ID) (T, bool)

    // Put stores the entity with the given ID
    Put(id snowflake.ID, entity T)

    // Remove removes and returns the entity
    Remove(id snowflake.ID) (T, bool)

    // RemoveIf removes all entities matching the filter
    RemoveIf(filterFunc FilterFunc[T])

    // Len returns the number of cached entities
    Len() int

    // All returns an iterator over all entities
    All() iter.Seq[T]
}

GroupedCache interface

For entities grouped by a parent ID (like members in guilds):
type GroupedCache[T any] interface {
    Get(groupID snowflake.ID, id snowflake.ID) (T, bool)
    Put(groupID snowflake.ID, id snowflake.ID, entity T)
    Remove(groupID snowflake.ID, id snowflake.ID) (T, bool)
    GroupRemove(groupID snowflake.ID)
    RemoveIf(filterFunc GroupedFilterFunc[T])
    GroupRemoveIf(groupID snowflake.ID, filterFunc GroupedFilterFunc[T])
    Len() int
    GroupLen(groupID snowflake.ID) int
    All() iter.Seq2[snowflake.ID, T]
    GroupAll(groupID snowflake.ID) iter.Seq[T]
}

Implementing a custom cache

Here’s a complete example implementing a custom grouped cache with an LRU eviction strategy:
package main

import (
    "container/list"
    "iter"
    "sync"

    "github.com/disgoorg/disgo/cache"
    "github.com/disgoorg/disgo/discord"
    "github.com/disgoorg/snowflake/v2"
)

type lruGroupedCache[T any] struct {
    maxSize int
    cache   map[snowflake.ID]map[snowflake.ID]*list.Element
    lru     *list.List
    mu      sync.RWMutex
}

type lruEntry[T any] struct {
    groupID snowflake.ID
    id      snowflake.ID
    value   T
}

func newLRUGroupedCache[T any](maxSize int) cache.GroupedCache[T] {
    return &lruGroupedCache[T]{
        maxSize: maxSize,
        cache:   make(map[snowflake.ID]map[snowflake.ID]*list.Element),
        lru:     list.New(),
    }
}

func (c *lruGroupedCache[T]) Get(groupID snowflake.ID, id snowflake.ID) (T, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()

    groupEntities, ok := c.cache[groupID]
    if !ok {
        var zero T
        return zero, false
    }

    element, ok := groupEntities[id]
    if !ok {
        var zero T
        return zero, false
    }

    // Move to front (most recently used)
    c.lru.MoveToFront(element)

    entry := element.Value.(*lruEntry[T])
    return entry.value, true
}

func (c *lruGroupedCache[T]) Put(groupID snowflake.ID, id snowflake.ID, entity T) {
    c.mu.Lock()
    defer c.mu.Unlock()

    groupEntities, ok := c.cache[groupID]
    if !ok {
        groupEntities = make(map[snowflake.ID]*list.Element)
        c.cache[groupID] = groupEntities
    }

    // Check if already exists
    if element, ok := groupEntities[id]; ok {
        c.lru.MoveToFront(element)
        entry := element.Value.(*lruEntry[T])
        entry.value = entity
        return
    }

    // Add new entry
    entry := &lruEntry[T]{
        groupID: groupID,
        id:      id,
        value:   entity,
    }
    element := c.lru.PushFront(entry)
    groupEntities[id] = element

    // Evict if necessary
    if c.lru.Len() > c.maxSize {
        c.evict()
    }
}

func (c *lruGroupedCache[T]) evict() {
    element := c.lru.Back()
    if element == nil {
        return
    }

    c.lru.Remove(element)
    entry := element.Value.(*lruEntry[T])

    if groupEntities, ok := c.cache[entry.groupID]; ok {
        delete(groupEntities, entry.id)
        if len(groupEntities) == 0 {
            delete(c.cache, entry.groupID)
        }
    }
}

func (c *lruGroupedCache[T]) Remove(groupID snowflake.ID, id snowflake.ID) (T, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()

    groupEntities, ok := c.cache[groupID]
    if !ok {
        var zero T
        return zero, false
    }

    element, ok := groupEntities[id]
    if !ok {
        var zero T
        return zero, false
    }

    c.lru.Remove(element)
    delete(groupEntities, id)

    if len(groupEntities) == 0 {
        delete(c.cache, groupID)
    }

    entry := element.Value.(*lruEntry[T])
    return entry.value, true
}

func (c *lruGroupedCache[T]) GroupRemove(groupID snowflake.ID) {
    c.mu.Lock()
    defer c.mu.Unlock()

    groupEntities, ok := c.cache[groupID]
    if !ok {
        return
    }

    for _, element := range groupEntities {
        c.lru.Remove(element)
    }

    delete(c.cache, groupID)
}

func (c *lruGroupedCache[T]) RemoveIf(filterFunc cache.GroupedFilterFunc[T]) {
    c.mu.Lock()
    defer c.mu.Unlock()

    for groupID, groupEntities := range c.cache {
        for id, element := range groupEntities {
            entry := element.Value.(*lruEntry[T])
            if filterFunc(groupID, entry.value) {
                c.lru.Remove(element)
                delete(groupEntities, id)
            }
        }
        if len(groupEntities) == 0 {
            delete(c.cache, groupID)
        }
    }
}

func (c *lruGroupedCache[T]) GroupRemoveIf(groupID snowflake.ID, filterFunc cache.GroupedFilterFunc[T]) {
    c.mu.Lock()
    defer c.mu.Unlock()

    groupEntities, ok := c.cache[groupID]
    if !ok {
        return
    }

    for id, element := range groupEntities {
        entry := element.Value.(*lruEntry[T])
        if filterFunc(groupID, entry.value) {
            c.lru.Remove(element)
            delete(groupEntities, id)
        }
    }

    if len(groupEntities) == 0 {
        delete(c.cache, groupID)
    }
}

func (c *lruGroupedCache[T]) Len() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.lru.Len()
}

func (c *lruGroupedCache[T]) GroupLen(groupID snowflake.ID) int {
    c.mu.RLock()
    defer c.mu.RUnlock()

    groupEntities, ok := c.cache[groupID]
    if !ok {
        return 0
    }
    return len(groupEntities)
}

func (c *lruGroupedCache[T]) All() iter.Seq2[snowflake.ID, T] {
    return func(yield func(snowflake.ID, T) bool) {
        c.mu.RLock()
        defer c.mu.RUnlock()

        for groupID, groupEntities := range c.cache {
            for _, element := range groupEntities {
                entry := element.Value.(*lruEntry[T])
                if !yield(groupID, entry.value) {
                    return
                }
            }
        }
    }
}

func (c *lruGroupedCache[T]) GroupAll(groupID snowflake.ID) iter.Seq[T] {
    return func(yield func(T) bool) {
        c.mu.RLock()
        defer c.mu.RUnlock()

        groupEntities, ok := c.cache[groupID]
        if !ok {
            return
        }

        for _, element := range groupEntities {
            entry := element.Value.(*lruEntry[T])
            if !yield(entry.value) {
                return
            }
        }
    }
}

Using custom cache implementations

Register your custom cache when creating the bot:
import (
    "github.com/disgoorg/disgo"
    "github.com/disgoorg/disgo/bot"
    "github.com/disgoorg/disgo/cache"
)

client, err := disgo.New(token,
    bot.WithCacheConfigOpts(
        cache.WithCaches(cache.FlagMembers),
        cache.WithMemberCache(
            cache.NewMemberCache(
                newLRUGroupedCache[discord.Member](10000),
            ),
        ),
    ),
)

Cache policies

Policies control which entities to cache based on their properties.

Built-in policies

import "github.com/disgoorg/disgo/cache"

// Cache everything
cache.PolicyAll[discord.Member]

// Cache nothing
cache.PolicyNone[discord.Member]

// Cache members in specific guilds
cache.PolicyMembersInclude(guildID1, guildID2)

// Cache only pending members
cache.PolicyMembersPending

// Cache members in voice channels
cache.PolicyMembersInVoice(caches)

// Cache specific channel types
cache.PolicyChannelInclude(
    discord.ChannelTypeGuildText,
    discord.ChannelTypeGuildVoice,
)

Custom policies

Create custom policies as simple filter functions:
// Cache only boosting members
boosterPolicy := func(member discord.Member) bool {
    return member.PremiumSince != nil
}

// Cache members with specific roles
rolePolicy := func(member discord.Member) bool {
    targetRole := snowflake.MustParse("123456789")
    for _, roleID := range member.RoleIDs {
        if roleID == targetRole {
            return true
        }
    }
    return false
}

// Use the policy
client, err := disgo.New(token,
    bot.WithCacheConfigOpts(
        cache.WithMemberCachePolicy(boosterPolicy),
    ),
)

Combining policies

Policies can be combined with And, Or, or helper functions:
// Cache members who are boosting OR in voice
combinedPolicy := cache.AnyPolicy(
    boosterPolicy,
    cache.PolicyMembersInVoice(caches),
)

// Cache members who are boosting AND in specific guilds
strictPolicy := cache.AllPolicies(
    boosterPolicy,
    cache.PolicyMembersInclude(guildID),
)

// Using methods
combinedPolicy := boosterPolicy.Or(voicePolicy)
strictPolicy := boosterPolicy.And(guildPolicy)

Cache flags

Flags control which entity types to cache:
import "github.com/disgoorg/disgo/cache"

client, err := disgo.New(token,
    bot.WithCacheConfigOpts(
        cache.WithCaches(
            cache.FlagGuilds,
            cache.FlagChannels,
            cache.FlagMembers,
            cache.FlagMessages,
            cache.FlagRoles,
        ),
    ),
)
Available flags:
  • FlagGuilds - Guild objects
  • FlagChannels - Channel objects
  • FlagRoles - Role objects
  • FlagMembers - Member objects
  • FlagMessages - Message objects
  • FlagEmojis - Emoji objects
  • FlagStickers - Sticker objects
  • FlagVoiceStates - Voice state objects
  • FlagPresences - Presence objects
  • FlagStageInstances - Stage instance objects
  • FlagGuildScheduledEvents - Scheduled event objects
  • FlagThreadMembers - Thread member objects

Redis cache example

Here’s an example using Redis as a cache backend:
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "iter"
    "time"

    "github.com/disgoorg/disgo/cache"
    "github.com/disgoorg/snowflake/v2"
    "github.com/redis/go-redis/v9"
)

type redisGroupedCache[T any] struct {
    client *redis.Client
    prefix string
    ttl    time.Duration
}

func newRedisGroupedCache[T any](client *redis.Client, prefix string, ttl time.Duration) cache.GroupedCache[T] {
    return &redisGroupedCache[T]{
        client: client,
        prefix: prefix,
        ttl:    ttl,
    }
}

func (c *redisGroupedCache[T]) key(groupID, id snowflake.ID) string {
    return fmt.Sprintf("%s:%s:%s", c.prefix, groupID, id)
}

func (c *redisGroupedCache[T]) Get(groupID, id snowflake.ID) (T, bool) {
    var entity T
    ctx := context.Background()

    data, err := c.client.Get(ctx, c.key(groupID, id)).Bytes()
    if err != nil {
        return entity, false
    }

    if err := json.Unmarshal(data, &entity); err != nil {
        return entity, false
    }

    return entity, true
}

func (c *redisGroupedCache[T]) Put(groupID, id snowflake.ID, entity T) {
    ctx := context.Background()

    data, err := json.Marshal(entity)
    if err != nil {
        return
    }

    c.client.Set(ctx, c.key(groupID, id), data, c.ttl)
}

// Implement other methods...
External cache backends like Redis add latency. Consider using them only for specific entity types or as a secondary cache layer.

Best practices

Only cache what you need. Unnecessary caching wastes memory:
// Good: Cache only what you use
cache.WithCaches(
    cache.FlagGuilds,
    cache.FlagChannels,
    cache.FlagRoles,
)

// Bad: Cache everything
cache.WithCaches(
    cache.FlagGuilds |
    cache.FlagChannels |
    cache.FlagMembers |
    cache.FlagMessages |
    // ... all flags
)
For large bots, use policies to limit member caching:
cache.WithMemberCachePolicy(
    cache.PolicyMembersInVoice(caches),
)
Always use mutexes in custom cache implementations:
type myCache struct {
    mu    sync.RWMutex
    data  map[snowflake.ID]entity
}

func (c *myCache) Get(id snowflake.ID) (entity, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    // ...
}
Implement eviction strategies for large caches to prevent memory issues:
// LRU eviction
// TTL-based eviction
// Size-based limits

Build docs developers (and LLMs) love