Setiap kueri Cypher disusun ulang oleh planner, yaitu komponen yang memilih urutan langkah paling murah untuk menjawab pola yang diminta, dan hasil pilihannya disebut rencana kueri (query plan). Topik ini membahas pembacaan rencana tersebut melalui EXPLAIN yang menyusun rencana tanpa menjalankan kuerinya, PROFILE yang sekaligus mengukur eksekusinya, struktur pohon operator, satuan db hits, yakni hitungan operasi baca dan tulis mesin, serta perbandingan baris terestimasi dengan baris aktual. Contoh kontrasnya mencari artikel pada dataset 10000 node berlabel :Artikel, dan setiap keluaran berasal dari eksekusi nyata melalui cypher-shell.
EXPLAIN dan PROFILE#
Neo4j menyediakan dua kata kunci untuk melihat rencana, yaitu EXPLAIN dan PROFILE, dan keduanya diletakkan di depan kueri. EXPLAIN menyusun rencana tanpa mengeksekusi kuerinya, sehingga keluaran hanya memuat struktur rencana beserta estimasinya. PROFILE menjalankan kueri hingga selesai, lalu melaporkan pengukuran nyata pada rencana yang sama. Karena kuerinya benar-benar dieksekusi, PROFILE menuntut sumber daya lebih banyak dan hanya digunakan saat sebuah kueri sedang dianalisis.
Kueri berikut mencari satu artikel dari properti slug yang didukung oleh constraint keunikan artikel_slug_m6.
EXPLAIN MATCH (a:Artikel)
WHERE a.slug = 'artikel-7777'
RETURN a.judul AS judul;Plan: "EXPLAIN"
Statement: "READ_ONLY"
Version: "25"
Planner: "COST"
Runtime: "SLOTTED"
Time: 2Ringkasan yang dicetak cypher-shell memuat identitas rencana, bukan rincian operatornya. Baris Planner: "COST" menunjukkan planner berbasis biaya yang membandingkan kandidat rencana lalu memilih yang termurah, sedangkan Runtime menunjukkan mesin yang mengeksekusi rencana itu. Kolom DbHits dan Rows tidak muncul karena kuerinya tidak dijalankan, sehingga keluaran EXPLAIN hanya digunakan untuk menilai bentuk rencana. Kueri yang sama dijalankan ulang dengan PROFILE.
PROFILE MATCH (a:Artikel)
WHERE a.slug = 'artikel-7777'
RETURN a.judul AS judul;judul
"Artikel 7777"
Plan: "PROFILE"
Statement: "READ_ONLY"
Version: "25"
Planner: "COST"
Runtime: "SLOTTED"
Time: 1
DbHits: 3
Rows: 1
Memory (Bytes): 64Ringkasan kini memuat pengukuran eksekusi. DbHits: 3 adalah total akses penyimpanan, Rows: 1 adalah jumlah baris hasil, dan Memory (Bytes) melaporkan memori puncak yang terpakai. Hasil kuerinya dicetak di atas ringkasan karena kuerinya memang dijalankan.
Membaca pohon operator#
Rencana kueri berbentuk pohon operator, yaitu rangkaian tahap eksekusi yang masing-masing melakukan satu jenis pekerjaan atas baris data. Pohonnya dibaca dari bawah ke atas. Operator terbawah adalah sumber baris yang mengambil data dari penyimpanan, sedangkan operator teratas pada kueri baca adalah ProduceResults yang menyerahkan hasil kepada klien. Setiap operator mengambil baris dari anaknya, mengolah baris itu sesuai tugasnya, lalu meneruskan hasil pengolahan kepada induk.
Tabel berikut memuat operator yang muncul pada contoh topik ini.
| Operator | Peran |
|---|---|
ProduceResults | menyiapkan hasil agar dapat dikonsumsi klien |
EagerAggregation | menghitung agregat seperti count() dengan mengumpulkan seluruh baris input |
Filter | menguji predikat WHERE dan meneruskan baris yang lolos |
Projection | menyusun nilai ekspresi keluaran kueri |
NodeByLabelScan | mengambil seluruh node dengan label tertentu |
NodeIndexSeek | menemukan node melalui pencarian pada indeks rentang |
Rencana kueri slug pada bagian sebelumnya tersusun sebagai rantai tiga operator. Daunnya adalah NodeUniqueIndexSeek, yaitu varian NodeIndexSeek yang digunakan ketika indeksnya milik constraint keunikan, dilanjutkan Projection yang menyusun nilai judul, dan bermuara di ProduceResults. Nama operator daun memperlihatkan strategi akses yang dipilih planner, dan kata Seek menandai pencarian terarah melalui indeks sedangkan Scan menandai pembacaan beruntun. Perbedaan itu menjadi petunjuk pertama sebelum angka biayanya dibaca, karena strategi akses menentukan banyaknya data yang disentuh.
Satuan db hits#
Db hits menghitung operasi baca dan tulis tingkat penyimpanan yang dikerjakan mesin, mencakup pembacaan entri indeks, node, relasi, dan properti. Satuannya bukan waktu dan bukan jumlah baris, sehingga dua kueri dengan ukuran hasil sama dapat berbeda total biayanya. Kueri berikut menghitung artikel terbitan 2020 pada properti tahun yang tidak berindeks, sehingga satu-satunya cara mengumpulkan kandidatnya adalah memindai seluruh label.
PROFILE MATCH (a:Artikel)
WHERE a.tahun = 2020
RETURN count(*) AS jumlah;jumlah
1000
Plan: "PROFILE"
Statement: "READ_ONLY"
Version: "25"
Planner: "COST"
Runtime: "SLOTTED"
Time: 4
DbHits: 20001
Rows: 1
Memory (Bytes): 88Hasilnya satu baris berisi 1000, tetapi biayanya 20001 db hits yang terbagi ke dua operator. NodeByLabelScan memindai seluruh node berlabel :Artikel dan mencatat 10001 db hits, sedangkan Filter membaca properti tahun pada setiap node yang diterimanya dan mencatat 10000 db hits. EagerAggregation dan ProduceResults tidak menambah akses penyimpanan karena keduanya bekerja di atas baris yang sudah dibaca.
Pemindaian label tumbuh linier terhadap jumlah node berlabel, sedangkan pencarian indeks berbiaya menurut baris yang ditemukannya. Kueri slug pada bagian pertama hanya mencatat 3 db hits, yaitu dua akses pada NodeUniqueIndexSeek untuk entri indeks dan node yang dirujuknya ditambah satu akses pada Projection untuk properti judul. Selisih antara 3 dan 20001 pada dataset yang sama inilah kontras yang dibaca dari PROFILE.
Baris terestimasi dan baris aktual#
Setiap operator membawa dua angka baris yang berbeda maknanya. Baris terestimasi (estimated rows) adalah taksiran planner terhadap banyaknya baris yang akan dihasilkan sebuah operator, dihitung dari statistik label dan indeks serta asumsi selektivitas, dan menjadi dasar pemilihan rencana. Baris aktual (actual rows) hanya tersedia pada PROFILE dan menyatakan banyaknya baris yang benar-benar dihasilkan. Taksiran dapat meleset karena tidak menggunakan data eksekusi, sehingga penyimpangan besar antara keduanya menandakan planner salah menilai selektivitas data.
Tabel berikut menampilkan kedua angka itu untuk rencana kueri tahun 2020, diurutkan dari daun ke akar.
| Operator | Baris terestimasi | Baris aktual | Db hits |
|---|---|---|---|
NodeByLabelScan | 10000 | 10000 | 10001 |
Filter | 500 | 1000 | 10000 |
EagerAggregation | 1 | 1 | 0 |
ProduceResults | 1 | 1 | 0 |
Estimasi NodeByLabelScan tepat karena jumlah node berlabel tersimpan sebagai statistik. Estimasi Filter meleset dua kali lipat, yaitu 500 alih-alih 1000, karena planner tidak memiliki statistik properti dan mengandalkan asumsi selektivitas bawaannya. Penyimpangan sebesar ini lazim, sedangkan kesalahan taksiran yang jauh lebih besar patut dicurigai, karena pilihan rencana ditetapkan dari angka estimasinya. Ringkasan cypher-shell hanya mencantumkan Rows akhir, sehingga kedua angka per operator dibaca pada Neo4j Browser.
cypher-shell dan Neo4j Browser#
cypher-shell menampilkan rencana sebagai ringkasan, sedangkan Neo4j Browser menampilkannya sebagai pohon operator yang dapat ditelusuri. Ringkasan cypher-shell hanya memuat agregat biaya keseluruhan, sehingga totalnya terbaca cepat, tetapi baris per operator tidak dicetak. Baris Planner dan Runtime tetap tercetak pada ringkasan, sehingga sifat rencana masih terbaca dari terminal. Penampil rencana (Plan view) pada Browser tersedia untuk kueri EXPLAIN dan PROFILE serta menampilkan setiap operator beserta kolom Details, Estimated Rows, Rows, dan DB Hits. Pengukuran yang sama dapat diulangi dari terminal, dan kata sandinya diminta secara interaktif.
# tidak-dijalankan
# melihat ringkasan rencana dari terminal dengan cypher-shell
cypher-shell -a bolt://localhost:7689 -u neo4j "PROFILE MATCH (a:Artikel) WHERE a.slug = 'artikel-7777' RETURN a.judul AS judul;"| Aspek | cypher-shell | Neo4j Browser |
|---|---|---|
| Bentuk rencana | ringkasan agregat biaya | pohon operator yang dapat ditelusuri |
| Data per operator | tidak ditampilkan | Details, Estimated Rows, Rows, DB Hits |
| Kegunaan utama | pemeriksaan cepat dan otomatisasi dari terminal | analisis operator per operator |
Kebiasaan yang sehat adalah memulai dari ringkasan untuk menemukan lonjakan biayanya, lalu membuka Plan view ketika operator penyumbang biaya terbesar perlu diidentifikasi. Kedua antarmuka membaca rencana yang sama, sehingga perbedaannya terletak pada penyajian, bukan pada isi rencana. Keluaran ringkasan berupa teks juga dapat disimpan dan dibandingkan pada beberapa eksekusi, sehingga memudahkan pencatatan hasil PROFILE.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul saat topik ini dipraktikkan.
- Membandingkan
DbHitsdua kueri yang menyentuh dataset berbeda. Biaya dalam db hits tumbuh mengikuti jumlah data yang disentuh, sehingga nilainya tidak dapat diperbandingkan langsung antardataset. Perbaikannya adalah mengukur kedua kueri pada dataset yang sama sebelum kesimpulan diambil. - Memperlakukan baris terestimasi sebagai hasil pengukuran. Estimasi berasal dari statistik dan asumsi selektivitas, sebagaimana taksiran 500 alih-alih 1000 pada
Filter. AngkaRowspada ringkasan cypher-shell hanya menyatakan baris akhir, sehingga taksiran per operator memang tidak terlihat di sana. Perbaikannya adalah memeriksa baris aktual melaluiPROFILEsebelum kualitas sebuah rencana dinilai. - Membaca pohon operator dari atas ke bawah. Operator teratas hanya menyerahkan hasil, sedangkan sumber biaya biasanya berada pada operator daun di bagian bawah. Pembacaan dari bawah juga sejalan dengan arah eksekusinya, karena baris mengalir dari daun menuju akar. Perbaikannya adalah memeriksa operator daun terlebih dahulu, karena pilihan daun menentukan sebagian besar biayanya.
Ringkasan#
EXPLAINmenyusun rencana tanpa menjalankan kueri sehingga hanya memuat estimasi, sedangkanPROFILEmenjalankan kuerinya dan melaporkanDbHits,Rows, danMemory (Bytes).- Rencana kueri berupa pohon operator yang dibaca dari bawah ke atas, dari daun seperti
NodeByLabelScandanNodeIndexSeekhingga akarProduceResults. - Db hits menghitung akses penyimpanan, bukan jumlah baris, sehingga kueri yang hasilnya satu baris dapat berbiaya 20001 db hits.
- Baris terestimasi berasal dari statistik dan dapat meleset, sehingga kualitas rencana dinilai dari baris aktual yang rinciannya terbaca pada Plan view Neo4j Browser.
Referensi#
- Understanding query plans — Neo4j Docs
— Cara kerja
EXPLAINdanPROFILEserta arti kolomEstimated Rows,Rows, danDB Hits. - Operators — Neo4j Docs
— Daftar operator rencana eksekusi Cypher beserta perannya, termasuk
NodeByLabelScandanNodeIndexSeek. - Result frames — Neo4j Docs
— Plan view pada Neo4j Browser untuk kueri
EXPLAINdanPROFILE.