Skip to content

Commit

Permalink
Merge pull request #595 from Th3-M4jor/add-message-cache
Browse files Browse the repository at this point in the history
Message cache
  • Loading branch information
jchristgit committed May 26, 2024
2 parents c0b17ec + f6bedde commit 1a695c0
Show file tree
Hide file tree
Showing 22 changed files with 1,421 additions and 35 deletions.
13 changes: 13 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,16 @@ config :nostrum,
config :logger, :console, metadata: [:shard, :guild, :channel]

if File.exists?("config/secret.exs"), do: import_config("secret.exs")

if Mix.env() == :test do
config :nostrum,
# constrain the size of the message cache in tests
# to make it easier to test eviction
# and to make sure we don't accidentally modify a production table
# when running tests, use a different table name
caches: [
messages:
{Nostrum.Cache.MessageCache.Noop,
size_limit: 10, eviction_count: 4, table_name: :nostrum_messages_test}
]
end
21 changes: 14 additions & 7 deletions guides/advanced/pluggable_caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ needs, but all of the caches can be exchanged for your own implementations. For
this, implement the behaviours exported by the cache modules under
`Nostrum.Cache`.

> ### Exception {: .info}
>
> The exception to the above is the `Nostrum.Cache.MessageCache`, which does not
> include an ETS-based implementation, and defaults to a NoOp cache. This is
> an intentional design decision because caching messages consumes a
> lot more memory than other objects, and is often not needed by most users.
Use the `[:nostrum, :caches]` configuration for configuring which cache
implementation you want to use. This can only be set at dependency compilation
time. A common situation is that you don't want to cache presences in your bot,
Expand Down Expand Up @@ -56,7 +63,7 @@ switch this can be added.

Mnesia-based caching assumes the user is familar with usage and
maintenance of Mnesia: the [Mnesia User's
Guide](https://www.erlang.org/doc/apps/mnesia/users_guide.html) is a good
Guide](https://www.erlang.org/doc/apps/mnesia/mnesia_chap1.html) is a good
starting point.


Expand All @@ -65,20 +72,20 @@ starting point.
The NoOp cache adapters are supplied for the case where you do not want to cache
specific data from Discord at all.

These cache adapters presently also don't send out any data they receive either:
this means that for caches using the NoOp cache adapters, you won't receive any
gateway events.


## Cache invalidation

nostrum does not invalidate cache in any special way: it will maintain it in
Nostrum does not invalidate most caches in any special way: it will maintain it in
response to gateway events (for instance by deleting a guild and its members
upon leaving it), but won't regularly prune caches or associate expiration times
with entries. For volatile (RAM-based) caches this is perfectly fine, however,
when implementing your own cache backend that persists to disk in some way, you
need to take care of this yourself.

The exception to this is the `Nostrum.Cache.MessageCache.Mnesia` module, which has a
default size limit of 10,000 and will automatically remove the 100 oldest
messages when this limit is reached as well as delete all cached messages for a
channel when the channel is deleted.


## Cache performance

Expand Down
37 changes: 37 additions & 0 deletions lib/nostrum/cache/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ defmodule Nostrum.Cache.Base do
@moduledoc since: "0.8.0"
@moduledoc false

@dialyzer {:nowarn_function, get_cache_module: 2, get_cache_options: 1}

@guild_cache_config Application.compile_env(:nostrum, [:caches, :guilds])
@channel_guild_mapping_config Application.compile_env(:nostrum, [
:caches,
:channel_guild_mapping
])
@member_cache_config Application.compile_env(:nostrum, [:caches, :members])
@message_cache_config Application.compile_env(:nostrum, [:caches, :messages])
@presence_cache_config Application.compile_env(:nostrum, [:caches, :presences])
@user_cache_config Application.compile_env(:nostrum, [:caches, :users])

@cache_map %{
guilds: @guild_cache_config,
channel_guild_mapping: @channel_guild_mapping_config,
members: @member_cache_config,
messages: @message_cache_config,
presences: @presence_cache_config,
users: @user_cache_config
}

def mnesia_note,
do: """
Please note that this module is only compiled if Mnesia is available on
Expand All @@ -11,4 +32,20 @@ defmodule Nostrum.Cache.Base do
To retrieve the table name used by this cache, use `table/0`.
"""

def get_cache_module(cache_name, default) do
case @cache_map do
%{^cache_name => nil} -> default
%{^cache_name => {module, _opts}} when is_atom(module) -> module
%{^cache_name => module} when is_atom(module) -> module
end
end

def get_cache_options(cache_name) do
case @cache_map do
%{^cache_name => nil} -> []
%{^cache_name => {_, opts}} -> opts
%{^cache_name => _} -> []
end
end
end
1 change: 1 addition & 0 deletions lib/nostrum/cache/cache_supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule Nostrum.Cache.CacheSupervisor do
Nostrum.Cache.ChannelGuildMapping,
Nostrum.Cache.GuildCache,
Nostrum.Cache.MemberCache,
Nostrum.Cache.MessageCache,
Nostrum.Cache.UserCache,
Nostrum.Cache.PresenceCache
]
Expand Down
5 changes: 2 additions & 3 deletions lib/nostrum/cache/channel_guild_mapping.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ defmodule Nostrum.Cache.ChannelGuildMapping do
"""
@moduledoc since: "0.8.0"

@configured_cache :nostrum
|> Application.compile_env(
[:caches, :channel_guild_mapping],
@configured_cache Nostrum.Cache.Base.get_cache_module(
:channel_guild_mapping,
@default_implementation
)

Expand Down
5 changes: 2 additions & 3 deletions lib/nostrum/cache/guild_cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule Nostrum.Cache.GuildCache do
- the `c:child_spec/1` callback for starting the cache under a supervisor.
You need to implement all of them for nostrum to work with your custom
cache.
cache.
The "upstream data" wording in this module references the fact that the
data that the guild cache (and other caches) retrieves represents the raw
Expand All @@ -45,8 +45,7 @@ defmodule Nostrum.Cache.GuildCache do
alias Nostrum.Struct.Sticker
alias Nostrum.Util

@configured_cache :nostrum
|> Application.compile_env([:caches, :guilds], @default_cache_implementation)
@configured_cache Nostrum.Cache.Base.get_cache_module(:guilds, @default_cache_implementation)

## Supervisor callbacks
# These set up the backing cache.
Expand Down
3 changes: 1 addition & 2 deletions lib/nostrum/cache/member_cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ defmodule Nostrum.Cache.MemberCache do
alias Nostrum.Struct.Guild.Member
alias Nostrum.Struct.User

@configured_cache :nostrum
|> Application.compile_env([:caches, :members], @default_cache_implementation)
@configured_cache Nostrum.Cache.Base.get_cache_module(:members, @default_cache_implementation)

@doc """
Add the member for the given guild from upstream data.
Expand Down
Loading

0 comments on commit 1a695c0

Please sign in to comment.