General concepts#
LVSM consists of two main components:
- The main service, which handles tasks for creating collections and automatically synchronizes them with LUNA PLATFORM to keep data always up-to-date.
- The search plugin, which performs searches across collections and is integrated into LUNA PLATFORM via the Python Matcher Proxy plugin system.
The module requires the following LUNA PLATFORM services:
-
LUNA Configurator — For managing system configuration.
-
LUNA Events — As a source of event data for creating and synchronizing vector collections.
-
LUNA Faces — For access to face descriptors used in searches.
-
LUNA Python Matcher Proxy — For integrating the search plugin.
Collections#
Collections are the foundation for organizing vector search. A collection represents a set of entities (events, faces, etc.) grouped by common filters. Each record in a collection contains:
- Entity identifier (e.g.,
event_id). - Descriptors — feature vectors extracted by a neural network from media containing a human face, body, or scene.
- Additional data (payload) in JSON format containing additional supported LUNA PLATFORM targets that the user wants in the search results.
Currently, the module only supports entities of type 'event'.
Descriptors are stored in uint8 format, which saves memory and speeds up searches. LVSM supports storing multiple descriptors in a single record if each descriptor has a unique type or version. For example, a collection can contain face and body descriptors for a single entity.
The parameters for storing multiple descriptors are set when creating a collection via a POST request /collections in the descriptor configuration:
...
"descriptors": [
{
"descriptor_type": "face",
"descriptor_version": 65
},
{
"descriptor_type": "body",
"descriptor_version": 105
}
],
...
Collection metadata#
In addition to collections, the database also stores metadata for all created collections. This metadata is used by the search plugin to determine which collection can be used for the current query. It includes:
collection_name- Collection name.parameters- Collection parameters defined by the user in the POST request/collections.max_link_key- Maximum identifier of added entities (technical info on synchronization with the source database, for reference only).max_unlink_key- Maximum identifier of deleted entities (technical info on synchronization with the source database, for reference only).sync_time- Last synchronization timestamp of the collection.
Example metadata structure:
{
"collection_name": "my-shiny-collection",
"parameters": {
"object_type": "events",
"descriptors": [{"descriptor_type": "face", "descriptor_version": 65}],
"payload": ["event_id", "handler_id"],
"conditions": {"account_id": "00000000-0000-4000-a000-000000311001"}
},
"max_link_key": 123456789,
"max_unlink_key": 123,
"sync_time": "2025-09-15T00:00:00Z"
}
Collection synchronization process#
After initial population, a collection begins synchronizing at a configured interval (default 1 second).
The synchronization cycle for each collection consists of the following steps:
1․ Retrieve entities from the source database according to the collection parameters for the period since the last synchronization and add them to the collection.
2․ Detect entity IDs removed from the source database since the last synchronization and remove the corresponding items from the collection.
3․ Update collection metadata based on the entity IDs and last synchronization time.
Synchronization process features
If a synchronization task fails (e.g., due to busy workers), it is queued and retried later. Worker limit: by default — 100 workers, so only a limited number of collections can synchronize simultaneously. Synchronization resets between failures and resumes from the previous state.
Information about collection synchronization can be found in service logs. Collection synchronization status (last synchronization timestamp) is also available via GET request /collections.
To properly synchronize deleted events, the "EVENT_DELETION_LOG" parameter must be enabled in the LUNA Events service.
Qdrant database#
Qdrant is a vector database that provides fast and efficient vector search. In the module, it is used to store and process collections, ensuring high performance and flexibility.
Key features of the Qdrant vector database:
- Built-in sharding—a horizontal scaling mechanism that ensures even load distribution and fault tolerance by automatically distributing data across shards.
- GPU indexing—expands the applicability of vector search to tasks with large data volumes and high performance requirements.
- Advanced search filters (including time intervals and GPS coordinates) with post-filtering—the ability to apply additional filters to search results.
Vector storage#
All data in Qdrant is organized in collections, which are divided into segments. Each segment stores vectors, payload, and indexes. See Qdrant documentation.
Vector storage types:
-
In-memory — All vectors are stored in RAM, providing maximum search speed, with disk access only for persistence.
-
Memmap — Creates a virtual address space mapped to a disk file. With sufficient RAM, it performs nearly as fast as In-memory.
Payload storage types:
-
In-memory — All additional data is kept in RAM, offering high operation speed but requiring significant resources.
-
OnDisk — Data is stored on disk, preferred for large payloads. Indexes are recommended for fast filtering.
Indexes#
An index is a collection of descriptors deployed together for approximate matching. It is built as a dependency graph where nodes are descriptors.
Qdrant supports two types of indexes:
1․ Vector indexes
A vector index is a data structure built on vectors using a mathematical model to speed up vector searches. HNSW index is supported for approximate search. Index creation requires extra computation and may take time.
See Qdrant documentation for details.
2․ Payload indexes
Accelerate filtering by payload. These indexes are created for specific fields and data types (uuid, datetime, int, text, etc.).
See Qdrant documentation for details.
Example of creating indexes via PUT request /collections/{collection_name}/index:
{
"field_name": "handler_id",
"field_schema": "uuid"
}
{
"field_name": "create_time",
"field_schema": "datetime"
}
{
"field_name": "age",
"field_schema": "int"
}
{
"field_name": "upper_body.sleeve.length",
"field_schema": "text"
}
Collection management#
Qdrant allows managing collections, including dynamic updating of configuration parameters.
Collection parameters can be modified after creation via PATCH /collections/{collection_name}:
{
"vectors_config": {
"face_65": {
"on_disk": false
},
"body_105": {
"on_disk": false
}
},
"hnsw_config": {
"on_disk": false
},
"params": {
"on_disk_payload": false
}
}
Collection management, index status, and configuration can also be accessed via the UI.
Distributed deployment#
Qdrant supports horizontal scaling via sharding.
A collection can consist of one or more shards. Each shard is an independent storage capable of performing all collection operations.
Automatic sharding distributes records among shards using consistent hashing. This balances load and ensures fault tolerance — each node knows where data is stored and can request it from other nodes.
See distributed deployment for details.
Descriptor settings#
Descriptor settings define parameters for all registered descriptors in the system and are stored as an array of objects in a dedicated collection. Each object contains:
object_type— Type of object the descriptor applies to (e.g., faces, events).descriptor_type— Descriptor type (e.g., face, body).descriptor_version— Descriptor version.descriptor_length— Descriptor size in bytes.platt_scaling— Parameters (a, b) for normalizing match scores using Platt scaling.
Example descriptor configuration:
[
{
"object_type": "events",
"descriptor_type": "face",
"descriptor_version": 52,
"descriptor_length": 256,
"platt_scaling": {
"a": 0.5,
"b": 1.0
}
},
{
"object_type": "events",
"descriptor_type": "body",
"descriptor_version": 116,
"descriptor_length": 512,
"platt_scaling": {
"a": 0.7,
"b": 0.13
}
}
]
Use cases#
1․ Creating a collection — when creating a collection, the system automatically retrieves descriptor_length and platt_scaling based on object_type, descriptor_type, and descriptor_version. Users can override platt_scaling if necessary.
2․ Matching via plugin — during matching operations in LUNA PLATFORM, the plugin automatically retrieves the platt_scaling parameters associated with the collection. These parameters normalize similarity scores returned from the database for consistency.
Search plugin#
The search plugin integrates into the Python Matcher Proxy service and performs vector search on collections created in LUNA Vector Search Module.
In LUNA Configurator, add settings to connect to the vector database. Example configuration:
LUNA_VECTOR_SEARCH_PLUGIN = {
"DB": [{"type": "qdrant", "url": "http://127.0.0.1:6333", "token": "luna"}],
"SEARCH_PARAMETERS": {"exact": true, "ef_search": 3200},
"COLLECTION_OBSOLETE_PERIOD": null,
"REQUEST_TIMEOUT": 60
}
Important: Ensure the database is running with the parameters passed in LUNA_VECTOR_SEARCH_PLUGIN.DB.
After adding connection settings to Qdrant, you must restart the LUNA PLATFORM service if it is already running.
Add the plugin name to the list of active plugins:
LUNA_MATCHER_PROXY_ACTIVE_PLUGINS = ["vector_search_plugin"]
Note: The installation documentation provides specific steps to enable the plugin.
Plugin overview#
Once integrated, the plugin analyzes each search query and determines if it matches any existing collection. It checks collection metadata with the following criteria:
1․ Collection freshness
Collection is considered fresh if the time since the last synchronization is less than COLLECTION_OBSOLETE_PERIOD. If set to null, freshness check is skipped.
2․ Object type
Query object type must match the collection’s object type.
3․ Descriptor type and version
Descriptors in the query must match types and versions stored in the collection.
4․ Query filters
Query filters must be a subset of the collection’s conditions.
5․ Additional filters
If query filters are not fully covered by collection conditions, the plugin checks whether the necessary fields exist in the collection payload.
Plugin operation example#
For a collection with parameters:
{
"object_type": "events",
"descriptors": [{"descriptor_type": "face", "descriptor_version": 65}],
"payload": ["event_id", "handler_id"],
"conditions": {"account_id": "00000000-0000-4000-a000-000000311001"}
}
Matching requests:
{
"origin": "events",
"account_id": "00000000-0000-4000-a000-000000311001"
}
{
"origin": "events",
"account_id": "00000000-0000-4000-a000-000000311001",
"event_id__gte": "5f8d954f-e76f-4381-be1a-7f4052013c63",
"handler_ids": ["a9a98a13-94bf-4147-9b47-2041e0ee00b6"]
}
Non-matching requests:
{
"origin": "events",
"handler_ids": ["a9a98a13-94bf-4147-9b47-2041e0ee00b6"]
}
Reason: filters are not a subset of collection conditions.
{
"origin": "events",
"account_id": "00000000-0000-4000-a000-000000311001",
"handler_ids": ["a9a98a13-94bf-4147-9b47-2041e0ee00b6"],
"age__gte": 42
}
Reason: field age is not present in the collection payload.
Search sesult#
After search execution, thresholding, and limitations, the plugin returns results enriched with payload data to Python Matcher Proxy.
Limitations#
The search plugin does not support:
- Filtering by geographic location (
geo_position). like/nlikeoperators for text fields (e.g.,user_data, fields inmeta).
If these filter values differ from those in the collection, search cannot be performed.