Vector Collection

The primary object for interacting with vector storage is a Vector Collection. A Vector Collection holds information about the vectors and associated metadata (user values).

For further information on the architecture and considerations for the Beta version of the Vector Collection data structure, see VectorCollection data structure design.

A collection consists of one or more indexes, all sharing a common metadata storage. Each index represents a distinct vector space. Vectors from different indexes are stored independently, and indexes can have different configurations.

Conceptually, a vector collection resembles a key-value store. Here, the key is a user-defined unique identifier, and the value is an object containing metadata alongside several vectors — one for each index.

Users can store any value they require as metadata. This could be additional characteristics of the source data or even the source data itself.

JVM configuration

Vector collection indexing and search are computationally very intensive and can utilize SIMD instructions. For best performance using vector collections, use Java 21 and enable Vector API from Project Panama by adding --add-modules jdk.incubator.vector to JVM parameters.

If you use Docker, you can use the following example command:

docker run -p 5701:5701 -e HZ_LICENSEKEY=<your_license_key> \
       -e "JAVA_OPTS=--add-modules jdk.incubator.vector -Dhazelcast.partition.count=16" \
       hazelcast/hazelcast-enterprise:latest-snapshot-slim-jdk21

Configuration

Collection configuration can be set dynamically during vector collection creation or statically during cluster configuration. Unlike other data structures, the configuration must be set up before the collection can be used. There is no default configuration for the vector collection. If no matching configuration is found for the specified vector collection, the system raises an error.

The configuration supports wildcards. To retrieve a vector collection, the system can search for an exact match of the specified collection name in the configuration, or use a wildcard match from the existing configurations.

The following tables list all available options:

Table 1. Collection configuration options
Option Description Required Default

name

The name of the vector collection. Can include letters, numbers, and the symbols -, _, *.

Required

NULL

indexes

Information about indexes configuration

Required

NULL

Table 2. Index configuration options
Option Description Required Default

name

The name of the vector index. Can include letters, numbers, and the symbols - and _.

Required for single-index vector collections. Optional for multi-index collection

NULL

dimension

Vectors dimension

Required

N/A

metric

Used to calculate the distance between two vectors. For further information on distance metrics, see the Available distance metrics table.

Required

N/A

max-degree

Used to calculate the maximum number of neighbors per node. The calculation used is max-degree * 2

Required

16

ef-construction

The size of the search queue to use when finding nearest neighbors.

Required

100

use-deduplication

Whether or not to use vector deduplication. When disabled, each added vector is treated as a distinct vector in the index, even if it is identical to an existing one. When enabled, the index consumes less space as duplicates share a vector, but the time required to add a vector increases.

Required

TRUE

Table 3. Available distance metrics
Name Description Score definition

EUCLIDEAN

Euclidean distance

1 / (1 + squareDistance(v1, v2))

COSINE

Cosine of the angle between the vectors

(1 + cos(v1, v2)) / 2

DOT

Dot product of the vectors

(1 + dotProduct(v1, v2)) / 2

The recommended method for computing cosine similarity is to normalize all vectors to unit length and use the DOT metric instead.

Configuration example:

  • XML

  • YAML

  • Java

  • Python

<hazelcast>
    <vector-collection name="books">
        <indexes>
            <index name="word2vec-index">
                <dimension>6</dimension>
                <metric>DOT</metric>
            </index>
            <index name="glove-index">
                <dimension>10</dimension>
                <metric>DOT</metric>
                <max-degree>32</max-degree>
                <ef-construction>256</ef-construction>
                <use-deduplication>false</use-deduplication>
            </index>
        </indexes>
    </vector-collection>
</hazelcast>
hazelcast:
  vector-collection:
    vector-collection-name:
      indexes:
        - name: word2vec-index
          dimension: 6
          metric: DOT
        - name: glove-index
          dimension: 10
          metric: DOT
          max-degree: 32
          ef-construction: 256
          use-deduplication: false
Config config = new Config();
VectorCollectionConfig collectionConfig = new VectorCollectionConfig("books")
    .addVectorIndexConfig(
            new VectorIndexConfig()
                .setName("word2vec-index")
                .setDimension(6)
                .setMetric(Metric.DOT)
    ).addVectorIndexConfig(
            new VectorIndexConfig()
                .setName("glove-index")
                .setDimension(10)
                .setMetric(Metric.DOT)
                .setMaxDegree(32)
                .setEfConstruction(256)
                .setUseDeduplication(false)
    );
config.addVectorCollectionConfig(collectionConfig);
client.create_vector_collection_config("books", indexes=[
    IndexConfig(name="word2vec-index", metric=Metric.DOT, dimension=6),
    IndexConfig(name="glove-index", metric=Metric.DOT, dimension=10,
                max_degree=32, ef_construction=256, use_deduplication=False),
])

Create collection

You can use either of the VectorCollection static methods to get the vector collection. Both methods either create a vector collection, or return an existing one that corresponds to the requested name. The methods are as follows:

  • getCollection(HazelcastInstance instance, VectorCollectionConfig collectionConfig)

    • If a collection with the provided name does not exist, a new collection is created with the given configuration. If the configuration for the collection already exists, the provided configuration must match the existing configuration; if the configuration does not match, an error is thrown.

    • If a collection with the same name and configuration already exists, it is returned.

    • If a collection with the same name but a different configuration exists, an error is thrown.

  • Java

  • Python

VectorCollectionConfig collectionConfig = new VectorCollectionConfig("books")
    .addVectorIndexConfig(
            new VectorIndexConfig()
                .setDimension(6)
                .setMetric(Metric.DOT)
    );
VectorCollection vectorCollection = VectorCollection.getCollection(hazelcastInstance, vectorCollectionConfig);
# create configuration and get collection separately
client.create_vector_collection_config("books", indexes=[
    IndexConfig(name=None, metric=Metric.DOT, dimension=6)
])
vectorCollection = client.get_vector_collection("books").blocking()
  • getCollection(HazelcastInstance instance, String collectionName).

    • If a collection with the provided name does not exist, the system creates the collection with the configuration created explicitly during static or dynamic configuration of the cluster. If the configuration does not exist, an error is thrown.

    • If a collection with the provided name exists, it is returned.

  • Java

  • Python

VectorCollection vectorCollection = VectorCollection.getCollection(hazelcastInstance, "books");
vectorCollection = client.get_vector_collection("books").blocking()
The Java Vector Collection API is only asynchronous, Python provides both asynchronous and synchronous APIs (using blocking())

Manage data

All methods of VectorCollection that work with collection data are asynchronous. The result is returned as a CompletionStage. A collection interacts with entries in the form of documents (VectorDocument). Each document comprises a value and one or more vectors associated with that value.

When using the asynchronous methods, clients must carefully control the number of requests and their concurrency. A large number of requests can potentially overwhelm both the server and the client by consuming significant heap memory during processing.

Create document

To create a document, use the static factory methods of the VectorDocument and VectorValues classes.

Example document for single-index vector collection:

  • Java

  • Python

VectorDocument<String> document = VectorDocument.of(
        "{'genre': 'novel', 'year': 1976}",
        VectorValues.of(
                new float[]{0.2f, 0.9f, -1.2f, 2.2f, 2.2f, 3.0f}
        )
);
document = Document(
    "{'genre': 'novel', 'year': 1976}",
    [
        Vector("", Type.DENSE, [0.2, 0.9, -1.2, 2.2, 2.2, 3.0]),
    ],
)

For multi-index collections, specify the names of the indexes to which the vectors belong:

  • Java

  • Python

VectorDocument<String> document = VectorDocument.of(
        "{'genre': 'fiction', 'year': 2022}",
        VectorValues.of(
                Map.of(
                        "word2vec-index", new float[] {0.2f, 0.9f, -1.2f, 2.2f, 2.2f, 3.0f},
                        "glove-index", new float[] {2f, 3f, 2f, 10f, -2f}
                )
        )
);
document = Document(
    "{'genre': 'novel', 'year': 1976}",
    [
        Vector("word2vec-index", Type.DENSE, [0.2, 0.9, -1.2, 2.2, 2.2, 3.0]),
        Vector("glove-index", Type.DENSE, [2, 3, 2, 10, -2]),
    ],
)

Put entries

To put a single document to a vector collection, use the putAsync, putIfAbsent or setAsync method of the VectorCollection class.

  • Java

  • Python

VectorDocument<String> document = VectorDocument.of(
        "{'genre': 'novel', 'year': 1976}",
        VectorValues.of(new float[] {0.2f, 0.9f, -1.2f, 2.2f, 2.2f, 3.0f})
);
CompletionStage<VectorDocument<String>> result = vectorCollection.putAsync("1", document);
vectorCollection.put("1", Document(
    "{'genre': 'novel', 'year': 1976}",
    [
        Vector("", Type.DENSE, [0.2, 0.9, -1.2, 2.2, 2.2, 3.0]),
    ],
))

To put several documents to a vector collection, use the putAllAsync method of the VectorCollection class.

  • Java

  • Python

VectorDocument<String> document1 = VectorDocument.of("{'genre': 'novel', 'year': 1976}", VectorValues.of(new float[] {1.2f, -0.3f, 2.2f, 0.4f, 0.3f, 0.4f}));
VectorDocument<String> document2 = VectorDocument.of("{'genre': 'fiction', 'year': 2022}", VectorValues.of(new float[] {1.2f, -0.3f, 2.2f, 0.4f, 0.3f, -2.0f}));
CompletionStage<Void> result = vectorCollection.putAllAsync(
        Map.of("1", document1, "2", document2)
);
vectorCollection.put_all(
    {
        "1": Document(
            "{'genre': 'novel', 'year': 1976}",
            [
                Vector("", Type.DENSE, [1.2, -0.3, 2.2, 0.4, 0.3, 0.4]),
            ]),
        "2": Document(
            "{'genre': 'novel', 'year': 1976}",
            [
                Vector("", Type.DENSE, [1.2, -0.3, 2.2, 0.4, 0.3, -2.0]),
            ]),
    }
)

Read entries

To get a document from a vector collection, use the getAsync method of the VectorCollection class.

  • Java

  • Python

CompletionStage<VectorDocument<String>> result = vectorCollection.getAsync("1");
vectorCollection.get("1")

Update entries

To update a single entry in a vector collection, use the putAsync or setAsync method of the VectorCollection class.

  • Java

  • Python

VectorDocument<String> document = VectorDocument.of("{'genre': 'fiction', 'year': 2022}", VectorValues.of(new float[] {1.2f, -0.3f, 2.2f, 0.4f, 0.3f, 0.4f}));
CompletionStage<Void> result = vectorCollection.setAsync("1", document);
vectorCollection.set("1", Document("{'genre': 'fiction', 'year': 2022}",
    [
        Vector("", Type.DENSE, [1.2, -0.3, 2.2, 0.4, 0.3, 0.4]),
    ]
))
When you update an entry, you have to provide both VectorDocument and VectorValues even if only one of them is changed for the entry.

Delete entries

To delete a document from a vector collection, use the deleteAsync or removeAsync method of the VectorCollection class.

  • Java

  • Python

CompletionStage<Void> resultDelete = vectorCollection.deleteAsync("1");
CompletionStage<VectorDocument<String>> resultRemove = vectorCollection.removeAsync("2");
vectorCollection.delete("1")
vectorCollection.remove("2")
These methods do not delete vectors but do mark them as deleted. This can impact search speed and memory usage. To permanently remove vectors from the index, you must run index optimization after deletion. For further information on running index optimization, see optimize method.

Vector search returns entries with vectors that are most similar to the query vector, based on specified metrics. Any query consists of a single vector to search and the search options, such as the limit of results to retrieve. For more information on the available options, see Similarity search options.

For a similarity search, use the searchAsync method of the VectorCollection.

In a single index vector collection, you do not need to specify the name of the index to search. However, for a multi-index vector collection, you must specify the name of the index to search.

Example for single-index vector collection:

  • Java

  • Python

CompletionStage<SearchResults<String, String>> results = vectorCollection.searchAsync(
        VectorValues.of(new float[] {0f, 0f, 0.2f, -0.3f, 1.2f, -0.5f}),
        SearchOptions.builder()
            .limit(5)
            .includeVectors()
            .includeValue()
            .build()
);
results = vectorCollection.search_near_vector(
    Vector("", Type.DENSE, [0, 0, 0.2, -0.3, 1.2, -0.5]),
    limit=5,
    include_value=True,
    include_vectors=True,
)

Example for multi-index vector collection:

  • Java

  • Python

CompletionStage<SearchResults<String, String>> results = vectorCollection.searchAsync(
        VectorValues.of("glove-index", new float[] {0f, 0f, 0.2f, -0.3f, 1.2f, -0.5f}),
        SearchOptions.builder()
            .limit(5)
            .includeVectors()
            .includeValue()
            .build()
);
results = vectorCollection.search_near_vector(
    Vector("glove-index", Type.DENSE, [0, 0, 0.2, -0.3, 1.2, -0.5]),
    limit=5,
    include_value=True,
    include_vectors=True,
)

Similarity search options

Search parameters are passed as a searchOptions argument to the searchAsync method.

Table 4. Search options
Option Description Default

limit

The number of results to return in a search result

10

includeValue

Whether to include the user value in the search result. By default, the user value is not included. To include the user value, set to TRUE

FALSE

includeVectors

Whether to include the vector values in the search result. By default, the vector values are not included. To include the vector values, set to TRUE

FALSE

hints

Extra hints for the search.

NULL

Table 5. Available hints
Hint Description

partitionLimit

Number of results to fetch from each partition.

memberLimit

Number of results to fetch from member in two-stage search.

singleStage

Force use of single stage search.

  • Java

var options = SearchOptions.builder()
                .limit(10)
                .includeValue()
                .includeVectors()
                .hint("partitionLimit", 1)
                .build();

INFORMATION: Hints allow fine-tuning for some aspects of search execution but are subject to change and may be removed in future versions.

Manage collection

This section provides additional methods for managing the vector collection.

Optimize collection

An optimization operation could be needed in the following cases:

  • To permanently delete vectors that were marked for removal.

  • After adding a significant number of vectors.

  • When the collection returns fewer vectors than expected.

The optimization operation can be a time-consuming and resource-intensive process, and no mutating operations are allowed during this process.
  • Java

  • Python

CompletionStage<Void> result = vectorCollection.optimizeAsync("glove-index");
vectorCollection.optimize("glove-index")

Clear collection

To remove all vectors and values from the vector collection use the clearAsync() method .

  • Java

  • Python

CompletionStage<Void> result = vectorCollection.clearAsync();
vectorCollection.clear()

Limitations in beta version

As this is a beta version, Vector Collection has some limitations; the most significant of which are as follows:

  1. The API could change in future versions

  2. The rolling-upgrade compatibility guarantees do not apply for vector collections. You might need to delete existing vector collections before migrating to a future version of Hazelcast

  3. The lack of fault tolerance, as backups cannot yet be configured. However, data in collections is migrated to other cluster members on graceful shutdown and a new member joining the cluster, which means that normal cluster maintenance (such as a rolling restart) is possible without data loss.

  4. Only on-heap storage of vector collections is available

Known issue

There is currently a known issue that has potential for causing a memory leak in Vector collections in some scenarios:

  1. Using destroy or clear on a non-empty vector collection.

  2. Making a vector collection partition empty by removing the last entry using deleteAsync or removeAsync.

  3. Updating the only entry in a vector collection partition using one of the put/set methods.

  4. Repeating migrations back and forth when a member is not restarted. This should not significantly affect rolling restart, provided sufficient heap margin. The leak may manifest itself when a subset of members is not restarted and the rest of them are repeatedly shut down or restarted gracefully.

The workaround for scenarios 1 - 3 is to avoid those situations or restart the affected cluster. For scenario 4 the workaround is to restart the affected member or cluster. The restart can be graceful, which should not cause loss of data.