Obsah
2. Podporované typy prvků vektorů
3. Indexy založené na použití sofistikovanějších datových struktur
4. Rozdělení celého prostoru Voroného dekompozicí
5. Konstrukce indexu IVFFlat v Postgresu
6. Index HNSW (Hierarchical Navigable Small World)
7. Základ pro benchmarky, které budeme provádět
8. Projektový soubor a spuštění benchmarku
9. Rychlost zápisu vektorů do tabulky
10. Vizualizace výsledků benchmarku v grafu
11. Vliv počtu dimenzí na rychlost zápisu
13. Časy vyhledávání vektorů na základě jejich podobnosti
14. Zdrojový kód benchmarku zjišťujícího časy vyhledávání
16. Metriky podporované při testování podobnosti vektorů
17. Změření rychlosti vyhledávání s využitím různých metrik
19. Repositář s demonstračními příklady
1. pgvector, embedding a sémantické vyhledávání (2. část: sofistikovanější indexy a různé typy vektorů)
V článcích pgvector: vektorová databáze postavená na Postgresu a Rozšíření PostgreSQL jménem pgvector, embedding a sémantické vyhledávání (1. část) jsme se seznámili se základními vlastnostmi knihovny pgvector, která zajišťuje rozšíření možností databáze PostgreSQL o operace nad vektory. Připomeňme si, že díky tomuto rozšíření je možné do tabulek ukládat záznamy obsahující vektory s pevně zadanou délkou a typem prvků. Tyto vektory jsou ukládány v binární podobě, tj. například vektor se 100 prvky typu float32 bude uložen v 100×4=400 bajtech (nepoužívá se tedy textový formát ani JSON). Ovšem pgvector podporuje i vyhledávání vektorů na základě jejich podobnosti (vector similarity search), přičemž je možné využívat různé metriky (L1, L2, skalární součin, Hammingovu vzdálenost atd.). A navíc lze vyhledávání urychlit pomocí speciálních „vektorových“ indexů.
V dnešním článku se zaměříme na benchmarky. Vektory totiž mohou obsahovat prvky různých typů, při vyhledávání lze využít odlišné metriky atd. Tyto parametry pochopitelně mají vliv na rychlost práce s databází – a efektivní a rychlé vyhledávání vektorů je například v oblasti jazykových modelů kritickou operací.
2. Podporované typy prvků vektorů
Rozšíření pgvector podporuje zpracování vektorů s prvky několika typů. Tyto typy jsou vypsány v následující tabulce:
| Jméno typu | Způsob uložení | Maximální počet dimenzí | Stručný popis |
|---|---|---|---|
| vector | 4×počet_dimenzí+8 | 2000 | prvky vektorů jsou typu float32/single (4 bajty) |
| halfvec | 2×počet_dimenzí+8 | 4000 | prvky vektorů jsou typu half float (2 bajty) |
| bit | počet_dimenzí/8+8 | 64000 | každý prvek je uložen v jediném bitu |
| sparcevec | 8×nenulové_prvky+16 | 1000 nenulových prvků | uloženy jsou jen nenulové prvky |
3. Indexy založené na použití sofistikovanějších datových struktur
V jazykových modelech, ale i v některých dalších oblastech informatiky, se můžeme setkat s datovými sadami, které obsahují desítky či dokonce stovky milionů vektorů. To znamená, že (v tomto případě zcela pochopitelně) existuje snaha o zmenšení času vyhledávání – ideálně změnou časové složitosti (z lineární složitosti k například logaritmické složitosti nebo složitosti s odmocninou), nebo alespoň sice se zachováním lineární složitosti, ovšem s jejím menším sklonem (což je ona někdy podceňovaná konstanta k ve vztahu k O(n)). I v těchto oblastech nám databáze PostgreSQL s rozšířením pgvector nabízí různá řešení.
V rámci dalších kapitol se seznámíme se dvěma různými technikami, které jsou založeny na použití sofistikovanějších datových struktur, než jsou pouhé sekvence (resp. v tomto případě tabulky) vektorů, které je nutné při vyhledávání vektorů na základě jejich podobnosti procházet sekvenčně, tj. prvek po prvku.
4. Rozdělení celého prostoru Voroného dekompozicí
Jeden z dostupných a relativně často používaných algoritmů, které byly navrženy pro urychlení vyhledávání podobných vektorů (similarity search) spočívá v tom, že se n-dimenzionální prostor, ve kterém vektory vstupní datové sady leží, rozdělí do zadaného či automaticky vypočteného (odhadnutého) počtu oblastí. Pro rozdělení se přitom typicky používá Voroného dekompozice, i když by teoreticky bylo možné použít i další metody (možná zobecněný oktalový strom nebo podobné rozdělení n-rozměrného prostoru). Algoritmus vyhledávání vektorů na základě jejich podobnosti je v tom nejjednodušším případě upraven následujícím způsobem:
- Vstupem je vektor (dotaz) k němuž v datové (v případě pgvector tabulce) sadě hledáme nejbližší vektory
- Pro tento vstupní vektor je zjištěna oblast (Voroného dekompozice), ve které bude prováděno vyhledávání
- Následně jsou nejbližší vektory vyhledávány pouze v této oblasti
Problém spočívá v tom, že právě popsaný algoritmus nemusí vyhledat skutečně nejbližší vektory. Tato potenciálně problematická situace nastane v případě, že se vstupní vektor nachází blízko hranice oblasti a nejbližší vektor (vektory) by se tedy nacházel(y) těsně za touto hranicí. Proto může být algoritmus vyhledávání upraven do nepatrně odlišné podoby:
- Vstupem je vektor (dotaz) k němuž v datové sadě hledáme nejbližší vektory
- Pro tento vstupní vektor je zjištěna oblast (Voroného dekompozice), ve které bude prováděno vyhledávání
- Dále jsou zjištěno m sousedních oblastí k nalezené oblasti (m je většinou nějakým způsobem konfigurovatelné)
- Následně jsou nejbližší vektory vyhledávány v nalezené oblasti (bod 2) i oblastech sousedních (bod 3)
Pochopitelně platí, že čím větší je hodnota m, tím pomalejší, ale přesnější je vyhledání nejbližších vektorů. V limitním případě (m=celkový počet oblastí) se vlastně vrátíme k prohledávání celého prostoru.
5. Konstrukce indexu IVFFlat v Postgresu
Rozdělení n-dimenzionálního prostoru podle Voroného dekompozice je pochopitelně nutné nějakým způsobem realizovat. Podporu pro toto rozdělení programátorům sice nabízí samotné rozšíření pgvector, ovšem samotná konstrukce indexu se nepatrně zkomplikuje. Připomeňme si, že původně byla tabulka obsahující vektory zkonstruována následujícím způsobem:
CREATE TABLE IF NOT EXISTS vektoryXYZ (
id bigserial PRIMARY KEY,
embedding vector(počet_dimenzí) NOT NULL
);
Při aplikaci Voroného dekompozice je nutné index explicitně vytvořit, například následujícím způsobem:
CREATE INDEX ON vektoryXYZ
USING ivfflat (embedding vector_l2_ops)
WITH (lists = počet_oblastí);
V tomto případě je nutné explicitně specifikovat počet oblastí. Uvádí se, že pro databáze obsahující do milionu vektorů by měl počet oblastí zhruba odpovídat počtu vektorů podělených konstantou 1000. Pro větší databáze by se mělo jednat o hodnotu, která se blíží druhé odmocnině počtu vektorů. Ovšem pochopitelně vás nic neomezuje v provedení benchmarků pro různé počty oblastí a nalezení nejlepší hodnoty (tyto benchmarky se provedou jedenkrát, zatímco vektorová databáze bude používána opakovaně).
6. Index HNSW (Hierarchical Navigable Small World)
Dalším potenciálně užitečným typem indexu, které jsou nabízeny (nejenom) rozšířením pgvector (ale například i ve Faissu), je index označovaný zkratkou HNSW neboli Hierarchical Navigable Small World. Jak již název tohoto indexu naznačuje, je metoda HNSW založena na rozdělení celého prostoru do hierarchicky organizovaných menších částí (ty jsou nazývány „malé světy“, z čehož plyne i zkratka celého algoritmu). Samotné rozdělování prostoru je časově velmi náročné, ovšem na druhou stranu se urychlí vyhledávání podobných vektorů, a to o jeden až dva řády (do značné míry ovšem záleží na dalších parametrech). Samozřejmě se i s tímto indexem pojí určité další nevýhody.
Případný index se vytvoří na základě toho, jakou metriku budeme při vyhledávání používat:
CREATE INDEX ON vektoryXYZ
USING hnsw (embedding vector_l2_ops);
Za slovo embedding se zapisuje operace podle příslušné metriky:
| Metrika | Slovo zapsané za embedding |
|---|---|
| L1 | vector_l1_ops |
| L2 | vector_l2_ops |
| skalární součin | vector_ip_ops |
| kosinus | vector_cosine_ops |
| Hammingova vzdálenost | bit_hamming_ops |
| Jaccardova vzdálenost | bit_jaccard_ops |
Ovšem dají se specifikovat i další parametry indexu, například:
CREATE INDEX ON vektoryXYZ
USING hnsw (embedding vector_l2_ops);
WITH (m = 16, ef_construction = 64);
Vhodné nastavení parametrů lze opět zjistit s využitím benchmarků.
7. Základ pro benchmarky, které budeme provádět
V dalších kapitolách budeme spouštět různé varianty benchmarků, kterými si ověříme chování rozšíření pgvector pro relativně malé, ale i pro rozsáhlejší vektorové databáze. Všechny tyto benchmarky budou založeny na stejném základu, který je popsán v této kapitole. V benchmarku vždy vytvoříme novou tabulku se sloupcem s vektory a výpisem všech viditelných tabulek si ověříme, že tabulka existuje. Dále do této tabulky vložíme zadaný počet pseudonáhodných vektorů s předem známou dimenzí. Celý zdrojový kód je založen na Pythonovských knihovnách psycopg2 a pgvector:
# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
from time import time
import psycopg2
from pgvector.psycopg2 import register_vector
import numpy as np
# parametry benchmarku
DIMENSIONS = 16
VECTOR_COUNT = 10000
def create_vector_table(connection, dimensions, print_tables=True):
"""Vytvoření tabulky obsahující sloupec s vektory."""
DROP_TABLE_STATEMENT = """
DROP TABLE IF EXISTS v3
"""
CREATE_TABLE_STATEMENT = """
CREATE TABLE IF NOT EXISTS v3 (
id bigserial PRIMARY KEY,
embedding vector(%s) NOT NULL
);
"""
LIST_TABLES_QUERY = """
SELECT table_schema,table_name
FROM information_schema.tables
WHERE table_schema='public'
ORDER BY table_schema,table_name;
"""
with connection.cursor() as cursor:
cursor.execute(DROP_TABLE_STATEMENT)
connection.commit()
cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
connection.commit()
cursor.execute(LIST_TABLES_QUERY)
tables = cursor.fetchall()
if print_tables:
print("Tables in database:")
for table in tables:
print(f" {table[0]}: {table[1]}")
print()
def fill_in_vector_table(connection, dimensions, vector_count):
"""Naplnění tabulky náhodnými vektory."""
t1 = time()
with connection.cursor() as cursor:
# prozatím budeme tabulku naplňovat po jednotlivých záznamech
for i in range(vector_count):
# náhodný vektor
vector = np.random.rand(dimensions).astype("float32")
cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
connection.commit()
t2 = time()
return t2 - t1
def print_vector_count(connection):
"""Tisk počtu vektorů uložených v tabulce."""
with connection.cursor() as cursor:
cursor.execute("SELECT count(*) FROM v3")
count = cursor.fetchone()
print(f"Vectors stored in table v3: {count[0]}")
def main():
"""Vstupní bod do benchmarku."""
# připojení k databázi
connection = psycopg2.connect(
host="", port=5432, user="tester", password="123qwe", dbname="test"
)
try:
# budeme používat rozšíření pgvector
register_vector(connection)
# vytvoření tabulky s vektory
create_vector_table(connection, DIMENSIONS)
# naplnění tabulky s vektory
t = fill_in_vector_table(connection, DIMENSIONS, VECTOR_COUNT)
print(f"Time to fill-in database: {t:3} seconds")
# zjištění počtu skutečně uložených vektorů
print_vector_count(connection)
finally:
connection.close()
# spuštění benchmarku
main()
8. Projektový soubor a spuštění benchmarku
Projektový soubor, který bude využit pro spuštění benchmarku, může vypadat následovně:
[project]
name = "sentence-transformer"
version = "0.1.0"
description = "Sentence transformer demos"
authors = [
{name = "Pavel Tisnovsky", email = "ptisnovs@redhat.com"},
]
dependencies = [
"datasets>=4.0.0",
"pgvector>=0.4.1",
"psycopg2>=2.9.10",
"sentence-transformers>=5.0.0",
]
requires-python = ">=3.12"
readme = "README.md"
license = {text = "MIT"}
[tool.pdm]
distribution = false
Samotné spuštění benchmarku provedeme přes pdm nebo uv:
$ uv run index-benchmark-1.py
Tables in database:
public: v3
Time to fill-in database: 0.02258753776550293 seconds
Vectors stored in table v3: 10
Nakonec si v konzoli PostgreSQL ověříme, zda byla tabulka v3 vytvořena a kolik obsahuje záznamů:
$ psql -U tester Password for user tester: psql (16.9) Type "help" for help.
Výpis všech tabulek:
test=> \dt
List of relations
Schema | Name | Type | Owner
--------+-------------------+-------+--------
public | v3 | table | tester
(1 row)
Schéma tabulky s3 se vypíše takto:
test=> \d v3
Table "public.v3"
Column | Type | Collation | Nullable | Default
-----------+------------+-----------+----------+--------------------------------
id | bigint | | not null | nextval('v3_id_seq'::regclass)
embedding | vector(16) | | not null |
Indexes:
"v3_pkey" PRIMARY KEY, btree (id)
Zjištění počtu zapsaných vektorů:
test=> select count(*) from v3; count -------- 100000 (1 row)
Jen pro zajímavost se podívejme na několik záznamů v tabulce v3:
test=> select * from v3 limit 10; 1 | [0.16963236,0.95897835,0.51473874,0.45849234,0.113175556,0.68751436,0.9654627,0.8743154,0.92741364,0.36292967,0.6634899,0.9496507,0.17675222,0.54969317,0 .6603673,0.43664044,0.88731503,0.008242492,... ... ... ... 10 | [0.7153847,0.7689986,0.75396407,0.05730126,0.74784076,0.34615812,0.250757,0.8888536,0.01584173,0.3476689,0.6566004,0.65356773,0.81164205,0.7884383,0.5832 1726,0.17815429,0.055939768,0.55867046,0.26687172,0.4249743,0.88436496,0.22805858,0.57775205,0.9806432,0.35423538,0.9512405,0.28012627,0.3412125,0.41170472,0.7 2094446,0.35541078,0.64600503, (10 rows)
9. Rychlost zápisu vektorů do tabulky
V prvním reálném benchmarku změříme rychlost zápisu vektorů do prázdné tabulky. V benchmarku se bude postupně zvětšovat počet zapisovaných vektorů a bude se měřit čas zápisu všech těchto vektorů:
# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy zápisu vektorů
from time import time
import psycopg2
from pgvector.psycopg2 import register_vector
import numpy as np
# parametry benchmarku
DIMENSIONS = 16
MAX_VECTOR_COUNT = 100000
STEPS = 10
def create_vector_table(connection, dimensions, print_tables=True):
"""Vytvoření tabulky obsahující sloupec s vektory."""
DROP_TABLE_STATEMENT = """
DROP TABLE IF EXISTS v3
"""
CREATE_TABLE_STATEMENT = """
CREATE TABLE IF NOT EXISTS v3 (
id bigserial PRIMARY KEY,
embedding vector(%s) NOT NULL
);
"""
LIST_TABLES_QUERY = """
SELECT table_schema,table_name
FROM information_schema.tables
WHERE table_schema='public'
ORDER BY table_schema,table_name;
"""
with connection.cursor() as cursor:
cursor.execute(DROP_TABLE_STATEMENT)
connection.commit()
cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
connection.commit()
cursor.execute(LIST_TABLES_QUERY)
tables = cursor.fetchall()
if print_tables:
print("Tables in database:")
for table in tables:
print(f" {table[0]}: {table[1]}")
print()
def fill_in_vector_table(connection, dimensions, vector_count):
"""Naplnění tabulky náhodnými vektory."""
t1 = time()
with connection.cursor() as cursor:
# prozatím budeme tabulku naplňovat po jednotlivých záznamech
for i in range(vector_count):
# náhodný vektor
vector = np.random.rand(dimensions).astype("float32")
cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
connection.commit()
t2 = time()
return t2 - t1
def print_vector_count(connection):
"""Tisk počtu vektorů uložených v tabulce."""
with connection.cursor() as cursor:
cursor.execute("SELECT count(*) FROM v3")
count = cursor.fetchone()
print(f"Vectors stored in table v3: {count[0]}")
def main():
"""Vstupní bod do benchmarku."""
# připojení k databázi
connection = psycopg2.connect(
host="", port=5432, user="tester", password="123qwe", dbname="test"
)
try:
# budeme používat rozšíření pgvector
register_vector(connection)
# benchmark - naplnění tabulky s vektory
print("Vector count", "Time")
for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
# vytvoření tabulky s vektory
create_vector_table(connection, DIMENSIONS, print_tables=False)
t = fill_in_vector_table(connection, DIMENSIONS, vector_count)
print(vector_count, t)
# zjištění počtu skutečně uložených vektorů
print_vector_count(connection)
finally:
connection.close()
# spuštění benchmarku
main()
Naměřené výsledky mohou vypadat následovně:
Vector count Time 0 0.0002460479736328125 10000 0.5639476776123047 20000 1.2714269161224365 30000 1.674407720565796 40000 2.2950122356414795 50000 2.6440303325653076 60000 3.145857334136963 70000 3.888411521911621 80000 5.093366622924805 90000 5.133985996246338 100000 5.900954723358154 Vectors stored in table v3: 100000
10. Vizualizace výsledků benchmarku v grafu
Výsledky benchmarků v tabulkové podobě se poměrně špatně čtou, resp. nemusí z nich být zcela zřejmé, jestli je čas zápisu vektorů lineární funkcí počtu zapisovaných vektorů či nikoli (tady pochopitelně závisí na tom, jaký index je použit). Proto si nyní benchmark z předchozí kapitoly upravíme do takové podoby, aby se na konci naměřené časy zobrazily formou grafu. Pro zobrazení grafu bude použita knihovna Matplotlib:
# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy zápisu vektorů
# - vizualizace výsledků formou grafu
from time import time
import psycopg2
from pgvector.psycopg2 import register_vector
import matplotlib.pyplot as plt
import numpy as np
# parametry benchmarku
DIMENSIONS = 16
MAX_VECTOR_COUNT = 100000
STEPS = 10
def create_vector_table(connection, dimensions, print_tables=True):
"""Vytvoření tabulky obsahující sloupec s vektory."""
DROP_TABLE_STATEMENT = """
DROP TABLE IF EXISTS v3
"""
CREATE_TABLE_STATEMENT = """
CREATE TABLE IF NOT EXISTS v3 (
id bigserial PRIMARY KEY,
embedding vector(%s) NOT NULL
);
"""
LIST_TABLES_QUERY = """
SELECT table_schema,table_name
FROM information_schema.tables
WHERE table_schema='public'
ORDER BY table_schema,table_name;
"""
with connection.cursor() as cursor:
cursor.execute(DROP_TABLE_STATEMENT)
connection.commit()
cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
connection.commit()
cursor.execute(LIST_TABLES_QUERY)
tables = cursor.fetchall()
if print_tables:
print("Tables in database:")
for table in tables:
print(f" {table[0]}: {table[1]}")
print()
def fill_in_vector_table(connection, dimensions, vector_count):
"""Naplnění tabulky náhodnými vektory."""
t1 = time()
with connection.cursor() as cursor:
# prozatím budeme tabulku naplňovat po jednotlivých záznamech
for i in range(vector_count):
# náhodný vektor
vector = np.random.rand(dimensions).astype("float32")
cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
connection.commit()
t2 = time()
return t2 - t1
def print_vector_count(connection):
"""Tisk počtu vektorů uložených v tabulce."""
with connection.cursor() as cursor:
cursor.execute("SELECT count(*) FROM v3")
count = cursor.fetchone()
print(f"Vectors stored in table v3: {count[0]}")
def display_graph(vectors_inserted, time_inserts):
"""Vykreslení grafu s časem zápisu n vektorů do tabulky."""
plt.xlabel("# vectors")
plt.ylabel("Time (sec)")
plt.plot(vectors_inserted, time_inserts, "m-", label="insertion")
# přidání legendy
plt.legend(loc="upper left")
# povolení zobrazení mřížky
plt.grid(True)
plt.savefig("insert-benchmark-3.png")
# zobrazení grafu
plt.show()
def main():
"""Vstupní bod do benchmarku."""
# připojení k databázi
connection = psycopg2.connect(
host="", port=5432, user="tester", password="123qwe", dbname="test"
)
try:
# budeme používat rozšíření pgvector
register_vector(connection)
# benchmark - naplnění tabulky s vektory
print("Vector count", "Time")
vectors_inserted = []
time_inserts = []
for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
# vytvoření tabulky s vektory
create_vector_table(connection, DIMENSIONS, print_tables=False)
t = fill_in_vector_table(connection, DIMENSIONS, vector_count)
vectors_inserted.append(vector_count)
time_inserts.append(t)
print(vector_count, t)
display_graph(vectors_inserted, time_inserts)
# zjištění počtu skutečně uložených vektorů
print_vector_count(connection)
finally:
connection.close()
# spuštění benchmarku
main()
Výsledky budou změřeny a zobrazeny jak pro databáze s relativně malým počtem vektorů, tak i pro rozsáhlejší databáze. Připomeňme si, že prozatím pracujeme s tabulkami bez „vektorových“ indexů:
Obrázek 2: Lineární závislost na počtu vektorů je patrná i u rozsáhlejších databází s miliony vektorů.
11. Vliv počtu dimenzí na rychlost zápisu
Při vytváření tabulky v databázi PostgreSQL je nutné specifikovat počet dimenzí vektorů, tj. počet jejich prvků:
CREATE TABLE IF NOT EXISTS vektoryXYZ (
id bigserial PRIMARY KEY,
embedding vector(počet_dimenzí) NOT NULL
);
V praxi se počet dimenzí volí na základě toho, jaký úkol se vlastně řeší. V případě „běžné“ grafiky by se tedy použily dvě, tři nebo čtyři dimenze, ovšem například při práci s embedded modely je počet dimenzí většinou mnohem vyšší. Dimenze vektorů se v těchto případech většinou pohybuje v rozsahu 256 až 4096 (což už jsou specializované případy); typická hodnota dimenze u embedded modelů používaných v souvislosti s LLVM je 384, 512 nebo 768. A celkový počet vektorů se pohybuje od několika set tisíc (modely pro konkrétní malou oblast) až do stovek milionů (obecné embedded modely).
Počet dimenzí bude mít pravděpodobně vliv na rychlost vyhledávání (což si ověříme příště), ale ovlivní i rychlost zápisu záznamů do databáze. Pokusíme se zjistit, jestli je tato závislost lineární nebo „horší“.
12. Benchmark a jeho výsledky
Pro změření, jaký má vliv počet dimenzí vektorů na rychlost jejich zápisu do databáze bude benchmark nepatrně upraven. Nyní budeme postupně měnit počet dimenzí, zatímco počet zapisovaných vektorů bude konstantní. Počet dimenzí budeme měnit od 16 do 1024:
# parametry benchmarku VECTOR_COUNT = 20000 MAX_DIMENSIONS = 1024 STEPS = 16
Změní se i kód benchmarku, a to konkrétně smyčka, která zjišťuje časy zápisu:
# benchmark - naplnění tabulky s vektory
print("Dimensions", "Time")
dimensions = []
time_inserts = []
for dimension in range(MAX_DIMENSIONS//STEPS, MAX_DIMENSIONS+1, MAX_DIMENSIONS//STEPS):
# vytvoření tabulky s vektory
create_vector_table(connection, dimension, print_tables=False)
t = fill_in_vector_table(connection, dimension, VECTOR_COUNT)
dimensions.append(dimension)
time_inserts.append(t)
print(dimension, t)
Upravený kód benchmarku bude vypadat následovně:
# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - průběžně se mění počet dimenzí
# - měří se časy zápisu vektorů
# - vizualizace výsledků formou grafu
from time import time
import psycopg2
from pgvector.psycopg2 import register_vector
import matplotlib.pyplot as plt
import numpy as np
# parametry benchmarku
VECTOR_COUNT = 20000
MAX_DIMENSIONS = 1024
STEPS = 16
def create_vector_table(connection, dimensions, print_tables=True):
"""Vytvoření tabulky obsahující sloupec s vektory."""
DROP_TABLE_STATEMENT = """
DROP TABLE IF EXISTS v3
"""
CREATE_TABLE_STATEMENT = """
CREATE TABLE IF NOT EXISTS v3 (
id bigserial PRIMARY KEY,
embedding vector(%s) NOT NULL
);
"""
LIST_TABLES_QUERY = """
SELECT table_schema,table_name
FROM information_schema.tables
WHERE table_schema='public'
ORDER BY table_schema,table_name;
"""
with connection.cursor() as cursor:
cursor.execute(DROP_TABLE_STATEMENT)
connection.commit()
cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
connection.commit()
cursor.execute(LIST_TABLES_QUERY)
tables = cursor.fetchall()
if print_tables:
print("Tables in database:")
for table in tables:
print(f" {table[0]}: {table[1]}")
print()
def fill_in_vector_table(connection, dimensions, vector_count):
"""Naplnění tabulky náhodnými vektory."""
t1 = time()
with connection.cursor() as cursor:
# prozatím budeme tabulku naplňovat po jednotlivých záznamech
for i in range(vector_count):
# náhodný vektor
vector = np.random.rand(dimensions).astype("float32")
cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
connection.commit()
t2 = time()
return t2 - t1
def print_vector_count(connection):
"""Tisk počtu vektorů uložených v tabulce."""
with connection.cursor() as cursor:
cursor.execute("SELECT count(*) FROM v3")
count = cursor.fetchone()
print(f"Vectors stored in table v3: {count[0]}")
def display_graph(dimensions, time_inserts):
"""Vykreslení grafu s časem zápisu n-dimenzionálních vektorů do tabulky."""
plt.xlabel("# dimensions")
plt.ylabel("Time (sec)")
plt.plot(dimensions, time_inserts, "m-", label="insertion")
# přidání legendy
plt.legend(loc="upper left")
# povolení zobrazení mřížky
plt.grid(True)
plt.savefig("insert-benchmark-4.png")
# zobrazení grafu
plt.show()
def main():
"""Vstupní bod do benchmarku."""
# připojení k databázi
connection = psycopg2.connect(
host="", port=5432, user="tester", password="123qwe", dbname="test"
)
try:
# budeme používat rozšíření pgvector
register_vector(connection)
# benchmark - naplnění tabulky s vektory
print("Dimensions", "Time")
dimensions = []
time_inserts = []
for dimension in range(MAX_DIMENSIONS//STEPS, MAX_DIMENSIONS+1, MAX_DIMENSIONS//STEPS):
# vytvoření tabulky s vektory
create_vector_table(connection, dimension, print_tables=False)
t = fill_in_vector_table(connection, dimension, VECTOR_COUNT)
dimensions.append(dimension)
time_inserts.append(t)
print(dimension, t)
display_graph(dimensions, time_inserts)
# zjištění počtu skutečně uložených vektorů
print_vector_count(connection)
finally:
connection.close()
# spuštění benchmarku
main()
Naměřené výsledky budou vypadat následovně:
13. Časy vyhledávání vektorů na základě jejich podobnosti
Nejprve si ověříme časovou složitost (konkrétně závislost času vyhledání na počtu uložených vektorů) pro tabulku, která nemá definován žádný speciální „vektorový“ index. Prvky vektorů budou datového typu float32, což sice znamená relativně velké nároky na diskový prostor zabraný tabulkami Postgresu, ovšem samotné výpočty (jsou prováděny na CPU!) jsou poměrně rychlé, protože lze k tomuto účelu využít instrukční sady SSEx či AVX s instrukcemi typu SIMD.
14. Zdrojový kód benchmarku zjišťujícího časy vyhledávání
Jak jsme si již řekli v předchozí kapitole, budeme v benchmarku zjišťovat časy vyhledávání podobných vektorů. Použijeme přitom porovnání vektorů podle metriky L2 a necháme si vyhledat maximálně pět nejbližších vektorů:
SELECT embedding FROM v3 ORDER BY embedding <-> %s::vector LIMIT 5
Funkce, která vyhledávání provádí a navíc i testuje, jestli se vrátil vyžadovaný počet nejbližších vektorů, vypadá takto:
def similarity_search(connection, dimensions, vector_count, repeat_count=1):
"""Vyhledávání na základě podobnosti vektorů."""
query = """
SELECT embedding
FROM v3
ORDER BY embedding <-> %s::vector
LIMIT 5
"""
t1 = time()
with connection.cursor() as cursor:
for i in range(repeat_count):
vector = np.random.rand(dimensions).astype("float32")
cursor.execute(query, (vector, ))
records = cursor.fetchall()
assert len(records) == min(vector_count, 5)
t2 = time()
return t2 - t1
Pochopitelně si ukážeme i celý zdrojový kód takto upraveného benchmarku:
# Rozšíření pgvector pro databázi PostgreSQL
#
# - příprava pro benchmark rozšíření pgvector
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy vyhledávání vektorů
# - vizualizace výsledků formou grafu
from time import time
import psycopg2
from pgvector.psycopg2 import register_vector
import matplotlib.pyplot as plt
import numpy as np
# parametry benchmarku
DIMENSIONS = 256
MAX_VECTOR_COUNT = 10000
STEPS = 10
REPEAT_COUNT = 100
def create_vector_table(connection, dimensions, print_tables=True):
"""Vytvoření tabulky obsahující sloupec s vektory."""
DROP_TABLE_STATEMENT = """
DROP TABLE IF EXISTS v3
"""
CREATE_TABLE_STATEMENT = """
CREATE TABLE IF NOT EXISTS v3 (
id bigserial PRIMARY KEY,
embedding vector(%s) NOT NULL
);
"""
LIST_TABLES_QUERY = """
SELECT table_schema,table_name
FROM information_schema.tables
WHERE table_schema='public'
ORDER BY table_schema,table_name;
"""
with connection.cursor() as cursor:
cursor.execute(DROP_TABLE_STATEMENT)
connection.commit()
cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
connection.commit()
cursor.execute(LIST_TABLES_QUERY)
tables = cursor.fetchall()
if print_tables:
print("Tables in database:")
for table in tables:
print(f" {table[0]}: {table[1]}")
print()
def fill_in_vector_table(connection, dimensions, vector_count):
"""Naplnění tabulky náhodnými vektory."""
t1 = time()
with connection.cursor() as cursor:
# prozatím budeme tabulku naplňovat po jednotlivých záznamech
for i in range(vector_count):
# náhodný vektor
vector = np.random.rand(dimensions).astype("float32")
cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
connection.commit()
t2 = time()
return t2 - t1
def similarity_search(connection, dimensions, vector_count, repeat_count=1):
"""Vyhledávání na základě podobnosti vektorů."""
query = """
SELECT embedding
FROM v3
ORDER BY embedding <-> %s::vector
LIMIT 5
"""
t1 = time()
with connection.cursor() as cursor:
for i in range(repeat_count):
vector = np.random.rand(dimensions).astype("float32")
cursor.execute(query, (vector, ))
records = cursor.fetchall()
assert len(records) == min(vector_count, 5)
t2 = time()
return t2 - t1
def print_vector_count(connection):
"""Tisk počtu vektorů uložených v tabulce."""
with connection.cursor() as cursor:
cursor.execute("SELECT count(*) FROM v3")
count = cursor.fetchone()
print(f"Vectors stored in table v3: {count[0]}")
def display_graph(vector_count, time_search):
"""Vykreslení grafu s časem vyhledávání n-dimenzionálních vektorů."""
plt.xlabel("# vectors")
plt.ylabel("Time (sec)")
plt.plot(vector_count, time_search, "m-", label="similarity search")
# přidání legendy
plt.legend(loc="upper left")
# povolení zobrazení mřížky
plt.grid(True)
plt.savefig("insert-benchmark-5.png")
# zobrazení grafu
plt.show()
def main():
"""Vstupní bod do benchmarku."""
# připojení k databázi
connection = psycopg2.connect(
host="", port=5432, user="tester", password="123qwe", dbname="test"
)
try:
# budeme používat rozšíření pgvector
register_vector(connection)
# benchmark - vyhledávání v tabulce s vektory
print("Vector count", "Time")
vectors_inserted = []
time_search = []
for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
# vytvoření tabulky s vektory
create_vector_table(connection, DIMENSIONS, print_tables=False)
fill_in_vector_table(connection, DIMENSIONS, vector_count)
t = similarity_search(connection, DIMENSIONS, vector_count, REPEAT_COUNT)
vectors_inserted.append(vector_count)
time_search.append(t)
print(vector_count, t)
display_graph(vectors_inserted, time_search)
# zjištění počtu skutečně uložených vektorů
finally:
connection.close()
# spuštění benchmarku
main()
15. Výsledky benchmarku
Výsledky benchmarku, který byl uveden v předchozí kapitole, jsou rozděleny na malé a středně velké databáze:
16. Metriky podporované při testování podobnosti vektorů
Prozatím jsme v benchmarku pro výpočty podobnosti (resp. vzdálenosti) vektorů používali klasickou Eukleidovskou metriku, tedy skutečnou vzdálenost bodů v prostoru (v tomto případě vzdálenost koncových bodů vektorů v prostoru). Tato metrika se taktéž označuje L2, ovšem rozšíření pgvector podporuje i další metriky, které jsou vypsány v následující tabulce:
| Operátor | Stručný popis |
|---|---|
| <-> | Eukleidovská vzdálenost |
| <#> | skalární součin (jedna z nejdůležitějších matematických operací vůbec; vrací se hodnota s otočeným znaménkem!) |
| <=> | 1 – kosinus úhlu mezi vektory (rozsah –1 až 1 za předpokladu, že jsou vektory normalizovány!) |
| <+> | Manhattanská metrika (vzdálenost při pohybu pouze po mřížce) |
| <~> | Hammingova vzdálenost (popíšeme si příště, používá se například u vektorizovaného textu) |
| <%> | Jaccardův index (opět si popíšeme příště) |
17. Změření rychlosti vyhledávání s využitím různých metrik
V dnešním posledním benchmarku otestujeme, jak se bude rychlost vyhledávání vektorů na základě jejich podobnosti lišit podle toho, jaká je použita metrika. Ve skutečnosti nemůžeme změřit všechny metriky, protože některé z nich jsou určeny například jen pro binární vektory. Budeme tedy měřit rychlost vyhledávání při využití metriky L2, výsledků skalárního součinu a výpočtu kosinu dvou vektorů (skutečně vrácené vektory budou odlišné):
def distance_function_to_operator(distance_function):
operators = {
"l2": "<->",
"inner product": "<#>",
"cosine": "<=>",
}
return operators[distance_function]
Funkce pro nalezení maximálně pěti nejbližších vektorů bude používat dotaz s proměnným operátorem:
def similarity_search(connection, dimensions, vector_count, repeat_count=1, distance_function="l2"):
"""Vyhledávání na základě podobnosti vektorů."""
operator = distance_function_to_operator(distance_function)
query = f"""
SELECT embedding
FROM v3
ORDER BY embedding {operator} %s::vector
LIMIT 5
"""
t1 = time()
with connection.cursor() as cursor:
for i in range(repeat_count):
vector = np.random.rand(dimensions).astype("float32")
cursor.execute(query, (vector, ))
records = cursor.fetchall()
assert len(records) == min(vector_count, 5)
t2 = time()
return t2 - t1
Následuje výpis celého zdrojového kódu dnešního posledního benchmarku:
# Rozšíření pgvector pro databázi PostgreSQL
#
# - vytvoření tabulky používané benchmarkem
# - je použit výchozí index
# - zápis vektorů do nově vytvořené tabulky
# - měří se časy vyhledávání vektorů podle různých kritérií
# - vizualizace výsledků formou grafu
from time import time
import psycopg2
from pgvector.psycopg2 import register_vector
import matplotlib.pyplot as plt
import numpy as np
# parametry benchmarku
DIMENSIONS = 256
MAX_VECTOR_COUNT = 10000
STEPS = 10
REPEAT_COUNT = 100
def create_vector_table(connection, dimensions, print_tables=True):
"""Vytvoření tabulky obsahující sloupec s vektory."""
DROP_TABLE_STATEMENT = """
DROP TABLE IF EXISTS v3
"""
CREATE_TABLE_STATEMENT = """
CREATE TABLE IF NOT EXISTS v3 (
id bigserial PRIMARY KEY,
embedding vector(%s) NOT NULL
);
"""
LIST_TABLES_QUERY = """
SELECT table_schema,table_name
FROM information_schema.tables
WHERE table_schema='public'
ORDER BY table_schema,table_name;
"""
with connection.cursor() as cursor:
cursor.execute(DROP_TABLE_STATEMENT)
connection.commit()
cursor.execute(CREATE_TABLE_STATEMENT, (dimensions,))
connection.commit()
cursor.execute(LIST_TABLES_QUERY)
tables = cursor.fetchall()
if print_tables:
print("Tables in database:")
for table in tables:
print(f" {table[0]}: {table[1]}")
print()
def fill_in_vector_table(connection, dimensions, vector_count):
"""Naplnění tabulky náhodnými vektory."""
t1 = time()
with connection.cursor() as cursor:
# prozatím budeme tabulku naplňovat po jednotlivých záznamech
for i in range(vector_count):
# náhodný vektor
vector = np.random.rand(dimensions).astype("float32")
cursor.execute("INSERT INTO v3 (embedding) VALUES (%s)", (vector,))
connection.commit()
t2 = time()
return t2 - t1
def distance_function_to_operator(distance_function):
operators = {
"l1": "<+>",
"l2": "<->",
"inner product": "<#>",
"cosine": "<=>",
}
return operators[distance_function]
def similarity_search(connection, dimensions, vector_count, repeat_count=1, distance_function="l2"):
"""Vyhledávání na základě podobnosti vektorů."""
operator = distance_function_to_operator(distance_function)
query = f"""
SELECT embedding
FROM v3
ORDER BY embedding {operator} %s::vector
LIMIT 5
"""
t1 = time()
with connection.cursor() as cursor:
for i in range(repeat_count):
vector = np.random.rand(dimensions).astype("float32")
cursor.execute(query, (vector, ))
records = cursor.fetchall()
assert len(records) == min(vector_count, 5)
t2 = time()
return t2 - t1
def print_vector_count(connection):
"""Tisk počtu vektorů uložených v tabulce."""
with connection.cursor() as cursor:
cursor.execute("SELECT count(*) FROM v3")
count = cursor.fetchone()
print(f"Vectors stored in table v3: {count[0]}")
def display_graph(distance_functions, vector_count, time_search):
"""Vykreslení grafu s časy vyhledávání n-dimenzionálních vektorů."""
plt.xlabel("# vectors")
plt.ylabel("Time (sec)")
for distance_function in distance_functions:
plt.plot(vector_count, time_search[distance_function], label=distance_function)
# přidání legendy
plt.legend(loc="upper left")
# povolení zobrazení mřížky
plt.grid(True)
plt.savefig("insert-benchmark-6.png")
# zobrazení grafu
plt.show()
def main():
"""Vstupní bod do benchmarku."""
# připojení k databázi
connection = psycopg2.connect(
host="", port=5432, user="tester", password="123qwe", dbname="test"
)
distance_functions = (
#"l1",
"l2",
"cosine",
"inner product",
)
try:
# budeme používat rozšíření pgvector
register_vector(connection)
# benchmark - vyhledávání v tabulce s vektory
print("Vector count", "Function", "Time")
vectors_inserted = []
time_search = {}
# příprava slovníku seznamů
for distance_function in distance_functions:
time_search[distance_function] = []
for vector_count in range(0, MAX_VECTOR_COUNT+1, MAX_VECTOR_COUNT//STEPS):
# vytvoření tabulky s vektory
create_vector_table(connection, DIMENSIONS, print_tables=False)
fill_in_vector_table(connection, DIMENSIONS, vector_count)
for distance_function in distance_functions:
t = similarity_search(connection, DIMENSIONS, vector_count, REPEAT_COUNT, distance_function)
time_search[distance_function].append(t)
print(vector_count, distance_function, t)
vectors_inserted.append(vector_count)
display_graph(distance_functions, vectors_inserted, time_search)
finally:
connection.close()
# spuštění benchmarku
main()
18. Výsledky benchmarku
Opět se podívejme na výsledky benchmarku, který ukazuje, jak se liší (nebo naopak neliší) časy porovnávání vektorů s využitím různých metrik. Výsledky jsou rozděleny na „malé“ a středně velké až rozsáhlé databáze:
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python s nainstalovanými balíčky psycopg2 a pgvector byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V tabulce zobrazené níže jsou odkazy na jednotlivé příklady:
20. Odkazy na Internetu
- pgvector: vektorová databáze postavená na Postgresu
https://www.root.cz/clanky/pgvector-vektorova-databaze-postavena-na-postgresu/ - Rozšíření PostgreSQL jménem pgvector, embedding a sémantické vyhledávání (1. část)
https://www.root.cz/clanky/rozsireni-postgresql-jmenem-pgvector-embedding-a-semanticke-vyhledavani-1-cast/ - FAISS: knihovna pro rychlé a efektivní vyhledávání podobných vektorů
https://www.root.cz/clanky/faiss-knihovna-pro-rychle-a-efektivni-vyhledavani-podobnych-vektoru/ - FAISS: knihovna pro rychlé a efektivní vyhledávání podobných vektorů (2. část)
https://www.root.cz/clanky/faiss-knihovna-pro-rychle-a-efektivni-vyhledavani-podobnych-vektoru-2-cast/ - Knihovna FAISS a embedding: základ jazykových modelů
https://www.root.cz/clanky/knihovna-faiss-a-embedding-zaklad-jazykovych-modelu/ - Knihovna FAISS a embedding: základ jazykových modelů (2. část)
https://www.root.cz/clanky/knihovna-faiss-a-embedding-zaklad-jazykovych-modelu-2-cast/ - FAISS (Facebook AI Similarity Search)
https://en.wikipedia.org/wiki/FAISS - FAISS documentation
https://faiss.ai/ - Introduction to Facebook AI Similarity Search (Faiss)
https://www.pinecone.io/learn/series/faiss/faiss-tutorial/ - Faiss: Efficient Similarity Search and Clustering of Dense Vectors
https://medium.com/@pankaj_pandey/faiss-efficient-similarity-search-and-clustering-of-dense-vectors-dace1df1e235 - Cosine Distance vs Dot Product vs Euclidean in vector similarity search
https://medium.com/data-science-collective/cosine-distance-vs-dot-product-vs-euclidean-in-vector-similarity-search-227a6db32edb - F16C
https://en.wikipedia.org/wiki/F16C - FP16 (AVX-512)
https://en.wikipedia.org/wiki/AVX-512#FP16 - Top 8 Vector Databases in 2025: Features, Use Cases, and Comparisons
https://synapsefabric.com/top-8-vector-databases-in-2025-features-use-cases-and-comparisons/ - Is FAISS a Vector Database? Complete Guide
https://mljourney.com/is-faiss-a-vector-database-complete-guide/ - Vector database
https://en.wikipedia.org/wiki/Vector_database - Similarity search
https://en.wikipedia.org/wiki/Similarity_search - Nearest neighbor search
https://en.wikipedia.org/wiki/Nearest_neighbor_search#Approximation_methods - Decoding Similarity Search with FAISS: A Practical Approach
https://www.luminis.eu/blog/decoding-similarity-search-with-faiss-a-practical-approach/ - MetricType and distances
https://github.com/facebookresearch/faiss/wiki/MetricType-and-distances - RAG – Retrieval-augmented generation
https://en.wikipedia.org/wiki/Retrieval-augmented_generation - pgvector na GitHubu
https://github.com/pgvector/pgvector - Why we replaced Pinecone with PGVector
https://www.confident-ai.com/blog/why-we-replaced-pinecone-with-pgvector - PostgreSQL as VectorDB – Beginner Tutorial
https://www.youtube.com/watch?v=Ff3tJ4pJEa4 - What is a Vector Database? (neobsahuje odpověď na otázku v titulku :-)
https://www.youtube.com/watch?v=t9IDoenf-lo - PGVector: Turn PostgreSQL Into A Vector Database
https://www.youtube.com/watch?v=j1QcPSLj7u0 - Milvus
https://milvus.io/ - Vector Databases simply explained! (Embeddings & Indexes)
https://www.youtube.com/watch?v=dN0lsF2cvm4 - Vector databases are so hot right now. WTF are they?
https://www.youtube.com/watch?v=klTvEwg3oJ4 - Step-by-Step Guide to Installing “pgvector” and Loading Data in PostgreSQL
https://medium.com/@besttechreads/step-by-step-guide-to-installing-pgvector-and-loading-data-in-postgresql-f2cffb5dec43 - Best 17 Vector Databases for 2025
https://lakefs.io/blog/12-vector-databases-2023/ - Top 15 Vector Databases that You Must Try in 2025
https://www.geeksforgeeks.org/top-vector-databases/ - Picking a vector database: a comparison and guide for 2023
https://benchmark.vectorview.ai/vectordbs.html - Top 9 Vector Databases as of Feburary 2025
https://www.shakudo.io/blog/top-9-vector-databases - What is a vector database?
https://www.ibm.com/think/topics/vector-database - SQL injection
https://en.wikipedia.org/wiki/SQL_injection - Cosine similarity
https://en.wikipedia.org/wiki/Cosine_similarity - Euclidean distance
https://en.wikipedia.org/wiki/Euclidean_distance - Dot product
https://en.wikipedia.org/wiki/Dot_product - Hammingova vzdálenost
https://cs.wikipedia.org/wiki/Hammingova_vzd%C3%A1lenost - Jaccard index
https://en.wikipedia.org/wiki/Jaccard_index - Manhattanská metrika
https://cs.wikipedia.org/wiki/Manhattansk%C3%A1_metrika - FAISS (Facebook AI Similarity Search)
https://en.wikipedia.org/wiki/FAISS - FAISS documentation
https://faiss.ai/ - Introduction to Facebook AI Similarity Search (Faiss)
https://www.pinecone.io/learn/series/faiss/faiss-tutorial/ - Faiss: Efficient Similarity Search and Clustering of Dense Vectors
https://medium.com/@pankaj_pandey/faiss-efficient-similarity-search-and-clustering-of-dense-vectors-dace1df1e235 - Cosine Distance vs Dot Product vs Euclidean in vector similarity search
https://medium.com/data-science-collective/cosine-distance-vs-dot-product-vs-euclidean-in-vector-similarity-search-227a6db32edb - F16C
https://en.wikipedia.org/wiki/F16C - FP16 (AVX-512)
https://en.wikipedia.org/wiki/AVX-512#FP16 - Top 8 Vector Databases in 2025: Features, Use Cases, and Comparisons
https://synapsefabric.com/top-8-vector-databases-in-2025-features-use-cases-and-comparisons/ - Is FAISS a Vector Database? Complete Guide
https://mljourney.com/is-faiss-a-vector-database-complete-guide/ - FAISS and sentence-transformers in 5 Minutes
https://www.stephendiehl.com/posts/faiss/ - Sentence Transformer: Quickstart
https://sbert.net/docs/quickstart.html#sentence-transformer - Sentence Transformers: Embeddings, Retrieval, and Reranking
https://pypi.org/project/sentence-transformers/ - uv
https://docs.astral.sh/uv/ - A Gentle Introduction to Retrieval Augmented Generation (RAG)
https://wandb.ai/cosmo3769/RAG/reports/A-Gentle-Introduction-to-Retrieval-Augmented-Generation-RAG---Vmlldzo1MjM4Mjk1 - The Beginner’s Guide to Text Embeddings
https://www.deepset.ai/blog/the-beginners-guide-to-text-embeddings - What are Word Embeddings?
https://www.youtube.com/watch?v=wgfSDrqYMJ4 - How to choose an embedding model
https://www.youtube.com/watch?v=djp4205tHGU - What is a Vector Database? Powering Semantic Search & AI Applications
https://www.youtube.com/watch?v=gl1r1XV0SLw - How do Sentence Transformers differ from traditional word embedding models like Word2Vec or GloVe?
https://zilliz.com/ai-faq/how-do-sentence-transformers-differ-from-traditional-word-embedding-models-like-word2vec-or-glove - BERT (language model)
https://en.wikipedia.org/wiki/BERT_(language_model) - Levenštejnova vzdálenost
https://cs.wikipedia.org/wiki/Leven%C5%A1tejnova_vzd%C3%A1lenost - pgvector Tutorial: Integrate Vector Search into PostgreSQL
https://www.datacamp.com/tutorial/pgvector-tutorial - pgvectorbench
https://github.com/pgvectorBench/pgvectorBench - pgvector 0.4.0 performance
https://supabase.com/blog/pgvector-performance - IVFFlat (Inverted File Flat) Index
https://skyzh.github.io/write-you-a-vector-db/cpp-05-ivfflat.html - HNSW (Hierarchical Navigable Small Worlds) Index
https://skyzh.github.io/write-you-a-vector-db/cpp-06–02-hnsw.html - Vector Indexing: Your Guide to Understanding and Implementation
https://www.teradata.com/insights/ai-and-machine-learning/what-is-vector-index - Vector Indexing Explained: Everything You Need to Know
https://www.datastax.com/guides/what-is-a-vector-index - Voroného diagram
https://cs.wikipedia.org/wiki/Voron%C3%A9ho_diagram - Matplotlib Home Page
http://matplotlib.org/ - matplotlib (Wikipedia)
https://en.wikipedia.org/wiki/Matplotlib






