Anti-pola pemodelan

Hub raksasa, segala-sesuatu-adalah-node, dan penjejalan properti.

Daftar isi

Model graf yang tampak sehat pada data kecil dapat melambat ketika data menumpuk, dan penyebabnya sering kali terletak pada bentuk model, bukan pada perangkat kerasnya. Topik ini membahas tiga anti-pola pemodelan yang lazim menimbulkan perlambatan semacam itu, yaitu hub raksasa, segala-sesuatu-adalah-node, dan penjejalan properti. Ketiganya diperagakan pada miniatur yang dijalankan di server Neo4j beserta gejala, dampak, dan langkah perbaikannya. Setelah membacanya, ketiga bentuk keliru dapat dikenali sejak perancangan, dan model yang sudah berjalan dapat diperbaiki melalui migrasi yang terkendali.

Peta tiga anti-pola#

Anti-pola adalah pola pemodelan yang masih menyimpan dan mengembalikan data dengan benar, tetapi menimbulkan biaya yang tumbuh lebih cepat daripada datanya. Ketiganya jarang tampak pada hari pertama, karena model yang sama terlihat rapi selama data dan pertanyaannya masih sedikit. Gejalanya baru muncul ketika jumlah data bertambah atau pertanyaan baru diajukan, dan pada saat itu perbaikan sudah harus menyentuh data yang sedang digunakan. Tabel berikut meringkas gejala, dampak, dan perbaikan masing-masing, sedangkan tiga bagian setelahnya memeragakannya satu per satu.

Anti-polaGejala khasDampakPerbaikan
Hub raksasaSatu node menyimpan relasi dalam jumlah sangat besarKueri yang melintasinya menjangkau seluruh relasinyaNode antara, label tambahan, penjangkaran
Segala-sesuatu-adalah-nodeFakta kecil menjadi node tersendiriSetiap pola kueri bertambah panjangKembalikan fakta ke properti yang memilikinya
Penjejalan propertiSatu node memuat properti milik beberapa entitasKueri harus menyebut nama properti satu per satuPecah node menurut pemilik keterangannya

Setiap peragaan diakhiri langkah migrasi yang memindahkan fakta ke struktur perbaikannya, lalu mencabut struktur lama dari graf, sehingga miniatur yang tersisa adalah bentuk yang dianjurkan. Label dan relasi yang digunakan dalam peragaan diberi nama khusus, misalnya :Kota dan :KelasGede, agar data peragaan tidak bercampur dengan data lain pada server yang sama. Ketiga miniatur sengaja dibuat kecil dan skalanya dijelaskan dalam prosa. Tujuan peragaan adalah bentuk strukturnya, bukan ukurannya.

Hub raksasa#

Hub raksasa (super node) adalah node yang menyimpan relasi dalam jumlah sangat besar, misalnya satu node kota yang menerima relasi langsung dari setiap warganya. Jumlah relasi yang melekat pada satu node disebut derajat (degree). Pada graf pemukiman nyata dengan ratusan ribu warga, derajat node kota ikut mencapai ratusan ribu, sedangkan miniatur berikut hanya memuat enam warga agar keluarannya mudah dibaca. Skrip berikut membangun node kota dan keenam warganya dengan MERGE, lalu menghitung relasi yang diterima node kota.

cypher
MERGE (k:Kota {nama: "Sukamaju"});
MERGE (w:Warga {nama: "Ali"});
MERGE (w:Warga {nama: "Budi"});
MERGE (w:Warga {nama: "Citra"});
MERGE (w:Warga {nama: "Dewi"});
MERGE (w:Warga {nama: "Eka"});
MERGE (w:Warga {nama: "Fajar"});
MATCH (w:Warga), (k:Kota {nama: "Sukamaju"})
MERGE (w)-[:TINGGAL_DI]->(k);
MATCH (k:Kota {nama: "Sukamaju"})<-[:TINGGAL_DI]-(w:Warga)
RETURN k.nama AS kota, count(w) AS jumlahWarga;
text Keluaran
kota, jumlahWarga
"Sukamaju", 6

Node kota pada keluaran di atas berderajat enam, dan angka itu tumbuh mengikuti jumlah warga tanpa batas. Hub semacam ini menjadi titik tersibuk pada graf karena setiap pola yang dijangkarkan pada node kota memaksa mesin graf menjangkau seluruh relasinya. Pada derajat ratusan ribu, satu kueri yang melintasinya membaca ratusan ribu relasi meskipun jawabannya hanya satu baris.

Perbaikan pertama adalah menyisipkan node antara (intermediate node), yaitu entitas yang membagi derajat hub menjadi kelompok-kelompok kecil. Dua node perumahan disisipkan di antara warga dan kota, sehingga relasi langsung warga menuju kota dipindahkan menjadi relasi warga menuju perumahannya masing-masing. Skrip berikut membangun kedua perumahan dengan MERGE, memindahkan keenam warga, menandai Ali dan Citra sebagai pengurus, mencabut seluruh relasi langsung, lalu menghitung ulang derajat node kota.

cypher
MERGE (p:Perumahan {nama: "Griya Asri"});
MERGE (p:Perumahan {nama: "Melati Indah"});
MATCH (w:Warga), (p:Perumahan)
WHERE (w.nama IN ["Ali", "Budi", "Citra", "Dewi"] AND p.nama = "Griya Asri")
   OR (w.nama IN ["Eka", "Fajar"] AND p.nama = "Melati Indah")
MERGE (w)-[:ANGGOTA]->(p);
MATCH (p:Perumahan), (k:Kota {nama: "Sukamaju"})
MERGE (p)-[:TERLETAK_DI]->(k);
MATCH (w:Warga {nama: "Ali"}), (w2:Warga {nama: "Citra"})
SET w:Pengurus, w2:Pengurus;
MATCH (w:Warga)-[r:TINGGAL_DI]->(k:Kota)
DELETE r;
MATCH (k:Kota {nama: "Sukamaju"})<-[:TERLETAK_DI]-(p:Perumahan)
OPTIONAL MATCH (w:Warga)-[:ANGGOTA]->(p)
RETURN p.nama AS perumahan, count(w) AS warga
ORDER BY perumahan;
MATCH (k:Kota {nama: "Sukamaju"})-[r]-()
RETURN count(r) AS derajat;
text Keluaran
perumahan, warga
"Griya Asri", 4
"Melati Indah", 2
derajat
2

Derajat node kota turun dari enam menjadi dua, dan derajat itu kini tumbuh mengikuti jumlah perumahan, bukan jumlah warga. Dua perbaikan lain melengkapinya. Label tambahan menandai bagian kecil di dalam label yang besar, sehingga pola menunjuk bagian itu secara langsung tanpa menyisir seluruh warga, sebagaimana label :Pengurus pada kueri pertama berikut. Penjangkaran berarti memulai penelusuran dari node kecil yang spesifik, misalnya satu warga bernama, alih-alih dari node kota yang besar, sebagaimana kueri kedua berikut.

cypher
MATCH (w:Pengurus)-[:ANGGOTA]->(p:Perumahan)
RETURN w.nama AS pengurus, p.nama AS perumahan
ORDER BY pengurus;
MATCH (w:Warga {nama: "Ali"})-[:ANGGOTA]->(p:Perumahan)-[:TERLETAK_DI]->(k:Kota)
RETURN w.nama AS warga, p.nama AS perumahan, k.nama AS kota;
text Keluaran
pengurus, perumahan
"Ali", "Griya Asri"
"Citra", "Griya Asri"
warga, perumahan, kota
"Ali", "Griya Asri", "Sukamaju"

Segala-sesuatu-adalah-node#

Anti-pola kedua mengangkat fakta kecil menjadi node, sehingga setiap pertanyaan sederhana menuntut penelusuran yang panjang. Miniatur berikut membangun tiga peristiwa kegiatan warga, lalu menempatkan tanggal kegiatan pada node :Hari dan statusnya pada node :Status, sehingga satu kegiatan terikat tiga relasi. Kaitan :OLEH menuju node warga tetap dipertahankan karena warga adalah entitas yang berdiri sendiri, sedangkan tanggal dan status hanyalah keterangan milik kegiatan itu. Skrip berikut membangun miniatur tersebut dengan MERGE, lalu menutupnya dengan pertanyaan yang mencari tanggal dan status seluruh kegiatan milik Ali.

cypher
MERGE (h:Hari {tanggal: date("2026-11-02")});
MERGE (h:Hari {tanggal: date("2026-11-03")});
MERGE (h:Hari {tanggal: date("2026-11-05")});
MERGE (s:Status {nama: "selesai"});
MERGE (s:Status {nama: "tertunda"});
MERGE (k:Kegiatan {kode: "k1"});
MERGE (k:Kegiatan {kode: "k2"});
MERGE (k:Kegiatan {kode: "k3"});
MATCH (k:Kegiatan {kode: "k1"}), (w:Warga {nama: "Ali"}), (h:Hari {tanggal: date("2026-11-02")}), (s:Status {nama: "selesai"})
MERGE (k)-[:OLEH]->(w)
MERGE (k)-[:PADA]->(h)
MERGE (k)-[:BERSTATUS]->(s);
MATCH (k:Kegiatan {kode: "k2"}), (w:Warga {nama: "Ali"}), (h:Hari {tanggal: date("2026-11-03")}), (s:Status {nama: "selesai"})
MERGE (k)-[:OLEH]->(w)
MERGE (k)-[:PADA]->(h)
MERGE (k)-[:BERSTATUS]->(s);
MATCH (k:Kegiatan {kode: "k3"}), (w:Warga {nama: "Ali"}), (h:Hari {tanggal: date("2026-11-05")}), (s:Status {nama: "tertunda"})
MERGE (k)-[:OLEH]->(w)
MERGE (k)-[:PADA]->(h)
MERGE (k)-[:BERSTATUS]->(s);
MATCH (k:Kegiatan)-[:OLEH]->(w:Warga {nama: "Ali"})
MATCH (k)-[:PADA]->(h:Hari)
MATCH (k)-[:BERSTATUS]->(s:Status)
RETURN h.tanggal AS tanggal, s.nama AS status
ORDER BY tanggal;
text Keluaran
tanggal, status
2026-11-02, "selesai"
2026-11-03, "selesai"
2026-11-05, "tertunda"

Pertanyaan dua keterangan sederhana pada keluaran di atas menuntut satu node kegiatan, tiga relasi, dan tiga entitas berbeda. Setiap fakta kecil yang diangkat menambah satu node dan satu lompatan pada setiap pola yang menyentuhnya, sehingga graf membengkak dengan node yang tidak pernah menjadi sasaran pencarian mandiri. Node :Hari dan :Status hanya berperan sebagai pemberat, karena nilainya tidak perlu dicari tanpa kegiatannya. Pada skala ratusan ribu peristiwa, pembengkakan itu melipatgandakan jumlah node dan relasi tanpa menambah makna.

Perbaikannya mengembalikan fakta kecil ke pemiliknya, yaitu sebagai properti pada entitas atau relasi yang memilikinya. Tanggal dan status piket pada model modul ini sudah tersimpan sebagai properti relasi :MELAKSANAKAN, sehingga tidak ada node perantara yang perlu dilalui. Pertanyaan yang sama kini berbentuk satu pola dengan satu relasi, sebagaimana kueri berikut untuk kegiatan milik Endang.

cypher
MATCH (o:Orang {nama: "Endang"})-[r:MELAKSANAKAN]->(t:Tugas)
RETURN r.tanggal AS tanggal, r.status AS status, t.nama AS tugas
ORDER BY r.tanggal;
text Keluaran
tanggal, status, tugas
2026-11-03, "selesai", "Menyapu lantai"
2026-11-05, "tertunda", "Menyiram tanaman"

Miniatur anti-pola kemudian dicabut agar graf tidak menyimpan dua salinan model untuk fakta yang sama. Skrip berikut menghapus ketiga label peragaan sekaligus. Kueri penutupnya memastikan tidak ada sisa yang tertinggal.

cypher
MATCH (n) WHERE n:Kegiatan OR n:Hari OR n:Status
DETACH DELETE n;
MATCH (n) WHERE n:Kegiatan OR n:Hari OR n:Status
RETURN count(n) AS sisa;
text Keluaran
sisa
0

Penjejalan properti#

Anti-pola ketiga menjejalkan properti dari beberapa entitas ke dalam satu node. Node kelas pada miniatur berikut menampung data kelas, data walinya, dan daftar warganya sekaligus, sehingga satu node memuat sepuluh properti yang sebenarnya milik tiga pemilik berbeda. Pada data nyata, pola ini mudah berkembang hingga memuat puluhan properti karena setiap keterangan baru tampak sepele untuk ditambahkan. Skrip berikut membangun node tersebut dengan MERGE, lalu menutupnya dengan pertanyaan yang mencari kelas tempat Ali terdaftar.

cypher
MERGE (k:KelasGede {nama: "Kelas 5A"})
  SET k.ruang = "R-12", k.tingkat = 5, k.tahunAjaran = "2026/2027",
      k.waliNama = "Ratna", k.waliTelepon = "081234560001",
      k.warga1 = "Ali", k.warga2 = "Budi", k.warga3 = "Citra", k.warga4 = "Dewi";
MATCH (k:KelasGede)
WHERE k.warga1 = "Ali" OR k.warga2 = "Ali" OR k.warga3 = "Ali" OR k.warga4 = "Ali"
RETURN k.nama AS kelas;
MATCH (k:KelasGede {nama: "Kelas 5A"})
UNWIND keys(k) AS kunci
RETURN kunci
ORDER BY kunci;
text Keluaran
kelas
"Kelas 5A"
kunci
"nama"
"ruang"
"tahunAjaran"
"tingkat"
"waliNama"
"waliTelepon"
"warga1"
"warga2"
"warga3"
"warga4"

Pertanyaan pencarian di atas harus menyebut warga1 sampai warga4 satu per satu, dan mendukung warga kelima berarti menyunting kuerinya. Daftar kunci pada keluaran kedua memperlihatkan penumpukan itu secara utuh, yaitu properti kelas, properti berawalan wali, dan properti berawalan warga berdampingan dalam satu node. Pencarian pada nilai yang berpindah-pindah kunci tidak dapat dibantu indeks, dan keterangan tambahan untuk wali hanya bisa ditambahkan sebagai properti berawalan berikutnya.

Perbaikannya memecah node itu menurut pemilik keterangannya. Data kelas tetap pada node :Kelas, data wali menjadi node :Pengajar yang dihubungkan relasi :DIAWASI, dan daftar warga menjadi relasi :MENAMPUNG menuju node :Warga yang sudah ada pada miniatur pertama. Node :KelasGede yang lama dicabut setelah seluruh faktanya pindah, sehingga tidak ada dua salinan model yang tersisa.

cypher
MERGE (k:Kelas {nama: "Kelas 5A"})
  SET k.ruang = "R-12", k.tingkat = 5, k.tahunAjaran = "2026/2027";
MERGE (g:Pengajar {nama: "Ratna"})
  SET g.telepon = "081234560001";
MATCH (k:Kelas {nama: "Kelas 5A"}), (g:Pengajar {nama: "Ratna"})
MERGE (k)-[:DIAWASI]->(g);
MATCH (k:Kelas {nama: "Kelas 5A"}), (w:Warga)
WHERE w.nama IN ["Ali", "Budi", "Citra", "Dewi"]
MERGE (k)-[:MENAMPUNG]->(w);
MATCH (k:KelasGede)
DETACH DELETE k;
MATCH (k:Kelas {nama: "Kelas 5A"})-[:MENAMPUNG]->(w:Warga)
RETURN w.nama AS warga
ORDER BY warga;
MATCH (k:Kelas)-[:MENAMPUNG]->(w:Warga {nama: "Ali"})
RETURN k.nama AS kelas;
text Keluaran
warga
"Ali"
"Budi"
"Citra"
"Dewi"
kelas
"Kelas 5A"

Pertanyaan yang sama kini berbentuk satu pola yang tidak menyebut nama properti sama sekali, dan jumlah warga tidak lagi terikat jumlah kunci. Pertanyaan baru pun terbuka tanpa perubahan model, karena keterangan tambahan untuk wali cukup ditambahkan sebagai properti pada node :Pengajar. Ketiga anti-pola pada topik ini memperlihatkan pola perbaikan yang sama, yaitu memindahkan fakta ke pemiliknya lalu mencabut struktur lama.

Kesalahan umum#

Tiga kesalahan berikut muncul ketika perbaikan anti-pola dikerjakan.

  1. Menyisipkan node antara untuk relasi yang tidak memerlukan fakta sendiri. Node antara yang diangkat dari relasi tanpa keterangan hanya menambah satu lompatan pada setiap kueri, dan kebiasaan itu mengembalikan graf ke anti-pola segala-sesuatu-adalah-node. Sisipkan node antara hanya apabila relasinya memerlukan fakta sendiri atau entitasnya perlu dicari secara mandiri.
  2. Membiarkan dua salinan model setelah migrasi. Menambah node :Kelas tanpa mencabut :KelasGede membuat fakta tersimpan dua kali, dan kueri lama yang menyasar struktur lama terus mengembalikan data yang tidak pernah diperbarui. Pindahkan fakta sekali jalan, lalu cabut struktur lamanya sebagaimana dilakukan ketiga miniatur.
  3. Menilai anti-pola dari ukuran data awal semata. Hub kecil pada data uji tidak memperlihatkan biayanya, dan model yang rapi pada sepuluh baris data dapat melambat pada sepuluh ribu. Nilai model dari pertanyaan yang harus dijawab dan dari pertumbuhan data yang diperkirakan, bukan dari tampilannya pada data pertama.

Ringkasan#

  • Hub raksasa menyimpan relasi dalam jumlah yang tumbuh tanpa batas, dan perbaikannya adalah node antara yang membagi derajat, label tambahan yang menandai bagian kecil, serta penjangkaran kueri pada node kecil.
  • Segala-sesuatu-adalah-node mengangkat fakta kecil menjadi node sehingga setiap pola kueri bertambah panjang, dan perbaikannya mengembalikan fakta itu sebagai properti entitas atau relasi yang memilikinya.
  • Penjejalan properti menampung keterangan beberapa entitas pada satu node, dan perbaikannya memecah node itu menurut pemilik keterangannya lalu menghubungkan hasil pemecahan dengan relasi.
  • Setiap perbaikan diakhiri pencabutan struktur lama agar graf tidak menyimpan dua salinan model untuk fakta yang sama.

Referensi#