UNPKG

@zilliz/milvus2-sdk-node

Version:

Official Node.js SDK for [Milvus](https://github.com/milvus-io/milvus) vector database. Provides gRPC and HTTP clients for vector similarity search, metadata filtering, and full collection/index/user management.

157 lines (156 loc) • 7.63 kB
/** * Client-side Split-Block Bloom Filter (SBBF) construction for the * `membership_match(field, {blob}, type=bloom)` filter expression. * * Building the filter on the client and shipping the compact blob lets large membership * sets pass the proxy gRPC receive limit, which a raw value list would exceed: 10M int64 * values are roughly 90 MB as an `in [...]` list but about 14 MB as a bloom filter. The * proxy embeds the blob verbatim after validating the envelope and never rebuilds the * filter, so these bytes must match what the other SDKs produce exactly. * * The bit layout is bit-identical to Arrow C++'s `parquet::BlockSplitBloomFilter`, and thus * to the parquet-format BloomFilter.md spec, wrapped in the Milvus MBF1 envelope. See * `docs/design-docs/design_docs/20260707-bloom-filter-expression.md` in the milvus repo. */ /** The 4-byte MBF1 envelope magic. */ export declare const BLOOM_FILTER_MAGIC = "MBF1"; /** The MBF1 envelope version implemented here. */ export declare const BLOOM_FILTER_VERSION = 1; /** Identifies the parquet SBBF + XXH64 algorithm. */ export declare const BLOOM_FILTER_ALGO_PARQUET_SBBF_XXH64 = 1; /** Size in bytes of the MBF1 envelope header. */ export declare const BLOOM_FILTER_HEADER_SIZE = 32; /** Size of one SBBF block (parquet-format spec). */ export declare const BLOOM_FILTER_BYTES_PER_BLOCK = 32; /** Marks a filter that recorded int64 values (8-byte little-endian hash domain). */ export declare const BLOOM_FILTER_DOMAIN_INT64 = 1; /** Marks a filter that recorded string values (raw UTF-8 hash domain). */ export declare const BLOOM_FILTER_DOMAIN_UTF8 = 2; /** * Minimum / maximum filter body size, mirroring Arrow's * BlockSplitBloomFilter::kMinimumBloomFilterBytes / kMaximumBloomFilterBytes. */ export declare const BLOOM_FILTER_MIN_BYTES = 32; export declare const BLOOM_FILTER_MAX_BYTES: number; /** Lowest / highest accepted false-positive rate. */ export declare const BLOOM_FILTER_MIN_FPR = 0.0001; export declare const BLOOM_FILTER_MAX_FPR = 0.05; /** * Recommended false-positive rate when a caller has no specific target. Sizing follows the * Arrow formula, so a body holds roughly 0.72 members per byte at this rate. Bodies are * powers of two, so a member count just past a tier boundary doubles the blob; raising fpr * is usually the cheaper fix. */ export declare const BLOOM_FILTER_DEFAULT_FPR = 0.005; /** * XXH64 with seed 0 over raw bytes — the hash the SBBF spec mandates. * * BigInt rather than a 32-bit-pair implementation: JavaScript numbers cannot represent a * 64-bit hash, and every intermediate here (multiply, rotate) genuinely needs the full * width. Build cost is dominated by this, so the int64 domain gets a specialised path below. */ export declare const xxh64: (data: Uint8Array) => bigint; /** * XXH64 over an int64's 8-byte little-endian encoding, without materialising those bytes. * * Specialisation of {@link xxh64} for the one input length the int64 domain ever produces: * the 32-byte stripe loop and the 4-byte / 1-byte tails are unreachable, leaving a single * 8-byte lane. `value & MASK64` is exactly the little-endian two's-complement encoding read * back as a u64, so the pack/unpack round trip drops out. */ export declare const xxh64Int64: (value: bigint) => bigint; /** * XXH64 (seed 0) over raw bytes via the 32-bit-pair path, returned as a bigint. * * This is what {@link BloomFilterBuilder.addString} actually runs; {@link xxh64} is the * BigInt reference kept to check it against. Exported so that check can be a test. */ export declare const xxh64Pairs: (data: Uint8Array) => bigint; /** * Returns the exact byte length {@link buildBloomFilter} would produce for `n` members at * the given false-positive rate, without hashing anything. * * Use it to check a planned filter against the proxy limits before building it: the body * must fit `proxy.maxBloomFilterSize` (64 MiB by default) and the whole request must fit * `proxy.grpc.serverMaxRecvSize` (128 MiB by default). */ export declare const estimateBloomFilterSize: (n: number, fpr: number) => number; /** * Incrementally constructs an SBBF and serializes it into an MBF1 envelope. * * Unlike {@link buildBloomFilter} a builder accepts both value domains, producing a filter * that matches integer and string members alike — useful for a JSON path that legitimately * holds both. */ export declare class BloomFilterBuilder { private readonly buf; private readonly view; private readonly numBlocks; /** 32 - log2(numBlocks), and numBlocks - 1: together they reduce a hash to a block index. */ private readonly blockShift; private readonly blockMask; private readonly nDeclared; private readonly fpr; private domains; /** * @param n expected number of distinct members; it only drives sizing, so inserting more * is allowed and simply raises the effective false-positive rate * @param fpr false-positive rate, in [BLOOM_FILTER_MIN_FPR, BLOOM_FILTER_MAX_FPR] */ constructor(n: number, fpr?: number); /** Inserts an integer value, hashed as its 8-byte little-endian encoding. */ addInt64(value: number | bigint): this; /** * Inserts a string value, hashed as its raw UTF-8 bytes. * * `Buffer.from` rather than `TextEncoder`: over 10M members the encode step alone is 0.41s * via Buffer against 1.48s for a hoisted TextEncoder and 1.81s for a per-call one. Buffer * is a Uint8Array subclass, and `xxh64` reads through the view's byteOffset, so the pooled * backing store Buffer hands out is handled correctly. */ addString(value: string): this; /** Returns the value domains inserted so far. Zero means nothing was inserted. */ getDomains(): number; /** Returns the number of 32-byte blocks in the filter body. */ getNumBlocks(): number; /** * Stamps the MBF1 header onto the filter and returns the envelope. * * The returned array is the builder's own buffer, not a copy, so a filter costs one * allocation of its final size rather than a body plus an equal-sized serialization * buffer. Treat it as read-only, and copy it if you keep inserting afterwards. */ build(): Uint8Array; /** * Sets this hash's eight bits directly in the final MBF1 buffer. * * The block index needs the full 64-bit product, so it stays in BigInt; everything after * is 32-bit, where `Math.imul` gives exactly the wrapping multiply the spec wants and * `>>> 27` the logical shift. */ private addHash; } /** * Builds an MBF1-wrapped Split-Block Bloom Filter over a membership set. * * Members must be homogeneous: either all integers (`number` or `bigint`) or all strings. * An empty list produces a valid blob that records no domain and therefore matches nothing. * * Build the blob from the same value domain as the target field: integer fields hash int64, * VARCHAR fields hash UTF-8. The envelope records which domains were inserted, so a * wrong-domain blob is rejected by the proxy with a parameter error rather than silently * matching nothing. * * ```ts * const blob = buildBloomFilter(userIds); * await client.query({ * collection_name: 'docs', * filter: 'membership_match(user_id, {bf}, type=bloom)', * exprValues: { bf: blob }, * }); * ``` * * @param members the membership set * @param fpr false-positive rate, in [BLOOM_FILTER_MIN_FPR, BLOOM_FILTER_MAX_FPR] */ export declare const buildBloomFilter: (members: Array<number | bigint | string>, fpr?: number) => Uint8Array;