Настройка хранилища#

В Platform V Vector DB (далее - Vector DB) все данные внутри одной коллекции разделяются на сегменты. Каждый сегмент имеет независимые хранилища векторов и полезной нагрузки, а также индексы.

Данные, хранящиеся в сегментах, обычно не пересекаются. Однако хранение одной и той же точки в разных сегментах не вызовет проблем, так как поиск содержит механизм удаления дубликатов.

Сегменты состоят из хранилищ векторов и полезной нагрузки, индексов векторов и полезной нагрузки, а также сопоставителя идентификаторов, который хранит связь между внутренними и внешними идентификаторами.

Сегмент может быть appendable или non-appendable, в зависимости от типа используемого хранилища и индекса. Можно свободно добавлять, удалять и запрашивать данные в сегменте appendable. С помощью сегмента non-appendable можно только читать и удалять данные.

Конфигурация сегментов в коллекции может различаться и быть независимой друг от друга, но хотя бы один сегмент типа «добавляемый» должен присутствовать в коллекции.

Предусловия#

Пререквизиты:

  • Доступ к серверу: SSH или физический доступ.

  • Установленный продукт: сервер должен быть запущен (кроме операций с файлами данных).

  • Исходный код: скрипты находятся в /src/bin/ репозитория продукта.

  • Rust: установленный компилятор для сборки утилит.

Привилегии:

  • Файловая система: права на чтение/запись в storage_path.

  • API-ключ: обязателен ключ с правами admin для большинства операций.

  • Сетевой доступ: доступность API-порта (порт по умолчанию 6333).

Ключевые требования:

  • Vector DB остановлен.

  • Наличие прав на изменение конфигурационных файлов.

Хранилище векторов#

В зависимости от требований приложения Vector DB может использовать один из вариантов хранения данных. Выбор необходимо сделать между скоростью поиска и объемом используемой оперативной памяти.

Оперативное хранилище — сохраняет все векторы в оперативной памяти, обладает самой высокой скоростью, поскольку доступ к диску требуется только для обеспечения постоянства.

Хранилище Memmap - создает виртуальное адресное пространство, связанное с файлом на диске. Файлы mmapped напрямую не загружаются в оперативную память. Вместо этого они используют кеш страниц для доступа к содержимому файла. Эта схема позволяет гибко использовать имеющуюся память. При достаточном объеме оперативной памяти она почти такая же быстрая, как оперативная память.

Хранилище полезной нагрузки#

Vector DB поддерживает два типа хранилищ полезной нагрузки: InMemory и OnDisk.

Хранилище полезной нагрузки InMemory организовано аналогично векторной памяти. Данные полезной нагрузки загружаются в оперативную память при запуске службы, тогда как диск и Gridstore используются исключительно для постоянного хранения. Этот тип хранилища работает довольно быстро, однако он может потребовать много места для хранения всех данных в оперативной памяти, особенно если полезная нагрузка связана с большими значениями — абстрактными текстовыми описаниями или даже изображениями.

Если значения полезной нагрузки велики, лучше использовать хранилище полезной нагрузки OnDisk. Этот тип хранилища будет считывать и записывать полезную нагрузку непосредственно в БД, поэтому ему не потребуется значительный объем оперативной памяти для хранения. Недостатком является задержка доступа. Если необходимо запросить векторы по условиям, основанным на полезной нагрузке — проверка значений, хранящихся на диске, может занять слишком много времени. В таком сценарии рекомендуется создать индекс полезной нагрузки для каждого поля, используемого в условиях фильтрации, чтобы избежать обращения к диску. Как только будет создан индекс полей, Vector DB сохранит все значения указанного поля в оперативной памяти независимо от типа хранилища полезной нагрузки.

Желаемый тип хранилища полезной нагрузки можно указать либо в конфигурационном файле Config.yaml, либо с помощью параметра коллекции on_disk_payload во время создания коллекции.

Версионирование#

Для обеспечения целостности данных Vector DB выполняет все изменения данных в два этапа. На первом этапе данные записываются в журнал упреждающей записи (WAL), который упорядочивает все операции и присваивает им последовательный номер.

Как только изменение было добавлено в WAL, оно не потеряется даже в случае потери питания. Затем изменения поступают в сегменты. Каждый сегмент хранит последнюю версию изменений, примененных к нему, а также версию каждой отдельной точки. Если новое изменение имеет порядковый номер меньше текущей версии точки, обновитель проигнорирует изменение. Этот механизм позволяет Vector DB безопасно и эффективно восстанавливать хранилище из WAL в случае ненормального завершения работы.

Последовательность выполнения#

Настройка хранилища Memmap#

Существует два способа настроить использование хранилища memmap (также известного как on-disk):

  • Установите параметр on_disk для векторов при создании API-коллекции:

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 768,
      "distance": "Cosine",
      "on_disk": true
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(
        size=768, distance=models.Distance.COSINE, on_disk=True
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 768,
    distance: "Cosine",
    on_disk: true,
  },
});
use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, VectorParamsBuilder};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(768, Distance::Cosine).on_disk(true)),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.VectorParams;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        "{collection_name}",
        VectorParams.newBuilder()
            .setSize(768)
            .setDistance(Distance.Cosine)
            .setOnDisk(true)
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
  "{collection_name}",
  new VectorParams
  {
      Size = 768,
      Distance = Distance.Cosine,
      OnDisk = true
  }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     768,
      Distance: qdrant.Distance_Cosine,
      OnDisk:   qdrant.PtrOf(true),
  }),
})

Это создаст коллекцию со всеми векторами, сразу сохраненными в хранилище memmap. Это рекомендуемый способ, если экземпляр Vector DB работает с быстрыми дисками и большими коллекциями.

  • Установите опцию memmap_threshold. Этот параметр задает пороговое значение, после которого сегмент будет преобразован в хранилище memmap.

Настройка порога Memmap#

Существует два способа настройки порога:

  1. Установить глобальный порог в конфигурационном файле Config.yaml. Параметр называется memmap_threshold (memmap_threshold_kb).

  2. Отдельно задать порог для каждой коллекции во время ее создания или обновления.

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 768,
      "distance": "Cosine"
    },
    "optimizers_config": {
        "memmap_threshold": 20000
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
    optimizers_config=models.OptimizersConfigDiff(memmap_threshold=20000),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 768,
    distance: "Cosine",
  },
  optimizers_config: {
    memmap_threshold: 20000,
  },
});
use qdrant_client::qdrant::{
    CreateCollectionBuilder, Distance, OptimizersConfigDiffBuilder, VectorParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(768, Distance::Cosine))
            .optimizers_config(OptimizersConfigDiffBuilder::default().memmap_threshold(20000)),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.OptimizersConfigDiff;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        CreateCollection.newBuilder()
            .setCollectionName("{collection_name}")
            .setVectorsConfig(
                VectorsConfig.newBuilder()
                    .setParams(
                        VectorParams.newBuilder()
                            .setSize(768)
                            .setDistance(Distance.Cosine)
                            .build())
                    .build())
            .setOptimizersConfig(
                OptimizersConfigDiff.newBuilder().setMemmapThreshold(20000).build())
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
  collectionName: "{collection_name}",
  vectorsConfig: new VectorParams { Size = 768, Distance = Distance.Cosine },
  optimizersConfig: new OptimizersConfigDiff { MemmapThreshold = 20000 }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     768,
      Distance: qdrant.Distance_Cosine,
  }),
  OptimizersConfig: &qdrant.OptimizersConfigDiff{
      MaxSegmentSize: qdrant.PtrOf(uint64(20000)),
  },
})

Правило выбора параметра порога memmap:

  • сбалансированный сценарий использования — установите порог memmap таким же, как indexing_threshold (по умолчанию 20000). В этом случае оптимизатор не выполнит никаких дополнительных запусков и оптимизирует все пороги одновременно.

  • высокая нагрузка записи и мало оперативной памяти — установите порог memmap ниже, чем indexing_threshold, например, 10000. В этом случае оптимизатор сначала преобразует сегменты в хранилище memmap, а затем применяет индексирование.

Кроме того, можно использовать хранилище memmap не только для векторов, но и для индекса HNSW. Для этого нужно установить параметр hnsw_config.on_disk равным true во время создания или обновления коллекции.

PUT /collections/{collection_name}
{
    "vectors": {
      "size": 768,
      "distance": "Cosine",
      "on_disk": true
    },
    "hnsw_config": {
        "on_disk": true
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE, on_disk=True),
    hnsw_config=models.HnswConfigDiff(on_disk=True),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.createCollection("{collection_name}", {
  vectors: {
    size: 768,
    distance: "Cosine",
    on_disk: true,
  },
  hnsw_config: {
    on_disk: true,
  },
});
use qdrant_client::qdrant::{
    CreateCollectionBuilder, Distance, HnswConfigDiffBuilder,
    VectorParamsBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client
    .create_collection(
        CreateCollectionBuilder::new("{collection_name}")
            .vectors_config(VectorParamsBuilder::new(768, Distance::Cosine).on_disk(true))
            .hnsw_config(HnswConfigDiffBuilder::default().on_disk(true)),
    )
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.HnswConfigDiff;
import io.qdrant.client.grpc.Collections.VectorParams;
import io.qdrant.client.grpc.Collections.VectorsConfig;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .createCollectionAsync(
        CreateCollection.newBuilder()
            .setCollectionName("{collection_name}")
            .setVectorsConfig(
                VectorsConfig.newBuilder()
                    .setParams(
                        VectorParams.newBuilder()
                            .setSize(768)
                            .setDistance(Distance.Cosine)
                            .setOnDisk(true)
                            .build())
                    .build())
            .setHnswConfig(HnswConfigDiff.newBuilder().setOnDisk(true).build())
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
  collectionName: "{collection_name}",
  vectorsConfig: new VectorParams { Size = 768, Distance = Distance.Cosine, OnDisk = true },
  hnswConfig: new HnswConfigDiff { OnDisk = true }
);
import (
  "context"

  "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
  Host: "localhost",
  Port: 6334,
})

client.CreateCollection(context.Background(), &qdrant.CreateCollection{
  CollectionName: "{collection_name}",
  VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
      Size:     768,
      Distance: qdrant.Distance_Cosine,
      OnDisk:   qdrant.PtrOf(true),
  }),
  HnswConfig: &qdrant.HnswConfigDiff{
      OnDisk: qdrant.PtrOf(true),
  },
})

Результат#

Параметр

Результат

Использование памяти

Снижение потребления RAM за счет хранения данных на диске

Производительность

Задержки при чтении/записи увеличиваются (по сравнению с in-memory)

Масштабируемость

Возможность работы с большими коллекциями без ограничений по памяти

Гибкость

Динамическое управление порогами Memmap для разных коллекций.

Надежность

Данные сохраняются на диске, что снижает риски потери информации при сбоях