harperdb
Version:
HarperDB is a distributed database, caching service, streaming broker, and application development platform focused on performance and ease of use.
88 lines (87 loc) • 3.95 kB
TypeScript
import type { Id } from '../../resources/ResourceInterface.ts';
/**
* Represents a Hierarchical Navigable Small World (HNSW) index for approximate nearest neighbor search.
* This implementation is based on hierarchical graph navigation to efficiently index and search high-dimensional vectors.
* A HNSW is basically a multi-dimensional skip list. Each node has (potentially) higher levels that are used for quickly
* traversing the graph get in the neighborhood of the node, and then lower levels are used to more accurately find the
* closest neighbors.
*
* This implementation is based on the paper "Efficient and Robust Approximate Nearest Neighbor Search in High Dimensions"
* (mostly influenced AI's contributions)
*/
export declare class HierarchicalNavigableSmallWorld {
static useObjectStore: boolean;
indexStore: any;
M: number;
efConstruction: number;
efConstructionSearch: number;
mL: number;
optimizeRouting: number;
nodesVisitedCount: number;
idIncrementer: BigInt64Array | undefined;
distance: (a: number[], b: number[]) => number;
constructor(indexStore: any, options: any);
index(primaryKey: Id, vector: number[], existingVector?: number[]): void;
private getEntryPoint;
/**
* Search one layer of the skip-list using HNSW algorithm for creating a candidate list and navigating the graph
* TODO: This should be async, but we can't really do that with lmdb-js's transaction system right now. Should be
* doable with RocksDB. We could also create an async version for searching.
* @param queryVector
* @param entryPointId
* @param entryPoint
* @param ef
* @param level
* @param distanceFunction
* @private
*/
private searchLayer;
/**
* This the main entry from Harper's query functionality, where we actually search for an ordered list of nearest
* neighbors, using the provided sort/order definition object and performing the multi-layer skip-list search.
* This returns an iterable of the nearest neighbors to the provided target vector, with nearest ordered first.
* @param target
* @param value
* @param descending
* @param distance
* @param comparator
* @param context
*/
search({ target, value, descending, distance, comparator, }: {
target: number[];
value: number;
descending: boolean;
distance: string;
comparator: string;
}): {
key: string;
distance: number;
}[];
private checkSymmetry;
private addConnection;
validateConnectivity(startLevel?: number): {
isFullyConnected: boolean;
averageConnections: number;
};
get totalNodes(): number;
/**
* This is used by the query planner to determine what order to apply conditions. It is our best guess at an estimated count.
* This unit is typically the number of records that need to be accessed to satisfy the query. We know that we will visit
* a minimum of efConstructionSearch nodes and a maximum of the total nodes (in absolute worst case).
* The original paper described the complexity as polylogarithmic. From my testing, the
* best and simplest guess at the number of nodes that need to be accessed is the geometric mean of the total number of nodes
* and the efConstruction parameter (for search), which clearly constrains the estimate to the correct range and is
* similar to polylogarithmic for realistic values.
*
* @returns
*/
estimateCountAsSort(): number;
/**
* This is used to resolve the vector property, which should be resolved to the distance when used in a sort comparator
* We also want to cache distance calculations so they can be accessed efficently later
* @param vector
* @param context
* @param entry
*/
propertyResolver(vector: number[], context: any, entry: any): any;
}