View Source erlfdb_directory_cache (erlfdb v1.2.0)
An ETS-backed cache for erlfdb_directory nodes.
Opening a directory requires 1 + 2N sequential round trips to FDB for a
path of depth N. This cache eliminates that cost for repeated opens of the
same path by storing the resolved directory node in an ETS table after the
first lookup.
Usage
Create one cache at application startup and pass it alongside your
erlfdb_directory root wherever directories are opened:
% Cache is linked to calling process (ETS)
_ = erlfdb_directory_cache:new(),
Root = erlfdb_directory:root(),
Dir = erlfdb_directory_cache:create_or_open(Db, Root, [<<"customers">>, CustomerId]),
erlfdb_directory:pack(Dir, {some_field})Cache invalidation
Directory node names (and therefore subspace prefixes) are immutable once created - they do not change when data is written, across restarts, or across reconnects. Invalidation is only needed when a directory is explicitly moved or removed, and should be performed co-located with that operation:
erlfdb_directory:remove(Db, Root, Path),
erlfdb_directory_cache:invalidate(Root, Path)Cache invalidation can be safely executed within an erlfdb:transactional
callback because it is idempotent. ets:delete/2 takes effect immediately and
is not rolled back if the surrounding transaction retries - on each retry,
invalidation simply runs again, and deleting an already-absent key is a no-op.
A spurious eviction from a retried or aborted transaction carries no consistency
penalty: the cache is never a source of truth, only a read-through shortcut, so
any evicted entry is transparently re-populated from FDB on the next access.
Transaction semantics
Passing a Db handle to open/3,4 or create_or_open/3,4 is always safe:
the directory operation runs in its own self-contained FDB transaction, and the
cache is populated only after that transaction successfully commits.
Passing a Tx handle (inside an outer erlfdb:transactional callback) is safe
for open and for create_or_open when the directory already exists, because a
stable directory's node name does not change between retries. It is unsafe
for create_or_open when the directory does not yet exist: the High Contention
Allocator assigns a new node name on each transaction attempt, so if the outer
transaction retries, the cache will hold a node name from an attempt that was
never committed.
For that case, perform the directory operation directly and populate the cache
explicitly after the outer transaction commits using store/2,3:
Node = erlfdb:transactional(Db, fun(Tx) ->
Dir = erlfdb_directory:create_or_open(Tx, Root, Path),
%% ... other transactional work ...
Dir
end),
erlfdb_directory_cache:store(Root, Node)Concurrency
All operations are safe to call from multiple processes simultaneously. Cache
misses use ets:insert_new/2 so that only the first writer's result is kept;
concurrent openers for the same path discard their result and return the
winning entry.
Summary
Functions
Opens or creates a directory at Path under Root, using the default cache
table. Equivalent to create_or_open(erlfdb_directory_cache, TxObj, Root, Path).
Opens an existing directory at Path under Root, or creates it if absent,
returning the cached node if already known.
Removes the entry for Path under Root from the default cache table.
Equivalent to invalidate(erlfdb_directory_cache, Root, Path).
Removes the entry for Path under Root from Table so that the next
open/4 or create_or_open/4 call re-resolves the directory from FDB.
Creates a new ETS-backed directory cache using erlfdb_directory_cache as
the table name. Equivalent to new(erlfdb_directory_cache, []).
Creates a new ETS-backed directory cache registered publicly with the given name.
Equivalent to new(Name, []).
Creates a new ETS-backed directory cache registered publicly with the given name.
Opens a directory at Path under Root, using the default cache table.
Equivalent to open(erlfdb_directory_cache, TxObj, Root, Path).
Opens an existing directory at Path under Root, returning the cached node
if already known.
Deletes all entries from the default cache table.
Equivalent to purge(erlfdb_directory_cache, -1).
Deletes all entries from Table.
Equivalent to purge(Table, -1).
Deletes entries from Table that were cached more than Ttl milliseconds ago.
Removes the directory at Path under Root from FDB and invalidates its
cache entry in the default table.
Equivalent to remove(erlfdb_directory_cache, TxObj, Root, Path).
Removes the directory at Path under Root from FDB and invalidates its
cache entry in Table.
Removes the directory at Path under Root from FDB (if it exists) and
invalidates its cache entry in the default table.
Equivalent to remove_if_exists(erlfdb_directory_cache, TxObj, Root, Path).
Removes the directory at Path under Root from FDB if it exists, and
invalidates its cache entry in Table. Unlike remove/4, this does not
raise an error when the directory is absent.
Stores Node in the default cache table under Root.
Equivalent to store(erlfdb_directory_cache, Root, Node).
Stores a directory Node directly in Table, bypassing the FDB lookup.
Functions
-spec create_or_open(erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> erlfdb_directory:t().
Opens or creates a directory at Path under Root, using the default cache
table. Equivalent to create_or_open(erlfdb_directory_cache, TxObj, Root, Path).
-spec create_or_open(ets:table(), erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> erlfdb_directory:t().
Opens an existing directory at Path under Root, or creates it if absent,
returning the cached node if already known.
On a cache miss the directory is resolved (and created if necessary) via
erlfdb_directory:create_or_open/3 and the result is stored in Table.
-spec invalidate(erlfdb_directory:t(), erlfdb_directory:path()) -> true.
Removes the entry for Path under Root from the default cache table.
Equivalent to invalidate(erlfdb_directory_cache, Root, Path).
-spec invalidate(ets:table(), erlfdb_directory:t(), erlfdb_directory:path()) -> true.
Removes the entry for Path under Root from Table so that the next
open/4 or create_or_open/4 call re-resolves the directory from FDB.
Call this co-located with any erlfdb_directory:move/4 or
erlfdb_directory:remove/3 on the same path.
-spec new() -> ets:tid().
Creates a new ETS-backed directory cache using erlfdb_directory_cache as
the table name. Equivalent to new(erlfdb_directory_cache, []).
Creates a new ETS-backed directory cache registered publicly with the given name.
Equivalent to new(Name, []).
Creates a new ETS-backed directory cache registered publicly with the given name.
The table is created as a named, public set with read_concurrency enabled so
that concurrent lookups are cheap. The returned value is the ETS table
identifier and can be passed to open/4, create_or_open/4, store/3, and
invalidate/3.
-spec open(erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> erlfdb_directory:t().
Opens a directory at Path under Root, using the default cache table.
Equivalent to open(erlfdb_directory_cache, TxObj, Root, Path).
-spec open(ets:table(), erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> erlfdb_directory:t().
Opens an existing directory at Path under Root, returning the cached node
if already known.
On a cache miss the directory is resolved from FDB via erlfdb_directory:open/3
and the result is stored in Table. Raises {erlfdb_directory, {open_error, path_missing, Path}} if the directory does not exist; errors are not cached.
-spec purge() -> non_neg_integer().
Deletes all entries from the default cache table.
Equivalent to purge(erlfdb_directory_cache, -1).
-spec purge(ets:table()) -> non_neg_integer().
Deletes all entries from Table.
Equivalent to purge(Table, -1).
-spec purge(ets:table(), integer()) -> non_neg_integer().
Deletes entries from Table that were cached more than Ttl milliseconds ago.
A Ttl of -1 matches all entries regardless of age, making purge(Table, -1)
equivalent to clearing the entire table.
Periodic purging can be scheduled with timer:apply_interval(Ttl, erlfdb_directory_cache, purge, [Table, Ttl]).
-spec remove(erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> ok.
Removes the directory at Path under Root from FDB and invalidates its
cache entry in the default table.
Equivalent to remove(erlfdb_directory_cache, TxObj, Root, Path).
-spec remove(ets:table(), erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> ok.
Removes the directory at Path under Root from FDB and invalidates its
cache entry in Table.
The cache entry is invalidated before the FDB operation so that a concurrent reader that races the removal sees a miss rather than a stale entry. The invalidation is idempotent — if the surrounding transaction retries, the delete simply runs again with no ill effect.
-spec remove_if_exists(erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> ok.
Removes the directory at Path under Root from FDB (if it exists) and
invalidates its cache entry in the default table.
Equivalent to remove_if_exists(erlfdb_directory_cache, TxObj, Root, Path).
-spec remove_if_exists(ets:table(), erlfdb:tx_object(), erlfdb_directory:t(), erlfdb_directory:path()) -> ok.
Removes the directory at Path under Root from FDB if it exists, and
invalidates its cache entry in Table. Unlike remove/4, this does not
raise an error when the directory is absent.
-spec store(erlfdb_directory:t(), erlfdb_directory:t()) -> true.
Stores Node in the default cache table under Root.
Equivalent to store(erlfdb_directory_cache, Root, Node).
-spec store(ets:table(), erlfdb_directory:t(), erlfdb_directory:t()) -> true.
Stores a directory Node directly in Table, bypassing the FDB lookup.
Use this when a directory has been opened or created inside an outer
erlfdb:transactional callback that may retry — call store/2,3 with the
committed node after the transaction returns, rather than letting
create_or_open/3,4 populate the cache mid-transaction. See the
Transaction semantics section in the module documentation.
Unlike the cache miss path in open/4 and create_or_open/4, this function
uses ets:insert/2 rather than ets:insert_new/2, so it overwrites any
previously cached entry for the same path.