~/ / Artikel AI / LLM lokal di VPS / Simpan dan cari embedding Ollama di PostgreSQL dengan pgvector
Simpan dan cari embedding Ollama di PostgreSQL dengan pgvector
Embedding dari /api/embed milik Ollama baru berguna untuk pencarian setelah disimpan di suatu tempat. pgvector adalah ekstensi open source yang menambahkan pencarian kemiripan vektor ke PostgreSQL, sehingga vektor dapat berada di database yang sama dengan teks dan data Anda yang lain, di server yang sama. Artikel ini membahas cara menginstalnya, membuat tabel, memilih jarak dan indeks, serta pengaturan yang mengubah hasilnya, berdasarkan README pgvector versi 0.8.7. Setelah itu, hasil pencarian diteruskan ke /api/chat milik Ollama agar jawabannya menyebut sumbernya, lalu sitasi tersebut diperiksa. Kami menjalankan SQL-nya pada pgvector 0.8.7 dengan PostgreSQL 16, memakai vektor asli dari Ollama, dan permintaan chat pada Ollama 0.40.11 dengan qwen2.5:7b. Empat alat di halaman ini menuliskan SQL dan permintaannya serta memeriksa jawabannya di browser Anda.
Terbit: · 9 menit baca · Fakta-fakta ini diperiksa terhadap README dan kode sumber pgvector versi 0.8.7 serta dokumentasi dan kode sumber Ollama (0.40.11) pada 2026-11-05. Periksa dokumentasi terbaru sebelum Anda mengandalkannya.
Apa yang ditambahkan pgvector ke PostgreSQL
README pgvector menggambarkannya sebagai pencarian kemiripan vektor open source untuk Postgres. Ia mendukung pencarian nearest neighbor yang eksak maupun aproksimasi; vektor presisi tunggal, setengah presisi, biner, dan sparse; serta jarak L2, inner product, jarak kosinus, jarak L1, jarak Hamming, dan jarak Jaccard. Karena vektornya adalah kolom PostgreSQL biasa, vektor itu juga mendapatkan kepatuhan ACID, point-in-time recovery, dan JOIN.
Untuk pencarian di dokumen Anda, Anda menyimpan satu baris per chunk berisi teks dan vektornya, membuat embedding untuk pertanyaan dengan model yang sama, lalu mengurutkan baris berdasarkan jaraknya ke vektor pertanyaan.
Instal
pgvector mendukung PostgreSQL 13 dan yang lebih baru. README mencantumkan beberapa cara instalasi, termasuk berikut ini:
| Cara | Yang dikatakan README |
|---|---|
| Build dari kode sumber | git clone --branch v0.8.7, lalu make dan make install (mungkin perlu sudo) |
| Docker | Image pgvector/pgvector menambahkan pgvector ke image Postgres. Tag seperti pg18-trixie atau 0.8.7-pg16 memilih versi Postgres. |
| APT (Debian, Ubuntu) | sudo apt install postgresql-18-pgvector dari PostgreSQL APT Repository, dengan 18 diganti versi Postgres Anda |
| Homebrew | brew install pgvector, yang hanya menambahkannya ke formula postgresql@18 dan postgresql@17 |
Setelah itu, aktifkan sekali di setiap database yang ingin Anda pakai, dengan CREATE EXTENSION vector;. Untuk artikel ini kami memakai image Docker pgvector/pgvector:0.8.7-pg16.
Buat tabelnya
Kolom bertipe vector(n) menyimpan vektor dengan tepat n dimensi, dan n harus sama dengan panjang vektor dari model embedding Anda. Vektor dengan panjang berbeda ditolak: ketika kami memasukkan vektor 2 dimensi ke kolom vector(3), PostgreSQL mengembalikan "expected 3 dimensions, not 2". all-minilm mengembalikan 384 dimensi, jadi kolomnya vector(384).
README menyebutkan penyimpanan setiap vektor sebesar 4 × dimensi + 8 byte, dengan setiap elemen berupa bilangan floating-point presisi tunggal, dan mengizinkan hingga 16,000 dimensi dalam satu kolom. Indeks mendukung dimensi yang lebih sedikit, seperti ditunjukkan di bawah. Tipe halfvec menyimpan nilai setengah presisi dalam 2 × dimensi + 8 byte.
| Dimensi | vector | halfvec |
|---|---|---|
| 384 | 1,544 byte | 776 byte |
| 768 | 3,080 byte | 1,544 byte |
| 1024 | 4,104 byte | 2,056 byte |
Pada tabel uji kami, pg_column_size memberi 1,544 byte untuk vector(384) dan 776 byte untuk halfvec(384), sesuai dengan rumusnya. Ini hanya vektornya; kolom teks dan indeks memakan ruang tambahan.
Muat vektor dari Ollama
pgvector membaca vektor sebagai teks dalam tanda kurung siku, seperti '[1,2,3]', dan README mengatakan semua elemen harus terhingga (tanpa NaN, Infinity, atau -Infinity). Setiap array di bidang embeddings pada respons /api/embed sudah berbentuk seperti ini, dan respons mempertahankan urutan input, sehingga teks pertama cocok dengan vektor pertama.
README menampilkan INSERT untuk beberapa baris, INSERT … ON CONFLICT untuk upsert, dan COPY untuk pemuatan massal. Konverter di bawah memasangkan teks Anda dengan respons dan menulis pernyataan INSERT dengan tanda kutip yang sudah di-escape. Kami menjalankan keluarannya dengan teks yang berisi tanda kutip, backslash, pemisah baris, dan emoji, dan semuanya kembali tanpa berubah.
Coba sekarang: Embeddings to pgvector INSERT
Contoh ini memakai vektor karangan dengan panjang 1 dan 4 dimensi agar mudah dibaca. Embedding asli punya ratusan atau ribuan dimensi.
3 baris dengan 4 dimensi. Teks dibaca sebagai body permintaan /api/embed, vektor dibaca sebagai respons /api/embed dari Ollama.
Pernyataan INSERT
Jalankan di psql atau klien lain. Baris-barisnya dimasukkan dalam satu transaksi.
BEGIN;
INSERT INTO documents (content, embedding) VALUES
('Embeddings turn text into numeric vectors.', '[0.620186,0.410123,0.550165,0.380114]'),
('pgvector stores vectors in PostgreSQL, next to the rest of your data.', '[0.578815,0.469039,0.518937,0.419142]'),
('Ollama''s /api/embed returns one vector per input, in the same order.', '[0.119982,0.809879,-0.329951,0.46993]');
COMMIT;Format vektor dari README pgvector, diperiksa terhadap pgvector 0.8.7 pada 2026-11-04. pgvector README
Semuanya dilakukan di browser Anda. Alat ini menulis SQL dan tidak terhubung ke mana pun.
Pilih jaraknya
Dokumentasi Ollama merekomendasikan kemiripan kosinus untuk sebagian besar kasus pencarian semantik dan mengatakan /api/embed mengembalikan vektor dengan panjang 1. Untuk vektor seperti itu, jarak kosinus, jarak L2, dan inner product mengurutkan baris dengan urutan yang sama, karena kuadrat jarak L2 sama dengan 2 dikurangi 2 kali kemiripan kosinus. Pada uji kami dengan all-minilm, tiga baris teratas sama pada ketiganya, dan jarak L2 sebesar 0.931 sesuai dengan kemiripan kosinus 0.566.
Setiap jarak punya operatornya sendiri. Sebuah pencarian mengurutkan berdasarkan operator itu dan mengambil baris pertama dengan LIMIT, dan indeks dibuat untuk satu operator class.
| Operator | Mengembalikan | Operator class indeks |
|---|---|---|
| <=> | Jarak kosinus. Kemiripan kosinus adalah 1 - (a <=> b). | vector_cosine_ops |
| <-> | Jarak L2 (Euclidean) | vector_l2_ops |
| <#> | Inner product negatif. Kalikan dengan -1 untuk mendapatkan inner product. | vector_ip_ops |
| <+> | Jarak L1 | vector_l1_ops |
README menjelaskan bahwa <#> mengembalikan inner product negatif karena Postgres hanya mendukung pemindaian indeks dengan urutan ASC pada operator. Untuk halfvec, operator class-nya adalah halfvec_cosine_ops, halfvec_l2_ops, dan seterusnya.
Pencarian eksak atau indeks
Tanpa indeks, pgvector melakukan pencarian nearest neighbor yang eksak, yang menurut README memberi recall sempurna. Indeks aproksimasi menukar sebagian recall dengan kecepatan, dan README memperingatkan bahwa kueri bisa mengembalikan hasil yang berbeda setelah Anda menambahkannya.
Indeks hanya dipakai jika kueri memiliki ORDER BY dan LIMIT, dan ORDER BY-nya adalah operator jarak itu sendiri dengan urutan menaik. ORDER BY embedding <=> $1 LIMIT 5 dapat memakai indeks; ORDER BY 1 - (embedding <=> $1) DESC tidak bisa. README juga mencatat bahwa pemindaian tabel mungkin lebih cepat ketika tabelnya kecil.
| Hal | HNSW | IVFFlat |
|---|---|---|
| Cara kerja | Graf berlapis banyak | Membagi vektor ke dalam list dan mencari list yang paling dekat dengan kueri |
| Dibandingkan yang lain | Tradeoff kecepatan-recall lebih baik, tetapi pembuatannya lebih lambat dan memakai lebih banyak memori | Pembuatannya lebih cepat dan memakai lebih sedikit memori, tetapi tradeoff kecepatan-recall lebih rendah |
| Kapan dibuat | Bisa dibuat pada tabel kosong (tanpa langkah pelatihan) | Setelah tabel berisi sejumlah data |
| Opsi build | m (16 secara default) dan ef_construction (64 secara default) | lists: mulai dengan rows / 1000 hingga 1M baris, dan sqrt(rows) di atas itu |
| Opsi pencarian | hnsw.ef_search, 40 secara default | ivfflat.probes, 1 secara default; mulai dengan sqrt(lists) |
| Dimensi | vector hingga 2,000, halfvec hingga 4,000 | vector hingga 2,000, halfvec hingga 4,000 |
Pada pgvector 0.8.7 kami juga mengonfirmasi bahwa kolom vector(2001) tidak bisa memiliki indeks HNSW, bahwa IVFFlat tidak punya operator class untuk jarak L1, dan bahwa ef_construction harus minimal 2 × m. Untuk dimensi yang lebih banyak, FAQ pada README mencantumkan halfvec, kuantisasi biner, dan opsi lain.
Coba sekarang: pgvector SQL Builder
Setiap vektor memakai 1,544 byte, jadi 10,000 baris memakai sekitar 15.4 MB untuk vektornya saja (indeks dan kolom lain tidak termasuk).
1. Ekstensi dan tabel
CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE documents ( id bigserial PRIMARY KEY, content text NOT NULL, embedding vector(384) NOT NULL );
2. Indeks
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
3. Pencarian
PREPARE menerima vektor pertanyaan dari /api/embed sebagai $1. Kirimkan sebagai teks, seperti '[0.01,-0.02,…]'.
PREPARE search_documents(vector(384)) AS
SELECT id, content, 1 - (embedding <=> $1) AS cosine_similarity
FROM documents
ORDER BY embedding <=> $1
LIMIT 5;
-- EXECUTE search_documents('[0.0123, -0.0456, ...]');Batasan dan nilai default dari README dan kode sumber pgvector 0.8.7, diperiksa pada 2026-11-04. pgvector README
Semuanya dilakukan di browser Anda. Alat ini menulis SQL dan tidak terhubung ke mana pun.
Mengapa sebuah pencarian bisa mengembalikan baris lebih sedikit daripada LIMIT
Dengan HNSW, hasilnya dibatasi oleh hnsw.ef_search, yang secara default 40. Pada tabel uji berisi 200 baris dengan indeks HNSW, LIMIT 100 mengembalikan 40 baris; setelah SET hnsw.ef_search = 100, ia mengembalikan 100 baris. Atur hnsw.ef_search minimal sebesar jumlah baris yang Anda butuhkan. SET LOCAL di dalam transaksi mengubahnya hanya untuk satu kueri.
Pemfilteran juga mengurangi hasil. Pada indeks aproksimasi, README mengatakan kondisi WHERE diterapkan setelah indeks dipindai: jika suatu kondisi cocok dengan 10% baris, HNSW dengan hnsw.ef_search default 40 hanya mengembalikan rata-rata 4 baris. Sejak versi 0.8.0, pemindaian indeks iteratif (SET hnsw.iterative_scan = strict_order, atau relaxed_order) memindai lebih banyak indeks sampai baris yang cukup ditemukan, hingga hnsw.max_scan_tuples (20,000 secara default).
README menambahkan bahwa vektor NULL tidak diindeks, dan vektor nol juga tidak untuk jarak kosinus.
Teruskan hasilnya ke /api/chat dengan sumber bernomor
Setelah pencarian mengembalikan baris terdekat, model membutuhkannya di dalam prompt. /api/chat milik Ollama menerima daftar pesan, masing-masing dengan sebuah peran (system, user, assistant, atau tool) dan isinya. Susunan sederhananya adalah pesan sistem berisi aturan dan pesan pengguna yang mendaftar baris-baris dengan nomor masing-masing, seperti [1] dan [2], diikuti pertanyaannya. Dengan nomor, jawaban dapat menyebut baris mana yang dipakai, dan Anda dapat memetakan setiap nomor kembali ke barisnya.
Agar sitasi berbentuk tetap, beri kolom format sebuah JSON Schema berisi jawaban dan daftar sitasi, masing-masing dengan nomor sumber dan kutipan singkat. Halaman keluaran terstruktur milik Ollama menyarankan agar skemanya juga ditulis di dalam prompt, dan agar temperature diturunkan, misalnya ke 0, supaya keluarannya lebih deterministik. Di psql, fungsi json_agg mengumpulkan baris sebuah kueri menjadi array JSON, yang langsung dibaca oleh alat di bawah.
Coba sekarang: RAG Prompt Builder
Contoh sumber ditulis tangan dalam bentuk yang dikembalikan json_agg.
3 sumber, dibaca sebagai array JSON berisi objek.
Perintah curl
Mengirim permintaan ke Ollama di mesin ini (localhost:11434).
curl http://localhost:11434/api/chat -d '{
"model": "qwen2.5:7b",
"messages": [
{
"role": "system",
"content": "Answer the question using only the numbered sources in the user message. Cite the number of each source you use. If the sources do not contain the answer, say that you do not know and cite nothing. Answer in the same language as the question. For each citation, give the source number and copy a short quote from that source exactly as it is written. Return JSON that matches this schema: {\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"},\"citations\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"integer\"},\"quote\":{\"type\":\"string\"}},\"required\":[\"source\",\"quote\"]}}},\"required\":[\"answer\",\"citations\"]}"
},
{
"role": "user",
"content": "Sources:\n\n[1] (id: 12)\nWith HNSW, hnsw.ef_search is 40 by default, so a query returns at most 40 rows unless you raise it.\n\n[2] (id: 7)\nSET LOCAL inside a transaction changes hnsw.ef_search for a single query.\n\n[3] (id: 3)\npgvector supports PostgreSQL 13 and later.\n\nQuestion: Why does my HNSW search return only 40 rows with LIMIT 100?"
}
],
"stream": false,
"format": {
"type": "object",
"properties": {
"answer": {
"type": "string"
},
"citations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {
"type": "integer"
},
"quote": {
"type": "string"
}
},
"required": [
"source",
"quote"
]
}
}
},
"required": [
"answer",
"citations"
]
},
"options": {
"temperature": 0
},
"truncate": false
}'Isi permintaan (JSON)
{
"model": "qwen2.5:7b",
"messages": [
{
"role": "system",
"content": "Answer the question using only the numbered sources in the user message. Cite the number of each source you use. If the sources do not contain the answer, say that you do not know and cite nothing. Answer in the same language as the question. For each citation, give the source number and copy a short quote from that source exactly as it is written. Return JSON that matches this schema: {\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"},\"citations\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"integer\"},\"quote\":{\"type\":\"string\"}},\"required\":[\"source\",\"quote\"]}}},\"required\":[\"answer\",\"citations\"]}"
},
{
"role": "user",
"content": "Sources:\n\n[1] (id: 12)\nWith HNSW, hnsw.ef_search is 40 by default, so a query returns at most 40 rows unless you raise it.\n\n[2] (id: 7)\nSET LOCAL inside a transaction changes hnsw.ef_search for a single query.\n\n[3] (id: 3)\npgvector supports PostgreSQL 13 and later.\n\nQuestion: Why does my HNSW search return only 40 rows with LIMIT 100?"
}
],
"stream": false,
"format": {
"type": "object",
"properties": {
"answer": {
"type": "string"
},
"citations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {
"type": "integer"
},
"quote": {
"type": "string"
}
},
"required": [
"source",
"quote"
]
}
}
},
"required": [
"answer",
"citations"
]
},
"options": {
"temperature": 0
},
"truncate": false
}Pada respons, jawabannya ada di message.content. Jika prompt_eval_count jauh lebih rendah dari yang Anda duga, prompt mungkin terpotong.
Kolom permintaan dari referensi API dan kode sumber Ollama, diperiksa pada Ollama 0.40.11 pada 2026-11-05. Ollama API: /api/chat
Semuanya dilakukan di browser Anda. Alat ini menulis permintaan dan tidak terhubung ke mana pun.
Bila sumbernya tidak muat, Ollama memotong awal prompt
Aturan, baris, dan pertanyaan harus muat dalam panjang konteks. Halaman panjang konteks milik Ollama menyebut default 4k token untuk VRAM kurang dari 24 GiB, dan FAQ-nya menyebut 4096 token; ollama ps pada mesin uji kami menunjukkan 4096. num_ctx di options atau variabel lingkungan OLLAMA_CONTEXT_LENGTH mengubahnya.
Bila prompt lebih panjang, /api/chat secara default tidak mengembalikan galat. Di kode sumber Ollama (0.40.11), server lebih dulu membuang seluruh pesan sebelumnya sambil mempertahankan pesan sistem dan pesan terakhir. Jika prompt yang tersisa masih terlalu panjang, server mempertahankan beberapa token pertama (num_keep, default 4) dan akhir prompt, lalu membuang bagian di antaranya. Pada uji kami dengan qwen2.5:7b dan num_ctx 1024, prompt 2,068 token terpotong menjadi 514. Pesan sistem dan sumber-sumber pertama hilang: ketika jawabannya ada di sumber [1], model menjawab dengan kata kode yang tidak ada di sumber mana pun, dan ketika jawabannya ada di sumber terakhir, model menjawab dengan benar tetapi tanpa nomor sumber yang diminta pesan sistem. Responsnya tidak berisi galat; log server memuat peringatan "truncating input prompt".
Kode sumber Ollama juga punya kolom truncate untuk permintaan chat, yang tidak tercantum di halaman /api/chat. Dengan "truncate": false, permintaan yang sama mengembalikan HTTP 400 dengan galat "request (2068 tokens) exceeds the available context size (1024 tokens), try increasing it". Kolom prompt_eval_count pada respons adalah jumlah token dalam prompt; pada uji yang terpotong nilainya 514, bukan 2,068.
Karena akhir prompt dipertahankan, letakkan pertanyaan setelah sumber. Untuk mengetahui kapan sumbernya tidak muat, kirim "truncate": false, lalu naikkan num_ctx atau kirim lebih sedikit baris. num_ctx yang lebih besar membutuhkan lebih banyak memori.
Periksa sitasinya
Sebuah sitasi dapat diperiksa secara mekanis: nomornya harus merujuk ke sumber yang dikirim, dan kutipannya seharusnya muncul di sumber itu. Pada uji kami dengan empat sumber dan qwen2.5:7b, kutipan dalam jawaban JSON cocok dengan sumbernya kata demi kata. Itu tidak membuktikan jawabannya benar: ketika ditanya ukuran vektor 384 dimensi, model mengutip aturan yang benar (4 kali jumlah dimensi ditambah 8 byte) dan tetap menjawab 1552 byte, bukan 1,544.
Perhatikan juga jawaban tanpa sitasi. Ketika sumbernya tidak memuat jawabannya, model sekali menjawab dari pengetahuannya sendiri tanpa sitasi, dan sekali mengatakan tidak tahu tetapi tetap mengutip sumber yang tidak terkait. Jika done_reason pada respons adalah "length", jawabannya terpotong oleh num_predict dan sitasinya mungkin hilang.
Pemeriksa di bawah hanya membandingkan teks. Apakah sebuah sumber benar-benar mendukung suatu kalimat tetap harus dinilai oleh manusia.
Coba sekarang: RAG Citation Checker
Contoh jawaban ditulis tangan untuk menunjukkan kutipan yang tidak ada di sumbernya.
4 sumber (array JSON berisi string). Jawaban dibaca sebagai respons /api/chat Ollama, dalam bentuk JSON dengan sitasi.
Token dalam prompt (prompt_eval_count): 306
- Kutipan pada sitasi #2 tidak ada di sumber [4].
Sitasi
| Sumber | Kutipan | Hasil |
|---|---|---|
| [2] | hnsw.ef_search is 40 by default | Ditemukan persis |
| [4] | 4 bytes per dimension plus 8 | Tidak ada di sumber ini |
Ini hanya memeriksa bahwa nomor dan kutipan cocok dengan sumber. Ini tidak dapat menentukan apakah jawabannya benar atau apakah sebuah sumber benar-benar mendukung suatu kalimat.
Format respons dari referensi API dan kode sumber Ollama, diperiksa pada Ollama 0.40.11 pada 2026-11-05. Ollama API: /api/chat
Semuanya dilakukan di browser Anda. Alat ini tidak terhubung ke mana pun.
Memori pada server kecil
README mengatakan indeks HNSW dibuat jauh lebih cepat ketika grafnya muat di maintenance_work_mem, menampilkan notifikasi ketika grafnya tidak lagi muat, dan memperingatkan agar tidak mengatur maintenance_work_mem setinggi itu sampai menghabiskan memori server. Seperti indeks lain, lebih cepat membuat indeks setelah memuat data awal. Indeks tidak harus muat di memori, tetapi Anda kemungkinan akan melihat performa lebih baik jika muat; SELECT pg_size_pretty(pg_relation_size('index_name')) menampilkan ukuran sebuah indeks.
Untuk working set dan indeks yang lebih kecil, README menyarankan halfvec, pengindeksan setengah presisi, atau kuantisasi biner. Jika Ollama berjalan di server yang sama, PostgreSQL dan model berbagi memori yang sama; artikel kami tentang memantau memori Ollama menjelaskan apa yang menentukan seberapa banyak model memakainya.
Sumber
- pgvector README (v0.8.7)
- pgvector source: HNSW limits (src/hnsw.h)
- pgvector source: IVFFlat limits (src/ivfflat.h)
- Ollama: Embeddings
- Ollama API: Generate embeddings (/api/embed)
- PostgreSQL documentation: Lexical Structure (string constants)
- Ollama API: Generate a chat message (/api/chat)
- Ollama: Structured Outputs
- Ollama: Context length
- Ollama FAQ (context window size)
- Ollama source v0.40.11: server/prompt.go (dropping earlier messages)
- Ollama source v0.40.11: llm/llama_server.go (truncating the prompt)
- Ollama source v0.40.11: api/types.go (truncate, num_keep)
- PostgreSQL documentation: Aggregate Functions (json_agg)
Baca selanjutnya
- Embedding dengan Ollama: dasar untuk pencarian dan RAGMembuat embedding dengan /api/embed milik Ollama: model dan ukurannya, truncate dan dimensions, vektor berpanjang satuan dan kemiripan kosinus, dari dokumentasi resmi.
- Memantau memori Ollama di VPS kecilLihat apa yang disimpan Ollama di memori, berapa lama bertahan di sana, dan pengaturan apa yang mengubahnya: ollama ps, keep_alive, dan batasannya, dari FAQ resmi.
- Menghubungkan Dify ke Ollama di VPS Anda sendiriJalankan Dify di VPS Anda sendiri dan pakai Ollama sebagai modelnya: instalasi, kolom plugin, masalah alamat di Docker, dan embedding, dari dokumentasi resmi.
- Menjalankan LLM lokal tanpa PC mahal: panduan pemula memakai VPSTidak punya PC bertenaga? Sewa VPS dan jalankan LLM lokal kecil dari browser Anda: enam langkah dari perkiraan memori sampai layar obrolan yang terlindungi, dengan batasannya dinyatakan secara jujur.