Gejala: Lonjakan Memori dan Putusnya Koneksi gRPC
Saat menjalankan persistent memory context retrieval untuk LLM agent (seperti pada arsitektur memory-os), instance Qdrant sering mengalami crash mendadak akibat Out-Of-Memory (OOM) Killer dari kernel Linux. Beban kerja ini melibatkan jutaan chunk percakapan dan metadata pengguna.
Gejala sistematis yang muncul sebelum crash meliputi:
- Lonjakan Resident Set Size (RSS) RAM worker secara eksponensial dalam hitungan detik saat concurrent search meningkat.
- Query timeout pada operasi vector similarity search, yang melonjak dari latensi normal sub-50ms menjadi lebih dari 10 detik.
- Koneksi gRPC terputus dengan status error
UNAVAILABLEatauRST_STREAMakibat proses daemon Qdrant dihentikan seketika oleh kernel (SIGKILL).
Root Cause: Unfiltered Payload Search dan Full-Scan
Qdrant menggunakan payload berbasis JSON untuk menyimpan metadata dokumen seperti user_id, session_id, dan timestamp. Masalah OOM terjadi akibat cara Qdrant mengevaluasi query filtering ketika vector index dan payload index berinteraksi.
Secara default, Qdrant tidak membuat index sekunder untuk field di dalam payload. Ketika aplikasi mengeksekusi vector search dengan filter metadata tanpa index eksplisit:
- Iterasi Tanpa Inverted Index: Qdrant harus memuat dan memeriksa (full-scan) payload JSON point per point dari disk atau page cache ke memori kerja untuk mengevaluasi apakah field cocok dengan kondisi filter.
- Allocations Overhead: Jika collection memiliki jutaan point, operasi filter non-indexed pada query concurrent memaksa deserialisasi JSON massal di memori heap Rust.
- Algoritma HNSW Fallback: Jika rasio filter menyaring terlalu banyak point tanpa payload index, traversal graf HNSW menjadi tidak efisien atau beralih ke pencarian brute-force unindexed yang mengonsumsi kapasitas buffer memori secara simultan hingga menyentuh memory limit container.
Investigasi: Metrik dan Log Qdrant
Langkah pertama investigasi adalah memeriksa event OOM pada kernel host menggunakan dmesg:
dmesg -T | grep -i -E "oom[- ]killer|killed process.*qdrant"Selanjutnya, periksa status collection melalui REST API Qdrant untuk melihat statistik segmen dan index:
curl -X GET http://localhost:6333/collections/agent_memoryPerhatikan bagian payload_schema. Jika payload yang sering difilter (misalnya user_id) tidak terdaftar dengan field schema type yang valid, maka setiap query terhadap field tersebut diproses melalui full-scan.
Pantau metrik Prometheus Qdrant pada endpoint /metrics. Indikator utama masalah ini meliputi:
app_memory_allocated_bytes: Terjadi lonjakan tajam saat request pencarian masuk, bukan saat proses upsert.grpc_server_handling_seconds_count: Terjadi peningkatan kegagalan pada gRPC methods/qdrant.Points/Search.
Solusi 1: Implementasi Payload Indexing Eksplisit
Field yang sering digunakan dalam klausa filter wajib memiliki payload index terdedikasi. Qdrant mendukung tipe index seperti keyword, integer, float, geo, dan text.
Eksekusi pembuatan payload index via Python SDK:
from qdrant_client import QdrantClient
from qdrant_client.http import models
client = QdrantClient(url="http://localhost:6333")
# Buat payload index untuk isolasi tenant/user
client.create_payload_index(
collection_name="agent_memory",
field_name="user_id",
field_schema=models.PayloadSchemaType.KEYWORD,
)
# Buat payload index untuk sorting atau range filter waktu
client.create_payload_index(
collection_name="agent_memory",
field_name="timestamp",
field_schema=models.PayloadSchemaType.INTEGER,
)Payload index berbasis keyword mengimplementasikan struktur hash map atau inverted index efisien, sehingga Qdrant dapat langsung mengambil subset point ID sebelum memuat payload utuh.
Solusi 2: Optimasi HNSW dan Storage (On-Disk vs mmap)
Secara default, menyimpan vector embedding dan index HNSW sepenuhnya di dalam RAM memicu footprint memori masif saat skala vector membesar. Pindahkan vector storage dan link graf HNSW ke disk dengan memory-mapped files (mmap).
Konfigurasi parameter collection saat inisialisasi:
client.create_collection(
collection_name="agent_memory",
vectors_config=models.VectorParams(
size=1536,
distance=models.Distance.COSINE,
on_disk=True, # Simpan raw vectors di disk (mmap)
),
hnsw_config=models.HnswConfigDiff(
on_disk=True, # Simpan graf HNSW di disk
m=16, # Jumlah link per node
ef_construct=100, # Akurasi saat indexing
),
)Trade-off: Mengaktifkan on_disk=True menurunkan konsumsi RAM hingga 70-80% dan memindahkan beban pembacaan data dingin ke OS page cache. Latensi query dapat sedikit meningkat (1-5ms) jika storage I/O Lambat (gunakan NVMe SSD untuk performa optimal).
Solusi 3: Pola Query Filtering Efisien di Python
Pastikan query context retrieval menyertakan filter yang terindeks dan batasi field payload yang dikembalikan (projection) agar tidak mentransfer metadata besar yang tidak dibutuhkan LLM context window.
query_vector = [0.012, -0.045, 0.089] # Dimensi vector embedding 1536
search_result = client.search(
collection_name="agent_memory",
query_vector=query_vector,
query_filter=models.Filter(
must=[
models.FieldCondition(
key="user_id",
match=models.MatchValue(value="usr_abc123"),
),
models.FieldCondition(
key="timestamp",
range=models.Range(gte=1704067200),
),
]
),
limit=5,
with_payload=["message_content", "role"], # Proyeksikan hanya key yang dibutuhkan
with_vectors=False, # Hindari fetch kembali dense vector ke Python runtime
)Penggunaan parameter with_payload eksplisit mencegah alokasi memori besar untuk deserialisasi payload yang tidak terpakai, dan with_vectors=False mengurangi beban alokasi memori di sisi client maupun server.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!