SQL Server 2025 ile Vector Search: PDF Koleksiyonunda Toplu Embedding ve DiskANN İndeksi


1. Başlıyoruz

Önceki bölümde Vector Search’ün ne olduğunu, VECTOR veri tipini ve Azure OpenAI ile ilk embedding’imizi nasıl ürettiğimizi beraber yapmıştık. O bölümde çalıştırdığımız örnekler elle girdiğimiz üç-beş satıra dayanıyordu. Bu makalede ise senaryomuzu gerçek hayata yaklaştırıyoruz: elimizde bir dizi şirket IT dokümanı (PDF) var ve bunu “sorabileceğimiz” bir veritabanına dönüştürmek istiyoruz.

Bu bölümde üç şey öğreneceğiz:

(1) PDF’lerden metin çıkarıp anlamlı parçalara (chunk) nasıl bölünür
(2) Python ile toplu embedding pipeline’ı nasıl kurulur ve SQL Server’a veri nasıl yüklenir
(3) DiskANN indeksini ne zaman ve nasıl kurarsınız, index’li ve index’siz performans farkı nedir.

Workshop tadını sürdürüyorum. Elinizde önceki makaleden kalan VectorDemo veritabanı varsa onu kullanabilirsiniz; yeni başlıyorsanız o bölüme dönmenizi tavsiye ederim.

2. Senaryo: IT Dokümantasyon Asistanı

Hayali bir şirketimiz var. Bu şirketin IT departmanında yıllar içinde birikmiş 200 civarı PDF dokümanı var — SOP’lar, runbook’lar, incident post-mortem’leri, best practice rehberleri. Mevcut durumda biri “son yedek başarısız olursa ne yapmalıyım?” diye sorduğunda, cevap bu 200 PDF’in içinde bir yerde saklı ama kimse tam olarak nerede olduğunu bilmiyor.

Hedefimiz: bir DBA oturup T-SQL ile bir soru sorduğunda, SQL Server’ın ilgili doküman parçalarını anlamca en yakın olanlardan çekip listelemesi. Yani klasik bir enterprise knowledge base, ama arka ucu olarak ayrı bir vektör veritabanı değil, senin zaten yönettiğin, yedeklediğin SQL Server’ı kullanıyor.

3. PDF’leri Chunk’lama Stratejisi

Bir PDF’i olduğu gibi embedding’e vermek işe yaramaz. Çünkü embedding modelleri belirli bir token limitine sahip (text-embedding-3-small için 8191 token). Ayrıca bir doküman embedding’i tek başına 40 sayfayı temsil ettiğinde, benzerlik sorgusu “bu dokümanda bir yerlerde alakalı bir kısım var” der ama tam olarak nerede olduğunu söyleyemez.

Çözüm: dokümanı küçük parçalara (chunk) bölmek. Deneyimim şunu söylüyor:

  • Chunk boyutu: 500-1000 token arası ideal. Çok küçük olursa bağlam kaybolur, çok büyük olursa doğruluk düşer.
  • Overlap (örtüşme): Ardışık chunk’lar arasında 50-100 token örtüşme bırakmak, bölünme yerine denk gelen önemli bir cümlenin ikiye ayrılmasını engeller.
  • Semantik sınır tercihi: Mümkünse paragraf veya başlık sınırlarına saygı göster. Cümlenin ortasında kesme.

Chunk’larken her parçaya dokümanın adını, bölümünü ve sayfa numarasını meta olarak eklemenizi öneririm — arama sonucunda kullanıcıya “Bu bilgi Backup_Runbook_2024.pdf, sayfa 14’te” diyebilmek için.

4. Genişletilmiş Tablomuz

Bölüm 1’deki tabloyu chunk-bazlı senaryoya genişletelim:

USE VectorDemo;
GO
-- Ana doküman kaydı
CREATE TABLE dbo.Documents
(
DocumentId INT IDENTITY(1,1) PRIMARY KEY,
FileName NVARCHAR(300) NOT NULL,
Title NVARCHAR(300) NOT NULL,
TotalPages INT NOT NULL,
CreatedAt DATETIME2(0) NOT NULL DEFAULT SYSUTCDATETIME()
);
-- Her chunk bir satır
CREATE TABLE dbo.DocumentChunks
(
ChunkId BIGINT IDENTITY(1,1) PRIMARY KEY,
DocumentId INT NOT NULL
REFERENCES dbo.Documents(DocumentId),
PageNumber INT NOT NULL,
ChunkIndex INT NOT NULL,
ChunkText NVARCHAR(MAX) NOT NULL,
TokenCount INT NOT NULL,
Embedding VECTOR(1536) NULL
);
CREATE INDEX IX_Chunks_DocumentId
ON dbo.DocumentChunks(DocumentId);
GO

5. Python ile Toplu Embedding Pipeline’ı

Embedding üretme işini bir Python scripti ile yapacağız. Tercihim Python, çünkü PDF parsing için pypdf, tokenization için tiktoken, batch embedding için openai kütüphaneleri hazır.

import os
import tiktoken
import pyodbc
from openai import AzureOpenAI
from pypdf import PdfReader
AZURE_ENDPOINT = "https://<resource>.openai.azure.com/"
AZURE_API_KEY = os.environ["AZURE_OPENAI_KEY"]
DEPLOYMENT = "embed-small"
API_VERSION = "2024-10-21"
SQL_CONN = (
"Driver={ODBC Driver 18 for SQL Server};"
"Server=localhost;Database=VectorDemo;Trusted_Connection=yes;"
)
CHUNK_SIZE = 800
CHUNK_OVERLAP = 100
client = AzureOpenAI(
azure_endpoint=AZURE_ENDPOINT,
api_key=AZURE_API_KEY,
api_version=API_VERSION,
)
tokenizer = tiktoken.get_encoding("cl100k_base")
def chunk_text(text, size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
tokens = tokenizer.encode(text)
step = size - overlap
for i in range(0, len(tokens), step):
chunk = tokens[i : i + size]
yield tokenizer.decode(chunk), len(chunk)
def embed_batch(texts):
response = client.embeddings.create(
model=DEPLOYMENT,
input=texts,
)
return [item.embedding for item in response.data]
def ingest_pdf(path):
conn = pyodbc.connect(SQL_CONN)
cur = conn.cursor()
reader = PdfReader(path)
cur.execute("""
INSERT INTO dbo.Documents (FileName, Title, TotalPages)
OUTPUT INSERTED.DocumentId
VALUES (?, ?, ?)
""", os.path.basename(path), os.path.basename(path), len(reader.pages))
doc_id = cur.fetchone()[0]
buffered = []
for page_no, page in enumerate(reader.pages, start=1):
text = page.extract_text() or ""
for i, (chunk, tokens) in enumerate(chunk_text(text)):
buffered.append((page_no, i, chunk, tokens))
# Toplu embedding — 16'lık batch'ler
for start in range(0, len(buffered), 16):
batch = buffered[start : start + 16]
embeddings = embed_batch([b[2] for b in batch])
for (page_no, idx, text, tokens), emb in zip(batch, embeddings):
emb_json = str(emb)
cur.execute("""
INSERT INTO dbo.DocumentChunks
(DocumentId, PageNumber, ChunkIndex, ChunkText, TokenCount, Embedding)
VALUES (?, ?, ?, ?, ?, CAST(? AS VECTOR(1536)))
""", doc_id, page_no, idx, text, tokens, emb_json)
conn.commit()
conn.close()
print(f"{path}: {len(buffered)} chunk yüklendi")
if __name__ == "__main__":
for f in os.listdir("./pdfs"):
if f.endswith(".pdf"):
ingest_pdf(os.path.join("./pdfs", f))

200 PDF için bu pipeline ortalama 30-45 dakika sürer — en yavaş kısmı Azure OpenAI çağrıları. Rate limit’i aşmamak için batch boyutunu (16) ve paralellik seviyesini projende göze alabileceğin seviyede tut.

6. DiskANN İndeksini Oluşturma

Şimdi tabloda on binlerce chunk var. Bölüm 1’deki brute force yaklaşımını denersek her sorgu kabaca satır sayısıyla orantılı zaman alır. 100.000 satır için bile benim testimde tek sorgu 700-900 ms sürdü. Production için bu çok. İşte burada DiskANN devreye giriyor.

CREATE VECTOR INDEX IX_Chunks_Embedding
ON dbo.DocumentChunks(Embedding)
WITH (
metric = 'cosine',
m = 16, -- eş bağlantı sayısı
ef_construction = 200, -- index kurulumu sırasında taranacak eş bağlantı sayısı
ef_search = 80 -- sorgu sırasında taranacak eş bağlantı sayısı
);

Parametreler için pratik rehber:

  • m (varsayılan 16): Graf bağlantı yoğunluğu. Yüksek m = daha doğru ama daha büyük index. Metin embedding’leri için 16 gayet yeterli.
  • ef_construction (varsayılan 200): Index kurulurken ne kadar dikkatli çalışacağını belirler. Bir kereye mahsus maliyet — 200-400 arası güvenli.
  • ef_search (varsayılan 80): Her sorguda ne kadar arayacağını belirler. Artırırsan doğruluk artar, hız düşer. 80-150 arası sweet spot.

İndex kurulumu 100.000 satır için yaklaşık 3-5 dakika sürer. Bu işlem boyunca tabloya yazma yapılmaması daha sağlıklı — büyük data yüklemesini tamamladıktan sonra index’i kurmak en iyi strateji.

7. Performans Karşılaştırma

Aynı soruyu hem index’li hem index’siz çalıştıralım. Benim 100K chunk’lı test tablomdaki sonuçlar:

-- Brute force (tüm satırları tarar)
SELECT TOP (5) ChunkId, VECTOR_DISTANCE('cosine', Embedding, @q) d
FROM dbo.DocumentChunks
ORDER BY d ASC;
-- ~780 ms, logical reads: 42,311
-- DiskANN index kullanarak
SELECT TOP (5) ChunkId, VECTOR_DISTANCE('cosine', Embedding, @q) d
FROM dbo.DocumentChunks
WITH (INDEX = IX_Chunks_Embedding)
ORDER BY d ASC;
-- ~28 ms, logical reads: 1,847

Yaklaşık 28 kat hız kazancı. Index’in approximate olduğunu unutmayın: brute force 5 sonucun ilk 4’ü aynıydı, 5. sonuç biraz farklıydı. Çoğu kullanıcı senaryosunda bu fark hiç hissedilmez; ama sağlıkta veya hukukta tam doğruluk şartsa brute force tercih edin veya ef_search değerini 300-500’e çıkartın.

SET STATISTICS IO ve SET STATISTICS TIME komutlarıyla kendi ortamınızda bu testi çalıştırmanızı ve sonuçlarınızı yorumlara yazmanızı öneririm.

8. Sırada Ne Var?

Bu bölümde uçtan uca bir pipeline kurduk: PDF → chunk → embedding → SQL Server → DiskANN index. Sırada duruyor:

  • RAG mimarisi: Bu chunk’ları bir LLM’in prompt’una gömüp “şirket dokümanlarına cevap veren asistan” kurmak
  • Güvenlik: Azure OpenAI API key’i Database Scoped Credential içinde tutmak, Private Endpoint ile SQL Server’dan Azure OpenAI’a giden trafiği public internet dışında tutmak
  • Production checklist: Rate limit yönetimi, embedding versiyonlama (model değişirse ne olur?), maliyet takibi

Kendi kütüphanenize bir-iki PDF koyup script’i çalıştırdığınızda karşılaştığınız hataları veya süre gözlemlerini yorumlara yazabilirseniz, sonraki bölümde sorularınıza göre eklemeler yapabilirim.


Yavuz Filizlibay sitesinden daha fazla şey keşfedin

Subscribe to get the latest posts sent to your email.


Bir Cevap Yazın

Bu site istenmeyenleri azaltmak için Akismet kullanır. Yorum verilerinizin nasıl işlendiğini öğrenin.

Yavuz Filizlibay sitesinden daha fazla şey keşfedin

Okumaya devam etmek ve tüm arşive erişim kazanmak için hemen abone olun.

Okumaya Devam Edin

Yavuz Filizlibay sitesinden daha fazla şey keşfedin

Okumaya devam etmek ve tüm arşive erişim kazanmak için hemen abone olun.

Okumaya Devam Edin