Oracle Database 23ai wprowadza natywną obsługę wektorów, eliminując potrzebę wdrażania osobnych baz jak Pinecone czy Milvus. Jako DBA z dwudziestoletnim doświadczeniem pokazuję, jak zbudować profesjonalny system wyszukiwania semantycznego wykorzystując wyłącznie Oracle.

Oracle AI Vector Search: kompletny przewodnik po wyszukiwaniu semantycznym bez zewnętrznej bazy wektorowej
Kluczowe punkty
  • Typ danych VECTOR w Oracle 23ai umożliwia przechowywanie embeddingów bezpośrednio w bazie relacyjnej
  • Wyszukiwanie semantyczne opiera się na metrykach podobieństwa: COSINE, DOT PRODUCT i EUCLIDEAN DISTANCE
  • Oracle może pełnić rolę kompletnego silnika RAG dla aplikacji wykorzystujących modele językowe
  • Indeksy wektorowe HNSW i IVF drastycznie przyspieszają wyszukiwanie w dużych zbiorach dokumentów

Ewolucja wyszukiwania: od LIKE do semantyki

Przez ponad dwie dekady mojej pracy z bazami danych Oracle obserwowałem kolejne rewolucje w sposobie wyszukiwania informacji. Zaczynaliśmy od prostych zapytań z operatorem LIKE i wyrażeniami regularnymi. Potem przyszedł Oracle Text z indeksami CONTEXT i CTXCAT, oferujący wyszukiwanie pełnotekstowe z obsługą języka naturalnego. Dziś stoimy u progu kolejnej transformacji; Oracle Database 23ai wprowadza natywne wsparcie dla danych wektorowych, otwierając drzwi do wyszukiwania semantycznego.

Różnica między wyszukiwaniem tekstowym a semantycznym jest fundamentalna. Klasyczne zapytanie SELECT * FROM dokumenty WHERE tresc LIKE '%polityka bezpieczeństwa%' znajdzie tylko dokumenty zawierające dokładnie tę frazę. Wyszukiwanie semantyczne odnajdzie również dokumenty mówiące o procedurach ochrony danych, zasadach cyberbezpieczeństwa czy standardach ISO 27001, ponieważ rozumie znaczenie, a nie tylko dopasowuje znaki.

Ta zmiana paradygmatu ma ogromne konsekwencje dla architektury systemów informatycznych. Do niedawna budowa aplikacji wykorzystujących wyszukiwanie semantyczne wymagała wdrożenia dedykowanej bazy wektorowej: Pinecone, Milvus, Weaviate czy Chroma. Oznaczało to dodatkową infrastrukturę, synchronizację danych między systemami, nowe punkty awarii i zwiększone koszty utrzymania. Oracle 23ai eliminuje tę złożoność.

Embeddingi: matematyczna reprezentacja znaczenia

Zanim przejdziemy do szczegółów implementacji w Oracle, musimy zrozumieć fundamentalny koncept embeddingów. Embedding to reprezentacja numeryczna tekstu (lub innego typu danych) w postaci wektora o stałej liczbie wymiarów. Współczesne modele językowe potrafią przekształcić dowolny tekst w wektor liczb, który koduje jego semantyczne znaczenie.

Wyobraźmy sobie przestrzeń wielowymiarową (typowo od 384 do 4096 wymiarów), w której każdy dokument zajmuje określony punkt. Dokumenty o podobnym znaczeniu znajdują się blisko siebie, podczas gdy dokumenty o odmiennej tematyce są od siebie oddalone. To właśnie ta właściwość umożliwia wyszukiwanie semantyczne; zamiast porównywać słowa, porównujemy położenie punktów w przestrzeni wektorowej.

Proces generowania embeddingów nazywamy enkodowaniem. Popularne modele to między innymi: text-embedding-ada-002 od OpenAI (1536 wymiarów), all-MiniLM-L6-v2 (384 wymiary) czy multilingual-e5-large (1024 wymiary). Wybór modelu ma bezpośredni wpływ na jakość wyszukiwania oraz wymagania dotyczące przestrzeni dyskowej i wydajności.

W mojej praktyce konsultacyjnej wielokrotnie obserwowałem, jak firmy wdrażały skomplikowane architektury z oddzielnymi bazami wektorowymi, tracąc przy tym spójność transakcyjną i mnożąc punkty awarii. Oracle AI Vector Search to powrót do fundamentalnej zasady: dane powinny być tam, gdzie logika biznesowa.

Typ danych VECTOR w Oracle Database 23ai

Oracle Database 23ai wprowadza nowy natywny typ danych VECTOR, zaprojektowany specjalnie do przechowywania embeddingów. Składnia definicji kolumny wektorowej jest intuicyjna i elastyczna:

CREATE TABLE dokumenty (id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tytul VARCHAR2(500), tresc CLOB, embedding VECTOR(1536, FLOAT32), data_utworzenia TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

Parametry typu VECTOR określają wymiarowość (liczbę elementów wektora) oraz precyzję numeryczną. Oracle obsługuje trzy formaty przechowywania: FLOAT32 (domyślny, 4 bajty na wymiar), FLOAT64 (8 bajtów, większa precyzja) oraz INT8 (1 bajt, skwantyzowana reprezentacja). Wybór formatu to kompromis między dokładnością a wykorzystaniem przestrzeni dyskowej.

Dla wektora 1536-wymiarowego w formacie FLOAT32 pojedynczy embedding zajmuje około 6 KB. Przy milionach dokumentów przekłada się to na dziesiątki gigabajtów samych embeddingów. Format INT8 redukuje to czterokrotnie, ale wymaga kwantyzacji, która może nieznacznie obniżyć jakość wyszukiwania.

Oracle pozwala również na definiowanie kolumn wektorowych bez określania wymiarowości: VECTOR(*). Jest to przydatne w scenariuszach, gdzie różne dokumenty mogą mieć embeddingi z różnych modeli. Jednak w środowiskach produkcyjnych zdecydowanie rekomenduję jawne określanie wymiarowości; zapobiega to przypadkowemu wstawieniu wektorów o nieprawidłowej długości.

Projektowanie repozytorium wiedzy

Budowa efektywnego systemu wyszukiwania semantycznego wymaga przemyślanej architektury danych. Typowa struktura obejmuje kilka powiązanych tabel:

Tabela źródeł definiuje pochodzenie dokumentów: CREATE TABLE zrodla_wiedzy (id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, nazwa VARCHAR2(200) NOT NULL, typ VARCHAR2(50) NOT NULL, url_bazowy VARCHAR2(1000), aktywne CHAR(1) DEFAULT 'T' CHECK (aktywne IN ('T','N')));

Tabela dokumentów przechowuje oryginalne treści: CREATE TABLE dokumenty (id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, zrodlo_id NUMBER REFERENCES zrodla_wiedzy(id), tytul VARCHAR2(500), tresc CLOB, metadata JSON, data_indeksacji TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

Tabela fragmentów zawiera podzielone dokumenty wraz z embeddingami: CREATE TABLE fragmenty_dokumentow (id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, dokument_id NUMBER REFERENCES dokumenty(id), numer_fragmentu NUMBER NOT NULL, tekst_fragmentu VARCHAR2(4000), embedding VECTOR(1536, FLOAT32), CONSTRAINT uk_fragment UNIQUE (dokument_id, numer_fragmentu));

Dlaczego dzielimy dokumenty na fragmenty? Modele generujące embeddingi mają ograniczenia co do długości przetwarzanego tekstu; typowo od 512 do 8192 tokenów. Ponadto mniejsze fragmenty dają precyzyjniejsze wyniki wyszukiwania. Dokument stustronicowy jako całość będzie miał uśredniony embedding, podczas gdy podział na akapity pozwala odnaleźć konkretny fragment odpowiadający na pytanie użytkownika.

Strategia fragmentacji (chunking) ma kluczowe znaczenie dla jakości systemu. Najprostsze podejście to podział na fragmenty o stałej liczbie znaków lub tokenów z określonym nakładaniem się (overlap). Bardziej zaawansowane metody uwzględniają strukturę dokumentu: nagłówki, akapity, listy. W Oracle możemy wykorzystać pakiet DBMS_VECTOR_CHAIN do automatycznej fragmentacji.

Metryki podobieństwa: wybór właściwej miary odległości

Wyszukiwanie semantyczne polega na znajdowaniu wektorów najbliższych do wektora zapytania. Oracle obsługuje trzy podstawowe metryki podobieństwa, każda z własnymi charakterystykami:

COSINE DISTANCE mierzy kąt między wektorami, ignorując ich długość. Wartości mieszczą się w zakresie od 0 (identyczne kierunki) do 2 (przeciwne kierunki). Jest to najczęściej stosowana metryka w wyszukiwaniu tekstowym, ponieważ normalizuje wpływ długości dokumentu.

DOT PRODUCT (iloczyn skalarny) uwzględnia zarówno kierunek, jak i długość wektorów. Wyższe wartości oznaczają większe podobieństwo. Metryka ta sprawdza się, gdy embeddingi są już znormalizowane lub gdy długość wektora niesie istotną informację.

EUCLIDEAN DISTANCE to klasyczna odległość euklidesowa w przestrzeni wielowymiarowej. Mniejsze wartości oznaczają większe podobieństwo. Intuicyjna, ale wrażliwa na różnice w skali wymiarów.

Podstawowe zapytanie wyszukujące podobne dokumenty wykorzystuje klauzulę ORDER BY z funkcją VECTOR_DISTANCE:

SELECT d.tytul, f.tekst_fragmentu, VECTOR_DISTANCE(f.embedding, :wektor_zapytania, COSINE) AS odleglosc FROM fragmenty_dokumentow f JOIN dokumenty d ON f.dokument_id = d.id ORDER BY odleglosc FETCH FIRST 10 ROWS ONLY;

Oracle 23ai wprowadza również uproszczoną składnię z operatorem podobieństwa, ale dla pełnej kontroli nad metryką rekomenduję jawne użycie VECTOR_DISTANCE.

Indeksowanie wektorów: HNSW vs IVF

Bez indeksów wyszukiwanie wektorowe wymaga porównania zapytania z każdym wektorem w tabeli. Dla miliona dokumentów oznacza to milion obliczeń odległości przy każdym zapytaniu. Oracle oferuje dwa typy indeksów wektorowych optymalizujących ten proces.

Indeks HNSW (Hierarchical Navigable Small World) buduje wielopoziomowy graf, w którym nawigacja zaczyna się od górnych warstw (mała liczba węzłów, duże skoki) i schodzi do dolnych (duża precyzja). Tworzenie indeksu: CREATE VECTOR INDEX idx_fragmenty_hnsw ON fragmenty_dokumentow(embedding) ORGANIZATION INMEMORY NEIGHBOR GRAPH DISTANCE COSINE WITH TARGET ACCURACY 95;

Parametr TARGET ACCURACY określa kompromis między precyzją a szybkością. Wartość 95% oznacza, że indeks zwróci 95% tych samych wyników co pełne przeszukanie, ale znacznie szybciej. Dla zastosowań krytycznych można ustawić 99%, dla mniej wymagających nawet 90%.

Indeks IVF (Inverted File) dzieli przestrzeń wektorową na klastry i podczas wyszukiwania sprawdza tylko wybrane klastry. Jest szybszy w budowie niż HNSW, ale może dawać nieco gorsze wyniki przy małej liczbie przeszukiwanych klastrów: CREATE VECTOR INDEX idx_fragmenty_ivf ON fragmenty_dokumentow(embedding) ORGANIZATION NEIGHBOR PARTITIONS DISTANCE COSINE WITH TARGET ACCURACY 90;

W mojej praktyce HNSW sprawdza się lepiej dla zbiorów do kilkudziesięciu milionów wektorów, podczas gdy IVF może być preferowany dla bardzo dużych zbiorów lub gdy priorytetem jest szybkość budowy indeksu.

Oracle jako silnik RAG

Retrieval Augmented Generation to architektura, w której model językowy otrzymuje kontekst z zewnętrznych źródeł przed wygenerowaniem odpowiedzi. Oracle Database 23ai doskonale sprawdza się jako backend dla systemów RAG, łącząc w jednym miejscu przechowywanie dokumentów, indeksowanie wektorowe i wyszukiwanie semantyczne.

Typowy przepływ RAG z Oracle wygląda następująco: użytkownik zadaje pytanie, aplikacja generuje embedding pytania (przez API modelu), wykonuje wyszukiwanie wektorowe w Oracle, pobiera N najbardziej podobnych fragmentów, konstruuje prompt zawierający pytanie i kontekst, wysyła prompt do modelu generatywnego i zwraca odpowiedź użytkownikowi.

Oracle może realizować kroki od drugiego do piątego w pojedynczym wywołaniu dzięki pakietowi DBMS_VECTOR_CHAIN. Przykładowa procedura PL/SQL implementująca kompletny przepływ RAG:

CREATE OR REPLACE FUNCTION znajdz_kontekst(p_pytanie VARCHAR2, p_limit NUMBER DEFAULT 5) RETURN CLOB IS v_embedding VECTOR(1536, FLOAT32); v_kontekst CLOB := ''; BEGIN v_embedding := DBMS_VECTOR.UTL_TO_EMBEDDING(p_pytanie, JSON('{"provider": "ocigenai", "credential_name": "OCI_CRED", "model": "cohere.embed-multilingual-v3.0"}')); FOR rec IN (SELECT tekst_fragmentu FROM fragmenty_dokumentow ORDER BY VECTOR_DISTANCE(embedding, v_embedding, COSINE) FETCH FIRST p_limit ROWS ONLY) LOOP v_kontekst := v_kontekst || rec.tekst_fragmentu || CHR(10) || CHR(10); END LOOP; RETURN v_kontekst; END;

Ta funkcja może być następnie wykorzystana w konstrukcji promptu dla modelu generatywnego, również bezpośrednio z poziomu Oracle poprzez DBMS_VECTOR_CHAIN.UTL_TO_GENERATE_TEXT.

Integracja z zewnętrznymi modelami AI

Oracle 23ai oferuje elastyczne opcje generowania embeddingów. Możemy wykorzystać modele Oracle Cloud Infrastructure Generative AI, OpenAI, Cohere lub dowolne inne poprzez REST API.

Konfiguracja połączenia z OCI GenAI wymaga utworzenia credential: BEGIN DBMS_CLOUD.CREATE_CREDENTIAL(credential_name => 'OCI_CRED', user_ocid => 'ocid1.user.oc1..xxx', tenancy_ocid => 'ocid1.tenancy.oc1..xxx', private_key => 'MIIEvgIBADANBgkqh...', fingerprint => 'aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99'); END;

Dla OpenAI proces jest podobny, ale wykorzystujemy klucz API: BEGIN DBMS_CLOUD.CREATE_CREDENTIAL(credential_name => 'OPENAI_CRED', username => 'OPENAI', password => 'sk-xxxxxxxxxxxxx'); END;

Po skonfigurowaniu credentials możemy generować embeddingi bezpośrednio w SQL: SELECT DBMS_VECTOR.UTL_TO_EMBEDDING('Tekst do zakodowania', JSON('{"provider": "openai", "credential_name": "OPENAI_CRED", "model": "text-embedding-3-small"}')) AS embedding FROM DUAL;

Dla scenariuszy wymagających niskich opóźnień lub pracy offline Oracle wspiera również ładowanie modeli ONNX bezpośrednio do bazy danych. Po zaimportowaniu modelu embeddingi generowane są lokalnie, bez wywołań sieciowych.

Optymalizacja wydajności

Wydajność systemu wyszukiwania wektorowego zależy od wielu czynników. Na podstawie moich doświadczeń z wdrożeniami produkcyjnymi przedstawiam kluczowe rekomendacje.

Pamięć jest krytyczna dla wydajności indeksów HNSW. Indeks powinien mieścić się w pamięci RAM; dla miliona wektorów 1536-wymiarowych to około 6-8 GB samych danych plus struktury indeksu. Parametr VECTOR_MEMORY_SIZE w init.ora kontroluje alokację pamięci dla operacji wektorowych.

Partycjonowanie tabel wektorowych poprawia zarówno wydajność zapytań, jak i zarządzalność. Możemy partycjonować po źródle dokumentów, dacie lub kategorii, a następnie wykorzystać partition pruning w zapytaniach: SELECT * FROM fragmenty_dokumentow PARTITION (p_procedury_2024) ORDER BY VECTOR_DISTANCE(embedding, :v, COSINE) FETCH FIRST 10 ROWS ONLY;

Batch processing przy ładowaniu embeddingów znacząco redukuje narzut. Zamiast pojedynczych insertów wykorzystujemy FORALL lub INSERT ALL. Dla masowych operacji rozważamy direct path load z wyłączonym logowaniem.

Monitorowanie wydajności zapytań wektorowych wymaga analizy planów wykonania. Sprawdzamy, czy indeks jest wykorzystywany: EXPLAIN PLAN FOR SELECT... oraz SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY). Operacja VECTOR INDEX SCAN potwierdza użycie indeksu.

Praktyczny projekt: wyszukiwarka dokumentacji technicznej

Zbudujmy kompletny system wyszukiwania w dokumentacji technicznej. Zakładamy, że mamy zbiór dokumentów PDF z procedurami IT, które chcemy udostępnić przez interfejs konwersacyjny.

Krok pierwszy to ekstrakcja tekstu z PDF. Oracle nie posiada natywnej funkcji parsowania PDF, więc wykorzystujemy zewnętrzną bibliotekę (np. Apache Tika) lub preprocesor w Pythonie. Wyekstrahowany tekst ładujemy do tabeli dokumenty.

Krok drugi to fragmentacja dokumentów. Tworzymy procedurę dzielącą dokumenty na fragmenty po około 500 tokenów z nakładaniem 50 tokenów:

CREATE OR REPLACE PROCEDURE fragmentuj_dokument(p_dokument_id NUMBER) IS v_tresc CLOB; v_fragment VARCHAR2(4000); v_pozycja NUMBER := 1; v_numer NUMBER := 1; v_rozmiar NUMBER := 2000; v_nakladanie NUMBER := 200; BEGIN SELECT tresc INTO v_tresc FROM dokumenty WHERE id = p_dokument_id; WHILE v_pozycja < DBMS_LOB.GETLENGTH(v_tresc) LOOP v_fragment := DBMS_LOB.SUBSTR(v_tresc, v_rozmiar, v_pozycja); INSERT INTO fragmenty_dokumentow (dokument_id, numer_fragmentu, tekst_fragmentu) VALUES (p_dokument_id, v_numer, v_fragment); v_pozycja := v_pozycja + v_rozmiar - v_nakladanie; v_numer := v_numer + 1; END LOOP; COMMIT; END;

Krok trzeci to generowanie embeddingów. Aktualizujemy fragmenty o wektory: UPDATE fragmenty_dokumentow SET embedding = DBMS_VECTOR.UTL_TO_EMBEDDING(tekst_fragmentu, JSON('{"provider": "ocigenai", "credential_name": "OCI_CRED", "model": "cohere.embed-multilingual-v3.0"}')) WHERE embedding IS NULL;

Krok czwarty to utworzenie indeksu wektorowego: CREATE VECTOR INDEX idx_dok_tech ON fragmenty_dokumentow(embedding) ORGANIZATION INMEMORY NEIGHBOR GRAPH DISTANCE COSINE WITH TARGET ACCURACY 95;

Krok piąty to implementacja funkcji wyszukującej z formatowaniem wyników: CREATE OR REPLACE FUNCTION szukaj_w_dokumentacji(p_pytanie VARCHAR2) RETURN SYS_REFCURSOR IS v_cursor SYS_REFCURSOR; v_emb VECTOR(1536, FLOAT32); BEGIN v_emb := DBMS_VECTOR.UTL_TO_EMBEDDING(p_pytanie, JSON('{"provider": "ocigenai", "credential_name": "OCI_CRED", "model": "cohere.embed-multilingual-v3.0"}')); OPEN v_cursor FOR SELECT d.tytul, f.tekst_fragmentu, ROUND(1 - VECTOR_DISTANCE(f.embedding, v_emb, COSINE), 3) AS podobienstwo FROM fragmenty_dokumentow f JOIN dokumenty d ON f.dokument_id = d.id ORDER BY VECTOR_DISTANCE(f.embedding, v_emb, COSINE) FETCH FIRST 5 ROWS ONLY; RETURN v_cursor; END;

System jest gotowy do integracji z frontendem lub chatbotem. Zapytanie szukaj_w_dokumentacji('Jak skonfigurować backup RMAN') zwróci najbardziej relevantne fragmenty dokumentacji, nawet jeśli nie zawierają dokładnie tych słów.

Bezpieczeństwo i governance

Przechowywanie embeddingów w Oracle Database daje nam pełne możliwości kontroli dostępu. Możemy wykorzystać Virtual Private Database do ograniczenia wyników wyszukiwania do dokumentów, do których użytkownik ma uprawnienia. Label Security pozwala na klasyfikację dokumentów według poziomów poufności.

Embeddingi same w sobie nie zawierają czytelnego tekstu, ale teoretycznie mogą być wykorzystane do częściowej rekonstrukcji treści. Dla dokumentów o najwyższej klasyfikacji rozważamy przechowywanie embeddingów w osobnym schemacie z ograniczonym dostępem lub szyfrowanie na poziomie kolumny (TDE).

Audytowanie dostępu do danych wektorowych działa identycznie jak dla innych typów danych. Unified Audit pozwala śledzić, kto i kiedy wykonywał wyszukiwania semantyczne.

Oracle AI Vector Search w wersji 23ai to przełomowa funkcjonalność eliminująca potrzebę utrzymywania oddzielnych baz wektorowych. Dla organizacji już korzystających z Oracle Database oznacza to uproszczenie architektury, zachowanie spójności transakcyjnej i redukcję kosztów operacyjnych. Natywna integracja z danymi relacyjnymi otwiera nowe możliwości budowy inteligentnych aplikacji; od wyszukiwarek semantycznych, przez systemy RAG, po zaawansowane analizy podobieństwa w dowolnej domenie biznesowej.