LMDB Backend¶
LMDB Backend utilizes python lmdb package wrapper for LMDB data store.
This backend provides following characteristics:
storage of all event structures
querying by all query data parameters
optimization for latest events querying
optimization for time series querying
fragmentation of time series entries based on event types
in memory caching of event registration with periodic disk persistence
configurable condition rules for filtering event by their content
Storage models¶
Storage of events in LMDB Backend is based on models:
key-value storage
time series storage
event id based reference storage
Key-value storage¶
Key-value storage is used as storage model for persisting latest events for each event type. Configuration provides parameter defining which events, based on their event type, are stored in key-value store. During backend initialization, content of whole key-value storage is loaded in memory and continuously updated with new event registrations. This enables quick access to event data without need for disk read operations during query executions.
Time series storage¶
Time series storage provide storage model where all events are orderly stored based on their event timestamp or event source timestamp. Configuration enables definition of multiple sets defined by event types and time source. For each configured set, single series storage is created. This fragmentation of stored events into independent time series enables fine tuning and optimizations based on expected query types. For general cases, usage of single set of configured event types, which includes all event that should be persisted, is suggested. Events without source timestamp can’t be stored in storage configured for source timestamp storage. Querying of events is based on sequential checking of query conditions which occurred in time segments constrained by query data.
event id based reference storage¶
In addition to key-value and time series event storage, additional event references are stored in dedicated event id based reference storage. These references are kept only for events available in key-value or time series storage. Once event is removed (e.g. due to configured limits) and not accessible in other storages, its reference is also removed from reference storage. This storage enables quick and sequential access to events based on their event id.
Event filtering¶
During registration and event querying, this backend discards all event occurrences which do not satisfy configured conditions. Conditions are defined and applied based on event type. These conditions provide plausibility of condition composition (by operators all and any) and testing based on event content.
Query planning¶
During event querying, simple query planning is done based on following steps:
if query data filters only latest events for each queried event type, then only key-value storage is searched for possible event instances
for all other queries, time series partition is searched, in order as they are defined in configuration, for first series that contains at least one possible event type constrained by query data - only that time series storage partition is used as source of event instances returned by query
if no time series storage could be found, empty list is returned as query result
In case of querying event sequences ordered by event id and based on starting event id, event id based reference storage is used (backends query_flushed method).
Warning
Query which utilizes time series search will retrieve only events from single partition. Thus, time series partitioning should be used in accordance with expected queries as additional optimization technique. It is strongly advised to configure only single timestamp and single source timestamp partition as initial configuration.
Disk persisting¶
Registration of new events doesn’t initiate immediate disk writes. All changes
are cached in memory and periodically written to disk as part of single
transaction which includes all storages. This period is defined by
configuration (flush_period
). Writing of memory cache is also part of
backend’s standard closing procedure. Single transaction responsible for
writing all memory caches to disk also include cleanup operation which enables
deletion of oldest entries in time series storages. Each time series storage
can have its own configuration defined limit which specifies event persistent
period based on event timestamp or event source timestamp, number of events in
partition or partitions approximated disk usage. event id based reference
storage is updated only during this periodic write transaction. Because of this,
queries utilizing event id reference storage will return only events that are
persisted on disk.
Limiting time series size¶
Each time series storage can optionally limit number of stored events. This functionality is associated with disk persisting procedure and is part of same database transaction. Supported limits include:
min_entries
Minimum number of events preserved in database despite of other limits. This property can be used as “low water mark” - number of entries always available.
max_entries
Maximum number of event preserver in database. This property can be used as “high water mark” - number of entries will never exceed this number.
duration
Time in seconds representing maximum duration between current time and time used as time series key. All entries which exceed this duration will be removed.
size
Size in bytes allocated for associated time series. Once time series exceeds this storage size, some of the oldest entries are removed. Number of removed entries is calculated based on average entry size and limiting storage size.
Configuration¶
$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "hat-event://backends/lmdb.yaml"
title: LMDB backend
type: object
required:
- db_path
- identifier
- flush_period
- cleanup_period
- conditions
- latest
- timeseries
properties:
db_path:
type: string
identifier:
type:
- "null"
- string
flush_period:
type: number
cleanup_period:
type: number
conditions:
type: array
items:
type: object
required:
- subscriptions
- condition
properties:
subscriptions:
$ref: "hat-event://backends/lmdb.yaml#/$defs/event_types"
condition:
$ref: "hat-event://backends/lmdb.yaml#/$defs/condition"
latest:
type: object
required:
- subscriptions
properties:
subscriptions:
$ref: "hat-event://backends/lmdb.yaml#/$defs/event_types"
timeseries:
type: array
items:
type: object
required:
- order_by
- subscriptions
properties:
order_by:
enum:
- TIMESTAMP
- SOURCE_TIMESTAMP
subscriptions:
$ref: "hat-event://backends/lmdb.yaml#/$defs/event_types"
limit:
$ref: "hat-event://backends/lmdb.yaml#/$defs/limit"
$defs:
event_types:
type: array
items:
type: array
items:
type: string
limit:
type: object
properties:
min_entries:
type: number
description: |
number of entries kept despite of other limits
max_entries:
type: number
description: |
maximum number of entries
duration:
type: number
description: |
limit for the persisted history based on keys
expressed as duration in seconds
size:
type: number
description: |
memory consumption size in bytes that triggers
additional cleanup based on average entry size
condition:
oneOf:
- $ref: "hat-event://backends/lmdb.yaml#/$defs/conditions/all"
- $ref: "hat-event://backends/lmdb.yaml#/$defs/conditions/any"
- $ref: "hat-event://backends/lmdb.yaml#/$defs/conditions/json"
conditions:
all:
type: object
required:
- type
- conditions
properties:
type:
const: all
conditions:
type: array
items:
$ref: "hat-event://backends/lmdb.yaml#/$defs/condition"
any:
type: object
required:
- type
- conditions
properties:
type:
const: any
conditions:
type: array
items:
$ref: "hat-event://backends/lmdb.yaml#/$defs/condition"
json:
type: object
required:
- type
properties:
type:
const: json
data_path:
$ref: "hat-event://backends/lmdb.yaml#/$defs/path"
data_type:
enum:
- "null"
- boolean
- string
- number
- array
- object
data_value: {}
path:
oneOf:
- type: string
- type: integer
- type: array
items:
$ref: "hat-event://backends/lmdb.yaml#/$defs/path"