dodb.cr/src/dodb/storage/common.cr

89 lines
2.7 KiB
Crystal

# Common database: only **recently added or requested** entries are kept in memory.
#
# Least recently used entries may be removed from the cache in order to keep the amount of memory used reasonable.
#
# The number of entries to keep in memory is **configurable**.
#
# This database is relevant for high demand applications;
# which means both a high number of entries (data cannot fit entirely in RAM),
# and a high number of requests, often to reach the same entries.
# Typically a retail website.
# In such applications, the "keep the most recently used data in cache" policy works since new users
# constantly ask for the same data over and over.
#
# ```
# # Creates a DODB database for common usage (a limited number of cached entries).
# car_database = DODB::Storage::Common.new "/path/to/db"
#
# # Creates a (cached) index.
# cars_by_name = car_database.new_index "name", &.name
#
# # Add a value in the database.
# car_database << Car.new "Corvet"
# ```
# On the file-system:
# ```plain
# storage
# ├── data
# │   └── 0000000000
# ├── indices
# │   └── by_name <- the "name" basic index
# │   └── Corvet -> ../../data/0000000000
# ```
#
# NOTE: fast for frequently requested data and requires a stable (and configurable) amount of memory.
class DODB::Storage::Common(V) < DODB::Storage::Cached(V)
# The *fifo* is an instance of `EfficientFIFO` where the key of the requested data is pushed.
# In case the number of stored entries exceeds what is allowed, the least recently used entry is removed.
property fifo : EfficientFIFO(Int32)
# Initializes the `DODB::Storage::Common` database with a maximum number of entries in the cache.
def initialize(@directory_name : String, max_entries : UInt32)
@fifo = EfficientFIFO(Int32).new max_entries
Dir.mkdir_p data_path
Dir.mkdir_p locks_directory
@cached_last_key = init_last_key
end
# Verifies that the value is in cache, or read it on disk.
# Pushes the key in the fifo.
def [](key : Int32) : V
val = @data[key]?
if val.nil?
raise MissingEntry.new(key) unless ::File.exists? file_path key
val = read file_path key
@data[key] = val
end
push_fifo key
val
end
# Assumes new entries are more requested than old ones.
def []=(key : Int32, value : V)
super key, value
push_fifo key
end
# :inherit:
#
# Assumes new entries are more requested than old ones.
def <<(item : V)
key = super item
push_fifo key
end
def unsafe_delete(key : Int32)
@fifo.delete key if super key
end
def delete(key : Int32)
@fifo.delete key if super key
end
private def push_fifo(key : Int32)
if entry_to_remove = @fifo << key
@data.delete entry_to_remove
end
end
end