Denormalisasi dalam Basis Data: Kapan dan Cara Menggunakannya
Pelajari bagaimana denormalisasi meningkatkan kinerja baca dengan mengurangi join dan menyederhanakan kueri. Pahami trade-off, teknik, dan kasus penggunaan yang menjadikannya alat ampuh untuk sistem analitik dan pelaporan.
Saya telah banyak menulis tentang normalisasi dan mengapa itu merupakan fondasi yang andal untuk integritas data. Ini bukan berarti saya menarik ucapan saya. Saya tetap percaya bahwa skema yang ternormalisasi dengan baik adalah titik awal yang tepat untuk sebagian besar sistem transaksional. Artikel ini membahas pilihan sadar untuk menyimpang dari kemurnian itu dalam situasi sangat spesifik ketika kinerja baca lebih penting daripada bentuk normal yang ketat.
Denormalisasi bukan berarti melewatkan normalisasi. Ini adalah optimasi kinerja yang Anda terapkan pada model yang sudah ternormalisasi ketika kueri nyata, pengguna nyata, dan SLA nyata memberi tahu Anda bahwa join dan perhitungan langsung terlalu lambat atau terlalu mahal. Dalam praktiknya, Anda menukar baca yang lebih cepat dan kueri yang lebih sederhana dengan lebih banyak penyimpanan, penulisan yang lebih kompleks, dan pekerjaan konsistensi tambahan.
Dalam artikel ini, saya akan menunjukkan di mana denormalisasi berguna dan kapan tidak, serta cara menerapkannya dengan aman di basis data SQL. Tujuannya bukan untuk meninggalkan desain yang baik, melainkan menambahkan jalan pintas di atasnya ketika beban kerja membenarkannya.
Apa itu Denormalisasi dalam Basis Data?
Jika Anda familier dengan normalisasi dan desain basis data, berikut jawaban singkatnya:
Denormalisasi adalah tindakan sengaja menambahkan data redundan ke skema yang sebelumnya ternormalisasi untuk mempercepat baca dan menyederhanakan kueri. Ini adalah optimasi kinerja yang terarah, bukan alasan untuk melewatkan pemodelan yang baik!
Jika Anda relatif baru dalam desain basis data, ada baiknya mengurai makna dari “ternormalisasi”, “terdenormalisasi”, dan “tidak ternormalisasi”. Anda sering mendengar 3 istilah itu secara daring, dan penting untuk tidak mencampuradukkannya.
Ternormalisasi: Data dipecah ke dalam tabel yang terstruktur dengan baik yang meminimalkan redundansi dan melindungi integritas (lihat 3NF/BCNF).
Terdenormalisasi: Anda memperkenalkan kembali redundansi yang selektif dengan kolom tambahan, nilai prahitung, atau tabel pra-join di atas model yang ternormalisasi itu untuk membuat baca umum lebih cepat. Dengan denormalisasi, Anda menjaga satu sumber kebenaran (tabel ternormalisasi), lalu memelihara satu atau lebih representasi yang lebih cepat untuk jalur panas, seperti dasbor, daftar produk, penelusuran, dll. Anda menukar penyimpanan + kompleksitas tulis dengan kecepatan baca + kueri yang lebih sederhana.
Tidak ternormalisasi: Data mentah, ad-hoc, atau berantakan di mana struktur dan kendalinya tidak pernah dirancang dengan benar. Bukan itu yang kita lakukan di sini.
Contoh dalam SQL
Berikut contoh kecil dalam SQL untuk membantu Anda memvisualisasikan perbedaan antara pemodelan ternormalisasi dan terdenormalisasi.
Pemodelan ternormalisasi
-- Source of truthCREATETABLE customers (
customer_id BIGINTPRIMARYKEY,
name TEXTNOTNULL,
tier TEXTNOTNULL);CREATETABLE orders (
order_id BIGINTPRIMARYKEY,
customer_id BIGINTNOTNULLREFERENCES customers(customer_id),
order_total NUMERIC(12,2)NOTNULL,
created_at TIMESTAMPNOTNULLDEFAULTnow());-- Typical report needs a joinSELECT c.name, c.tier,SUM(o.order_total)AS revenue
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
GROUPBY c.name, c.tier;Didukung oleh
Terdenormalisasi untuk jalur pelaporan
-- Add redundant fields for faster readsALTERTABLE orders
ADDCOLUMN customer_name TEXT,ADDCOLUMN customer_tier TEXT;-- Now most reports avoid the joinSELECT customer_name, customer_tier,SUM(order_total)AS revenue
FROM orders
GROUPBY customer_name, customer_tier;Didukung oleh
Dalam contoh ini, Anda tetap menjadikan customers sebagai sumber kebenaran dan memastikan orders.customer_name / orders.customer_tiertetap sinkron (mis., trigger, pekerjaan CDC, atau backfill terjadwal). Sumber kebenaran yang sama, dua representasi, masing-masing dioptimalkan untuk tugas yang berbeda.
Apakah denormalisasi adalah desain yang buruk?
Ada kesalahpahaman umum bahwa denormalisasi hanyalah produk dari desain yang buruk. Itu tidak benar jika diukur, diberi ruang lingkup, dan dipelihara. Ya, denormalisasi sering melanggar bentuk normal yang lebih tinggi (memang itulah tujuannya), tetapi itu disengaja dan didukung oleh rencana untuk menjaga konsistensi. Desain yang buruk adalah melewatkan normalisasi sama sekali atau menaburkan redundansi tanpa strategi sinkronisasi.
Jika Anda ingin mempelajari lebih lanjut tentang desain basis data, kursus pemula kami tentang topik ini adalah tempat yang baik untuk memulai!
Normalisasi vs Denormalisasi
Berikut perbandingan berdampingan. Ini tidak lengkap, tetapi akan membantu Anda memahami trade-off dari masing-masing pendekatan.
Aspek
Normalisasi
Denormalisasi
Tujuan utama
Integritas, redundansi minimal, pembaruan mudah & benar
Baca lebih cepat, kueri lebih sederhana pada jalur panas
Sering memerlukan join, bagus dengan indeks yang tepat
Lebih sedikit join, bisa jauh lebih cepat dan lebih prediktabel
Kompleksitas tulis
Sederhana: satu sumber kebenaran
Lebih tinggi: harus memperbarui/menyinkronkan salinan atau agregat redundan
Penyimpanan
Hemat
Lebih besar (kolom/tabel ekstra, tampilan prahitung)
Integritas data
Kuat secara desain (kendala 3NF/BCNF)
Memerlukan mekanisme untuk mencegah drift (trigger, CDC, pekerjaan)
Kecepatan perubahan
Ganti nama/perbarui kolom bersifat lokal
Perubahan dapat menyebar ke data yang diduplikasi
Beban operasional
Lebih rendah: lebih sedikit komponen bergerak
Lebih tinggi: kebijakan penyegaran, backfill, pemantauan
Mode kegagalan
Kueri N+1, join lambat, indeks hilang
Data basi, inkonsistensi, amplifikasi tulis
Evolusi skema
Prediktabel, mudah direfaktor
Memerlukan rencana migrasi untuk representasi redundan
Contoh tipikal
Pesanan, pelanggan, transaksi
Daftar produk pra-join; tabel penjualan agregat; tampilan termaterialisasi/berindeks
Mengapa dan Kapan Melakukan Denormalisasi
Denormalisasi menjadi sangat berguna ketika pengguna nyata dan kueri nyata terhambat oleh join, agregasi, atau lookup berulang. Setelah Anda memastikan bahwa pengindeksan, penyetelan kueri, dan caching tidak cukup, Anda mungkin ingin beralih ke denormalisasi untuk mengoptimalkan baca cepat pada pola akses yang dapat diprediksi.
Kapan denormalisasi mungkin membantu
Beban kerja baca-berat:latensi p95/p99 didominasi join atau agregasi, CPU dihabiskan untuk hash/merge join, churn buffer cache tinggi.
Bentuk kueri stabil:Dasbor/endpoint yang sama berjalan sepanjang hari dengan filter serupa (mis., penjualan kemarin per kategori).
Join menyebar (fan-out):Satu tabel panas bergabung ke 3–5 tabel lain hanya untuk merender kartu atau tampilan daftar.
Titik panas agregasi:Anda berulang kali menghitung total, hitungan, atau nilai terbaru di atas rentang besar.
Tekanan SLA:Produk memerlukan respons di bawah 200 ms di mana rencana saat ini tumpah ke disk atau memindai terlalu banyak.
Kasus penggunaan klasik
Dasbor & pelaporan BI (OLAP/analitik):Pra-hitung agregat harian/bulanan, simpan tampilan termaterialisasi dari group-by yang mahal, atau simpan tabel fakta terdenormalisasi untuk irisan umum.
E-niaga/katalog & halaman penelusuran/daftar:Duplikasi category_name, brand_name, price_with_tax, atau proyeksi produk pra-join untuk daftar dan filter yang cepat.
CMS/blog/umpan berita:Simpan author_name, primary_topic, atau rendered_excerpt pada tabel artikel/posting untuk menghindari join atau transformasi runtime.
Umpan aktivitas & penghitung:Simpan like_count, follower_count, atau latest_comment_at sebagai atribut turunan alih-alih menghitung ulang.
Analitik peristiwa/log skala besar (OLAP/NoSQL):Ratakan data bertingkat untuk penyimpanan kolumnar dan simpan baris lebar yang ramah partisi untuk membuat pemindaian lebih prediktabel.
Kapan tidak melakukan denormalisasi dan gunakan hal lain terlebih dahulu
OLTP tulis-berat dengan konsistensi ketat (pesanan, pembayaran, penyesuaian inventaris).
Bottleneck adalah indeks yang hilang/buruk, kueri N+1, atau ORM yang cerewet. Perbaiki itu dulu.
Kolom bervolatilitas tinggi (mis., ketersediaan produk berubah tiap detik) di mana duplikasi memperbesar churn.
Tim tanpa kepemilikan dan rencana sinkronisasi yang jelas (trigger, CDC/pekerjaan, kebijakan penyegaran, pemantauan drift). Tanpa ini, Anda berisiko lebih merusak daripada membantu!
Dataset cukup kecil sehingga indeks penutup atau cache sudah membuatnya cepat.
Anda benar-benar ingin menggunakan denormalisasi di mana itu memberi Anda kecepatan baca paling besar dengan beban operasional tambahan paling kecil, dan hanya setelah Anda menyingkirkan perbaikan yang lebih murah.
Jika Anda menyadari bahwa denormalisasi belum tepat untuk saat ini, tetaplah di sini! Kita akan mengeksplorasi alternatif denormalisasi di bagian berikutnya.
Alternatif untuk Denormalisasi
Denormalisasi bukan jawaban untuk semua masalah Anda. Sebelum Anda menambahkan redundansi, Anda perlu memaksimalkan mesin dan aplikasi Anda. Perbaikan ini lebih murah untuk dipelihara dan sering kali memberikan hasil yang sama.
1) Pengindeksan
Indeks komposit/penutup: Dalam kueri SQL Anda, letakkan kolom filter terlebih dahulu, lalu kolom GROUP BY/ORDER BY. Saya sertakan kolom daftar pilihan agar mesin dapat melayani kueri hanya dari indeks.
Indeks terfilter/parsial: Indeks hanya irisan panas (mis., status = 'ACTIVE'), menjaga indeks tetap kecil dan cepat.
Indeks ekspresi/fungsional: Indeks pada LOWER(email) atau date_trunc('day', created_at) untuk menghindari pemindaian terhitung.
2) Penyetelan kueri & paginasi
Hindari SELECT *. Ambil hanya yang Anda tampilkan.
Ganti join yang tidak perlu dengan EXISTS/SEMI saat Anda hanya perlu memeriksa keberadaan.
Dorong predikat ke bawah: filter lebih awal, agregasi belakangan.
Gunakan paginasi keyset (WHERE created_at < ? ORDER BY created_at DESC LIMIT 50) untuk scroll yang stabil dan cepat.
3) Caching
Gunakan cache aplikasi (mis., Redis) untuk kueri panas dan fragmen yang dirender.
Gunakan caching HTTP (ETag/Last-Modified) untuk halaman publik dan dasbor.
Cache berumur pendek (30–120 dtk) sering menghilangkan kebutuhan perubahan skema.
4) Replika baca
Alihkan beban baca berat ke replika. Sangat bagus untuk dasbor dan ekspor.
5) Partisi & pruning
Partisi Rentang/Hash tabel besar sehingga pemindaian Anda hanya menyentuh partisi yang relevan (mis., 30 hari terakhir). Ini bukan denormalisasi, omong-omong, ini sekadar mengurangi jumlah data yang dipindai.
6) Penyimpanan kolumnar/OLAP
Pindahkan analitik berat ke Snowflake/BigQuery/ClickHouse (melalui ELT/dbt). Pertahankan OLTP ternormalisasi dan biarkan gudang data menangani bentuk lebar yang ramah pemindaian.
7) Kebersihan ORM
Hilangkan kueri N+1 (eager-load atau batch), tetapkan daftar pilihan yang masuk akal, dan batasi ukuran halaman. Lapisan ORM yang bersih dapat menghilangkan kebutuhan untuk mendenormalisasi.
8) Kolom terhitung/tergenerasi (dipelihara DB)
Biarkan DB memelihara nilai turunan (mis., price_with_tax) sebagai kolom terhitung/tergenerasi atau melalui indeks ekspresi. Itu akan memberi Anda baca cepat tanpa logika sinkronisasi di tingkat aplikasi.
Teknik Denormalisasi
Berikut cara paling umum untuk menambahkan redundansi terkontrol ke model ternormalisasi. Untuk masing-masing, saya akan menunjukkan apa fungsinya, kapan menggunakannya, dan cara menjaganya tetap sinkron.
Pengaturan: anggap inti ternormalisasi dengan customers, orders, order_items, products.
1) Tabel “proyeksi” yang diratakan / pra-join
Apa: Buat tabel yang mempra-join kolom yang Anda butuhkan untuk baca cepat (mis., daftar produk atau dasbor pesanan).
Kapan: Jalur panas Anda menggabungkan 3 hingga 5 tabel dengan cara yang dapat diprediksi.
Cara (PostgreSQL):
-- Read-optimised projection for a typical orders listCREATETABLE orders_projection (
order_id BIGINTPRIMARYKEY,
created_day DATENOTNULL,
customer_id BIGINTNOTNULL,
customer_name TEXTNOTNULL,
total_amount NUMERIC(12,2)NOTNULL);-- Initial backfillINSERTINTO orders_projection (order_id, created_day, customer_id, customer_name, total_amount)SELECT o.order_id,
o.created_at::dateAS created_day,
c.customer_id,
c.name AS customer_name,SUM(oi.quantity * oi.unit_price)AS total_amount
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
GROUPBY o.order_id, created_day, c.customer_id, c.name;-- Index for fast filtering by day/customerCREATEINDEXON orders_projection (created_day, customer_id);Didukung oleh
Opsi sinkronisasi:
Pekerjaan pasca-tulis (enqueue “order.updated” → hitung ulang baris)
Pekerjaan batch malam/15-menit (dbt/cron)
2) Kolom redundan (menyalin beberapa atribut)
Apa: Duplikasi beberapa atribut yang sering dibaca ke tabel lain untuk menghindari join (mis., orders.customer_name).
Kapan: Anda hanya butuh 1 atau 2 nilai dan tidak ingin proyeksi penuh.
Cara (contoh trigger PostgreSQL):
ALTERTABLE orders
ADDCOLUMN customer_name TEXT,ADDCOLUMN customer_tier TEXT;-- Keep the redundant fields correct on insert/updateCREATEORREPLACEFUNCTION sync_order_customer_fields()RETURNSTRIGGERAS $
BEGINSELECT name, tier INTO NEW.customer_name, NEW.customer_tier
FROM customers WHERE customer_id = NEW.customer_id;RETURN NEW;END;
$ LANGUAGE plpgsql;CREATETRIGGER trg_orders_sync_customer
BEFORE INSERTORUPDATEOF customer_id ON orders
FOR EACH ROWEXECUTEFUNCTION sync_order_customer_fields();-- Propagate customer name/tier changes to existing ordersCREATEORREPLACEFUNCTION propagate_customer_changes()RETURNSTRIGGERAS $
BEGINUPDATE orders
SET customer_name = NEW.name,
customer_tier = NEW.tier
WHERE customer_id = NEW.customer_id;RETURN NEW;END;
$ LANGUAGE plpgsql;CREATETRIGGER trg_customer_propagate
AFTERUPDATEOF name, tier ON customers
FOR EACH ROWEXECUTEFUNCTION propagate_customer_changes();Didukung oleh
Trade-off: Baca sederhana, tetapi tulis menyebar saat pelanggan diperbarui.
3) Atribut turunan (menyimpan nilai terhitung)
Apa: Persistenkan nilai yang sebaliknya Anda hitung langsung (mis., price_with_tax, latest_comment_at, item_count).
Kapan: Perhitungannya dilakukan terus-menerus dan deterministik.
Dua pola umum:
Kolom ter-generasi (DB menghitung ulang saat tulis: kontrol Anda lebih sedikit, tidak ada referensi lintas tabel)
Kolom tersimpan + trigger/pekerjaan (Anda mengontrol kapan/bagaimana ia berubah)
-- Generated column example (Postgres 12+; same-table expressions)ALTERTABLE products
ADDCOLUMN price_with_tax NUMERIC(12,2) GENERATED ALWAYS AS(price *1.20) STORED;-- Counter maintained by triggers (likes per post)ALTERTABLE posts ADDCOLUMN like_count INTEGERNOTNULLDEFAULT0;CREATEORREPLACEFUNCTION inc_like_count()RETURNSTRIGGERAS $
BEGINUPDATE posts SET like_count = like_count +1WHERE post_id = NEW.post_id;RETURN NEW;END; $ LANGUAGE plpgsql;CREATETRIGGER trg_like_insert
AFTERINSERTON post_likes
FOR EACH ROWEXECUTEFUNCTION inc_like_count();CREATEORREPLACEFUNCTION dec_like_count()RETURNSTRIGGERAS $
BEGINUPDATE posts SET like_count = GREATEST(like_count -1,0)WHERE post_id = OLD.post_id;RETURN OLD;END; $ LANGUAGE plpgsql;CREATETRIGGER trg_like_delete
AFTERDELETEON post_likes
FOR EACH ROWEXECUTEFUNCTION dec_like_count();Didukung oleh
4) Tabel agregat / ringkasan
Apa: Prahitung ringkasan seperti pendapatan harian, pengguna aktif per hari, pesanan per kategori.
Kapan: Dasbor mengulang group-by yang sama dan pemindaian mentah mahal.
CREATETABLE daily_sales (
sales_day DATEPRIMARYKEY,
gross_amount NUMERIC(14,2)NOTNULL,
order_count INTEGERNOTNULL);-- Incremental upsert for "yesterday" (run hourly/15-min)INSERTINTO daily_sales (sales_day, gross_amount, order_count)SELECT(o.created_at AT TIME ZONE 'UTC')::dateAS sales_day,SUM(oi.quantity * oi.unit_price)AS gross_amount,COUNT(DISTINCT o.order_id)AS order_count
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.created_at >= date_trunc('day',now()-interval'1 day')AND o.created_at < date_trunc('day',now())GROUPBY sales_day
ON CONFLICT (sales_day)DOUPDATESET gross_amount = EXCLUDED.gross_amount,
order_count = EXCLUDED.order_count;Didukung oleh
Opsi sinkronisasi: pekerjaan terjadwal (cron/dbt), pembaruan streaming (CDC), atau agregator berbasis peristiwa.
5) Tampilan termaterialisasi (hasil kueri yang dipersistenkan)
Apa: Simpan keluaran kueri mahal sebagai tabel fisik yang dapat Anda indeks.
Kapan: Himpunan hasilnya mahal, relatif stabil, dan Anda dapat mentolerir jendela penyegaran.
-- Expensive reportCREATE MATERIALIZED VIEW mv_category_sales ASSELECT p.category_id,
date_trunc('day', o.created_at)ASday,SUM(oi.quantity * oi.unit_price)AS revenue
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id
GROUPBY p.category_id, date_trunc('day', o.created_at);-- Make it fast to queryCREATEINDEXON mv_category_sales (category_id,day);-- Refresh patterns-- Full, blocking:
REFRESH MATERIALIZED VIEW mv_category_sales;-- Non-blocking reads (requires unique index on the MV):-- 1) ensure a unique index exists (e.g., (category_id, day))-- 2) then:
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_category_sales;Didukung oleh
Trade-off: Anda akan mengelola irama penyegaran karena penyegaran inkremental tidak native di Postgres. Meski begitu, ini cukup mudah saat Anda menggunakan dbt atau pola “hanya-tambah + bangun ulang berjendela” kustom.
6) Proyeksi “dokumen” JSON prahitung
Apa: Simpan blob JSON terdenormalisasi yang sesuai dengan kebutuhan API/UI Anda (mis., kartu produk), bersama indeks JSON terarah.
Kapan: API Anda menyajikan bentuk yang sama berulang kali, dan Anda menginginkan satu kali baca.
CREATETABLE product_cards (
product_id BIGINTPRIMARYKEY,
card_json JSONB NOTNULL);-- Build or rebuild card documentsINSERTINTO product_cards (product_id, card_json)SELECT p.product_id,
jsonb_build_object('id', p.product_id,'name', p.name,'brand', b.name,'price_with_tax', p.price *1.20,'category', c.name
)FROM products p
JOIN brands b ON b.brand_id = p.brand_id
JOIN categories c ON c.category_id = p.category_id
ON CONFLICT (product_id)DOUPDATESET card_json = EXCLUDED.card_json;-- GIN index for JSON queries if neededCREATEINDEX idx_product_cards_gin ON product_cards USING GIN (card_json);Didukung oleh
Opsi sinkronisasi: bangun ulang berbasis peristiwa saat produk/merek/kategori berubah, atau penyegaran batch sering untuk produk yang baru diperbarui.
Memilih strategi sinkronisasi (panduan cepat)
Trigger: Langsung dan memastikan konsistensi transaksional. Umumnya bagus untuk fan-out kecil dan volume tulis rendah tetapi dapat merusak latensi tulis jika berlebihan.
Dual-write aplikasi: Aplikasi menulis ke sumber + denormalisasi. Sederhana namun lebih berisiko. Anda dapat menguranginya dengan retry idempoten dan pola outbox/CDC.
CDC → worker: Strategi sinkron ini andal dan dapat diskalakan karena menyebarkan perubahan secara asinkron. Ideal saat konsistensi eventual dapat diterima.
Pekerjaan terjadwal: Opsi paling sederhana untuk agregat dan tampilan termaterialisasi. Cukup pilih jendela penyegaran yang sesuai toleransi UX Anda.
Saya menyarankan agar Anda selalu membangun observabilitas ke dalam solusi Anda. Tidak ada yang lebih buruk daripada tidak yakin apakah perubahan telah diproses atau direplikasi dengan benar. Ini juga dapat membantu Anda menghitung pemeriksaan drift dan skrip backfill.
Cara Menerapkan Denormalisasi Langkah demi Langkah
Seperti biasa, mengutak-atik basis data cukup berisiko. Saya sangat menyarankan Anda mereplikasi basis data dan mencoba menerapkan denormalisasi pada replika terlebih dahulu, untuk memastikan semuanya berperilaku seperti yang Anda harapkan. Jika Anda memiliki lingkungan pengembangan, lebih baik lagi!
Prosesnya cukup sederhana: ukur → ubah sesedikit mungkin → jaga tetap benar → verifikasi → pantau → ulangi.
1) Profilkan dan tetapkan target keberhasilan
Tangkap kueri persisnya (teks + parameter) dan frekuensinya.
Catat baseline: EXPLAIN (ANALYZE, BUFFERS) plan, latensi p95, CPU/IO, baris yang dipindai.
Sepakati kriteria keberhasilan (misalnya “p95 < 120 ms dengan biaya tulis ≤1,2×”).
Konfirmasikan toleransi konsistensi (mis., “analitik boleh basi 5 menit”).
2) Pilih denormalisasi terkecil yang berhasil
Idealnya coba satu kolom redundan atau satu tabel ringkasan daripada proyeksi besar.
Sketsa bentuk baru:
Kolom redundan? (yang mana, mengapa)
Tabel agregat atau tampilan termaterialisasi? (butir, kunci, jendela penyegaran)
Tentukan model konsistensi:
Kuat (trigger/transaksional) vs eventual (penyegaran CDC/pekerjaan/tampilan termaterialisasi).
Tuliskan anggaran amplifikasi tulis (berapa banyak tulis ekstra per peristiwa yang dapat diterima).
Aplikasi menulis ke tabel sumber dan satu baris outbox dalam transaksi yang sama.
Worker membaca outbox dan memperbarui target terdenormalisasi secara idempoten.
Pekerjaan terjadwal / penyegaran MV (bagus untuk agregat):
Tentukan irama penyegaran, jendela, dan strategi backfill.
Idempotensi tidak bisa ditawar. Pembaruan harus aman untuk diulang (mis., UPSERT dengan prahitung deterministik). Percayalah, Anda akan berterima kasih pada diri sendiri nanti.
4) Buat, backfill, dan validasi
Buat struktur (tabel, kolom, indeks, trigger/pekerjaan) tetapi biarkan aplikasi tidak berubah.
Backfill dari sumber kebenaran.
Validasi paritas dengan invarian:
-- Example parity check: orders_projection vs live joinSELECTCOUNT(*)AS mismatches
FROM orders_projection p
JOIN orders o ON o.order_id = p.order_id
JOIN customers c ON c.customer_id = o.customer_id
WHERE p.customer_name <> c.name
OR p.total_amount <>(SELECTSUM(oi.quantity * oi.unit_price)FROM order_items oi WHERE oi.order_id = o.order_id
);Didukung oleh
4. Perbaiki ketidakcocokan; jalankan ulang hingga nol (atau dalam anggaran galat yang disepakati).
5) Alihkan dengan aman
Kirim perubahan aplikasi di balik feature flag atau canary (mis., 10% trafik membaca dari jalur terdenormalisasi).
Jalankan shadow read: hitung hasil lama di latar belakang dan bandingkan hash/agregat untuk sebagian permintaan.
Simpan rollback cepat (beralih kembali ke jalur ternormalisasi seketika).
6) Pantau hal yang tepat
Buat dasbor kecil di alat observabilitas pilihan Anda:
Baca: latensi p50/p95, baris terbaca, buffer hit, stabilitas rencana kueri.
Tulis: waktu tulis ekstra, galat trigger/pekerjaan, kelambatan antrean (CDC), stempel waktu penyegaran MV terakhir.
Kualitas data: penghitung drift (pemeriksaan paritas harian), jumlah baris yang di-backfill, % ketidakcocokan.
Biaya/jejak: ukuran tabel, bloat indeks, pertumbuhan ukuran MV.
7) Operasikan & iterasikan
Tetapkan kepemilikan (tim/sistem on-call) dan runbook (cara membangun ulang, backfill, memperbaiki). Ini sering terlewat, tetapi Anda akan ingin bisa merespons secepat mungkin saat insiden terjadi.
Evaluasi ulang tiap kuartal: masih diperlukan? Masih bentuk yang tepat? Bisakah kita sederhanakan? Dokumentasikan keputusan dan tautkan ke dasbor.
Kelebihan dan Kekurangan Denormalisasi
Kita telah membahas detailnya di tabel Normalisasi vs Denormalisasi pada bagian sebelumnya, jadi berikut ringkasan cepat.
Yang Anda dapatkan
Baca lebih cepat dan latensi p95/p99 lebih prediktabel
Kueri lebih sederhana pada jalur panas (lebih sedikit join/agregasi)
CPU/IO per baca lebih rendah dan kueri analitik lebih murah
Biayanya
Amplifikasi tulis (pembaruan/penyisipan ekstra)
Risiko konsistensi (data basi/menyimpang kecuali Anda menyinkronkan dengan baik)
Penyebaran perubahan (pembaruan skema/logika harus dicerminkan)
Alat dan Teknologi yang Mendukung Denormalisasi
Tidak semua basis data membantu Anda mendenormalisasi dengan cara yang sama. Beberapa memberi fitur kelas satu untuk mempersistenkan hasil kueri mahal sementara yang lain mengharapkan Anda membangunnya sendiri dengan pekerjaan, trigger, atau pipeline.
PostgreSQL
Postgres memberi Anda beberapa cara untuk mematerialisasi bentuk yang dioptimalkan baca.
Tampilan termaterialisasi mempersistenkan hasil kueri, sehingga baca menjadi instan. Anda memilih kapan menyegarkan (terjadwal atau ad hoc), dan Anda bahkan dapat menyegarkan secara bersamaan sehingga pembaca tidak diblokir.
Untuk duplikasi ringan, kolom ter-generasi dan indeks ekspresi memungkinkan basis data memelihara nilai turunan dan mempercepat filter umum tanpa menulis logika sinkronisasi ekstra.
Saat Anda memerlukan konsistensi kuat antara sumber dan salinan terdenormalisasi, Anda dapat menggunakan trigger untuk menjaga sinkron (namun ingat, trigger menambah pekerjaan pada jalur tulis!). Untuk penyegaran berbasis peristiwa atau batch, Anda dapat memasangkan Postgres dengan penjadwal (mis., pg_cron atau job runner eksternal) atau dengan replikasi logis/CDC untuk mengalirkan perubahan ke worker yang memperbarui tabel terdenormalisasi Anda secara asinkron.
Jika Anda ingin praktik langsung dan membuat basis data sendiri serta menguji denormalisasi, silasan lihat kursus PostgreSQL kami.
SQL Server
SQL Server sangat mengandalkan indexed views, semacam tampilan termaterialisasi yang selalu aktif, di mana mesin menjaga tampilan tetap sinkron pada setiap insert/update/delete, sehingga baca sangat cepat. Sisi lainnya adalah tulis kini memelihara tabel dasar dan tampilan, sehingga beban OLTP berat dapat melambat.
Selain itu, Anda dapat memanfaatkan kolom terhitung yang dipersistenkan untuk menutup atribut turunan baris tunggal.
Untuk pelaporan skala besar, indeks columnstore adalah alternatif denormalisasi: mereka mengompresi dan memindai dataset besar dengan sangat efisien, yang dapat menghilangkan kebutuhan menduplikasi data sejak awal.
Oracle
Materialized Views Oracle adalah opsi yang matang dengan penyegaran FAST yang menggunakan log perubahan untuk memperbarui hanya yang berubah, atau penyegaran COMPLETE saat Anda baik-baik saja membangun ulang semuanya.
Dengan Query Rewrite, pengoptimal dapat secara transparan menggunakan materialized view Anda saat seseorang mengkueri tabel dasar, sehingga tim mendapatkan peningkatan kecepatan tanpa mengubah SQL aplikasi. Seperti biasa, baca lebih cepat berarti ada pekerjaan ekstra di suatu tempat: memelihara log dan menyegarkan tampilan meningkatkan beban tulis dan operasional.
MySQL / MariaDB
MySQL tidak memiliki tampilan termaterialisasi native, jadi denormalisasi biasanya dibangun dengan tabel fisik plus pekerjaan terjadwal atau trigger untuk menjaganya tetap terbaru.
Kolom ter-generasi membantu untuk nilai turunan sederhana. Ini bekerja baik untuk dasbor dan ringkasan yang dapat diprediksi, namun sekali lagi, ada baiknya bersikap sengaja tentang irama penyegaran dan kompleksitas trigger agar Anda tidak tanpa sengaja membebani jalur tulis yang kritis. Banyak tim menggabungkannya dengan replika baca untuk sepenuhnya mengalihkan kueri pelaporan.
Snowflake / BigQuery (lapisan analitik)
Gudang kolumnar dibuat untuk data terdenormalisasi yang lebar dan ramah pemindaian. Anda biasanya memodelkan tabel fakta lebar dengan atribut bermanfaat yang sudah disematkan, lalu mengandalkan partisi/klastering untuk memangkas yang dipindai.
Kedua platform mendukung tampilan termaterialisasi dan tugas/kueri terjadwal untuk menjaga agregat tetap segar tanpa menyentuh sistem OLTP. Anda menukar biaya penyimpanan dan penyegaran untuk baca yang sangat prediktabel dan murah dalam skala besar. Ini sempurna untuk dasbor dan BI.
dbt dikenal sebagai lapisan “infrastruktur sebagai kode” untuk analitik terdenormalisasi. Anda mendefinisikan model sekali, memilih materialisasi (tabel, tampilan, inkremental), dan membiarkan dbt menangani build, dependensi, dan pengujian. Model inkremental sangat berguna untuk tabel ringkasan yang hanya perlu data baru ditambahkan dan digabung. Ini dengan mudah menjadi salah satu alat favorit saya!
CDC & pipeline data
Saat Anda ingin perubahan aplikasi mengalir ke struktur terdenormalisasi secara andal, Change Data Capture adalah andalannya. Alat seperti Debezium (self-hosted) atau konektor terkelola (Fivetran, Airbyte) mengalirkan perubahan tingkat baris dari OLTP ke worker atau gudang yang memperbarui tabel proyeksi, penghitung, atau agregat. Ini biasanya konsisten secara eventual (bagus untuk dasbor dan umpan), dan berskala jauh lebih baik daripada memasukkan semuanya ke dalam trigger. Buat pembaruan bersifat idempoten, pantau kelambatan, dan simpan jalur backfill untuk peristiwa yang terlambat atau terlewat.
Intisari & Pemikiran Akhir
Denormalisasi adalah optimasi pragmatis yang dilapiskan di atas desain ternormalisasi, bukan penolakannya. Anda menukar penyimpanan dan kompleksitas tulis untuk baca yang lebih cepat dan lebih sederhana pada jalur yang spesifik dan dipahami dengan baik.
Ingat:
Mulailah dengan ternormalisasi. Gunakan denormalisasi hanya di mana kueri bernama dan terukur membutuhkannya.
Habiskan perbaikan yang lebih murah terlebih dahulu (indeks, penulisan ulang kueri, caching, replika, OLAP).
Pilih denormalisasi sekecil mungkin yang memberikan hasil (satu kolom redundan, agregat kecil, atau tampilan termaterialisasi).
Bersikaplah eksplisit tentang kesegaran (konsistensi kuat versus eventual) dan bangun sinkronisasi yang idempoten.
Ukur sebelum/sesudah, pantau drift, dan siapkan sakelar rollback.
Perlakukan tabel ternormalisasi sebagai sumber kebenaran! Bagian terdenormalisasi adalah salinan yang dioptimalkan baca yang dapat Anda bangun ulang.
Penulis
Marie Fayard
Saya adalah tech lead berorientasi produk yang berspesialisasi dalam mengembangkan startup tahap awal dari prototipe pertama hingga mencapai product-market fit dan seterusnya. Saya sangat penasaran dengan cara orang menggunakan teknologi, dan saya senang bekerja erat dengan para founder serta tim lintas fungsi untuk mewujudkan ide-ide berani. Saat tidak membangun produk, saya mencari inspirasi di berbagai penjuru dunia atau melepas penat di studio yoga.
Kuasai pertanyaan wawancara basis data, dari konsep SQL dasar hingga skenario desain sistem tingkat lanjut. Panduan mendalam ini mencakup semua yang Anda perlukan untuk sukses di wawancara DBMS dan meraih peran berikutnya.
Temukan alasan mengapa Anda sebaiknya (tidak) menggunakan spaghetti plot untuk menyampaikan ketidakpastian jalur prediksi badai serta dampaknya terhadap interpretasi.
Saya telah banyak menulis tentang normalisasi dan mengapa itu merupakan fondasi yang andal untuk integritas data. Ini bukan berarti saya menarik ucapan saya. Saya tetap percaya bahwa skema yang ternormalisasi dengan baik adalah titik awal yang tepat untuk sebagian besar sistem transaksional. Artikel ini membahas pilihan sadar untuk menyimpang dari kemurnian itu dalam situasi sangat spesifik ketika kinerja baca lebih penting daripada bentuk normal yang ketat.
Denormalisasi bukan berarti melewatkan normalisasi. Ini adalah optimasi kinerja yang Anda terapkan pada model yang sudah ternormalisasi ketika kueri nyata, pengguna nyata, dan SLA nyata memberi tahu Anda bahwa join dan perhitungan langsung terlalu lambat atau terlalu mahal. Dalam praktiknya, Anda menukar baca yang lebih cepat dan kueri yang lebih sederhana dengan lebih banyak penyimpanan, penulisan yang lebih kompleks, dan pekerjaan konsistensi tambahan.
Dalam artikel ini, saya akan menunjukkan di mana denormalisasi berguna dan kapan tidak, serta cara menerapkannya dengan aman di basis data SQL. Tujuannya bukan untuk meninggalkan desain yang baik, melainkan menambahkan jalan pintas di atasnya ketika beban kerja membenarkannya.
Apa itu Denormalisasi dalam Basis Data?
Jika Anda familier dengan normalisasi dan desain basis data, berikut jawaban singkatnya:
Denormalisasi adalah tindakan sengaja menambahkan data redundan ke skema yang sebelumnya ternormalisasi untuk mempercepat baca dan menyederhanakan kueri. Ini adalah optimasi kinerja yang terarah, bukan alasan untuk melewatkan pemodelan yang baik!
Jika Anda relatif baru dalam desain basis data, ada baiknya mengurai makna dari “ternormalisasi”, “terdenormalisasi”, dan “tidak ternormalisasi”. Anda sering mendengar 3 istilah itu secara daring, dan penting untuk tidak mencampuradukkannya.
Ternormalisasi: Data dipecah ke dalam tabel yang terstruktur dengan baik yang meminimalkan redundansi dan melindungi integritas (lihat 3NF/BCNF).
Terdenormalisasi: Anda memperkenalkan kembali redundansi yang selektif dengan kolom tambahan, nilai prahitung, atau tabel pra-join di atas model yang ternormalisasi itu untuk membuat baca umum lebih cepat. Dengan denormalisasi, Anda menjaga satu sumber kebenaran (tabel ternormalisasi), lalu memelihara satu atau lebih representasi yang lebih cepat untuk jalur panas, seperti dasbor, daftar produk, penelusuran, dll. Anda menukar penyimpanan + kompleksitas tulis dengan kecepatan baca + kueri yang lebih sederhana.
Tidak ternormalisasi: Data mentah, ad-hoc, atau berantakan di mana struktur dan kendalinya tidak pernah dirancang dengan benar. Bukan itu yang kita lakukan di sini.
Contoh dalam SQL
Berikut contoh kecil dalam SQL untuk membantu Anda memvisualisasikan perbedaan antara pemodelan ternormalisasi dan terdenormalisasi.
Pemodelan ternormalisasi
Terdenormalisasi untuk jalur pelaporan
Dalam contoh ini, Anda tetap menjadikan customers sebagai sumber kebenaran dan memastikan
orders.customer_name / orders.customer_tiertetap sinkron (mis., trigger, pekerjaan CDC, atau backfill terjadwal). Sumber kebenaran yang sama, dua representasi, masing-masing dioptimalkan untuk tugas yang berbeda.Apakah denormalisasi adalah desain yang buruk?
Ada kesalahpahaman umum bahwa denormalisasi hanyalah produk dari desain yang buruk. Itu tidak benar jika diukur, diberi ruang lingkup, dan dipelihara. Ya, denormalisasi sering melanggar bentuk normal yang lebih tinggi (memang itulah tujuannya), tetapi itu disengaja dan didukung oleh rencana untuk menjaga konsistensi. Desain yang buruk adalah melewatkan normalisasi sama sekali atau menaburkan redundansi tanpa strategi sinkronisasi.
Jika Anda ingin mempelajari lebih lanjut tentang desain basis data, kursus pemula kami tentang topik ini adalah tempat yang baik untuk memulai!
Normalisasi vs Denormalisasi
Berikut perbandingan berdampingan. Ini tidak lengkap, tetapi akan membantu Anda memahami trade-off dari masing-masing pendekatan.
Aspek
Normalisasi
Denormalisasi
Tujuan utama
Integritas, redundansi minimal, pembaruan mudah & benar
Baca lebih cepat, kueri lebih sederhana pada jalur panas
Terbaik untuk
Sistem OLTP dengan tulis/pembaruan sering
Dasbor baca-berat, halaman penelusuran/daftar, pelaporan/analitik
Kinerja baca
Sering memerlukan join, bagus dengan indeks yang tepat
Lebih sedikit join, bisa jauh lebih cepat dan lebih prediktabel
Kompleksitas tulis
Sederhana: satu sumber kebenaran
Lebih tinggi: harus memperbarui/menyinkronkan salinan atau agregat redundan
Penyimpanan
Hemat
Lebih besar (kolom/tabel ekstra, tampilan prahitung)
Integritas data
Kuat secara desain (kendala 3NF/BCNF)
Memerlukan mekanisme untuk mencegah drift (trigger, CDC, pekerjaan)
Kecepatan perubahan
Ganti nama/perbarui kolom bersifat lokal
Perubahan dapat menyebar ke data yang diduplikasi
Beban operasional
Lebih rendah: lebih sedikit komponen bergerak
Lebih tinggi: kebijakan penyegaran, backfill, pemantauan
Mode kegagalan
Kueri N+1, join lambat, indeks hilang
Data basi, inkonsistensi, amplifikasi tulis
Evolusi skema
Prediktabel, mudah direfaktor
Memerlukan rencana migrasi untuk representasi redundan
Contoh tipikal
Pesanan, pelanggan, transaksi
Daftar produk pra-join; tabel penjualan agregat; tampilan termaterialisasi/berindeks
Mengapa dan Kapan Melakukan Denormalisasi
Denormalisasi menjadi sangat berguna ketika pengguna nyata dan kueri nyata terhambat oleh join, agregasi, atau lookup berulang. Setelah Anda memastikan bahwa pengindeksan, penyetelan kueri, dan caching tidak cukup, Anda mungkin ingin beralih ke denormalisasi untuk mengoptimalkan baca cepat pada pola akses yang dapat diprediksi.
Kapan denormalisasi mungkin membantu
Kasus penggunaan klasik
Kapan tidak melakukan denormalisasi dan gunakan hal lain terlebih dahulu
Anda benar-benar ingin menggunakan denormalisasi di mana itu memberi Anda kecepatan baca paling besar dengan beban operasional tambahan paling kecil, dan hanya setelah Anda menyingkirkan perbaikan yang lebih murah.
Jika Anda menyadari bahwa denormalisasi belum tepat untuk saat ini, tetaplah di sini! Kita akan mengeksplorasi alternatif denormalisasi di bagian berikutnya.
Alternatif untuk Denormalisasi
Denormalisasi bukan jawaban untuk semua masalah Anda. Sebelum Anda menambahkan redundansi, Anda perlu memaksimalkan mesin dan aplikasi Anda. Perbaikan ini lebih murah untuk dipelihara dan sering kali memberikan hasil yang sama.
1) Pengindeksan
Indeks komposit/penutup: Dalam kueri SQL Anda, letakkan kolom filter terlebih dahulu, lalu kolom
GROUP BY/ORDER BY. Saya sertakan kolom daftar pilihan agar mesin dapat melayani kueri hanya dari indeks.Indeks terfilter/parsial: Indeks hanya irisan panas (mis.,
status = 'ACTIVE'), menjaga indeks tetap kecil dan cepat.Indeks ekspresi/fungsional: Indeks pada
LOWER(email)ataudate_trunc('day', created_at)untuk menghindari pemindaian terhitung.2) Penyetelan kueri & paginasi
Hindari
SELECT *. Ambil hanya yang Anda tampilkan.Ganti join yang tidak perlu dengan
EXISTS/SEMIsaat Anda hanya perlu memeriksa keberadaan.Dorong predikat ke bawah: filter lebih awal, agregasi belakangan.
Gunakan paginasi keyset (
WHERE created_at < ? ORDER BY created_at DESC LIMIT 50) untuk scroll yang stabil dan cepat.3) Caching
4) Replika baca
5) Partisi & pruning
6) Penyimpanan kolumnar/OLAP
7) Kebersihan ORM
8) Kolom terhitung/tergenerasi (dipelihara DB)
Biarkan DB memelihara nilai turunan (mis.,
price_with_tax) sebagai kolom terhitung/tergenerasi atau melalui indeks ekspresi. Itu akan memberi Anda baca cepat tanpa logika sinkronisasi di tingkat aplikasi.Teknik Denormalisasi
Berikut cara paling umum untuk menambahkan redundansi terkontrol ke model ternormalisasi. Untuk masing-masing, saya akan menunjukkan apa fungsinya, kapan menggunakannya, dan cara menjaganya tetap sinkron.
Pengaturan: anggap inti ternormalisasi dengan customers, orders, order_items, products.
1) Tabel “proyeksi” yang diratakan / pra-join
Apa: Buat tabel yang mempra-join kolom yang Anda butuhkan untuk baca cepat (mis., daftar produk atau dasbor pesanan).
Kapan: Jalur panas Anda menggabungkan 3 hingga 5 tabel dengan cara yang dapat diprediksi.
Cara (PostgreSQL):
Opsi sinkronisasi:
2) Kolom redundan (menyalin beberapa atribut)
Apa: Duplikasi beberapa atribut yang sering dibaca ke tabel lain untuk menghindari join (mis., orders.customer_name).
Kapan: Anda hanya butuh 1 atau 2 nilai dan tidak ingin proyeksi penuh.
Cara (contoh trigger PostgreSQL):
Trade-off: Baca sederhana, tetapi tulis menyebar saat pelanggan diperbarui.
3) Atribut turunan (menyimpan nilai terhitung)
Apa: Persistenkan nilai yang sebaliknya Anda hitung langsung (mis.,
price_with_tax,latest_comment_at,item_count).Kapan: Perhitungannya dilakukan terus-menerus dan deterministik.
Dua pola umum:
4) Tabel agregat / ringkasan
Apa: Prahitung ringkasan seperti pendapatan harian, pengguna aktif per hari, pesanan per kategori.
Kapan: Dasbor mengulang group-by yang sama dan pemindaian mentah mahal.
Opsi sinkronisasi: pekerjaan terjadwal (cron/dbt), pembaruan streaming (CDC), atau agregator berbasis peristiwa.
5) Tampilan termaterialisasi (hasil kueri yang dipersistenkan)
Apa: Simpan keluaran kueri mahal sebagai tabel fisik yang dapat Anda indeks.
Kapan: Himpunan hasilnya mahal, relatif stabil, dan Anda dapat mentolerir jendela penyegaran.
Trade-off: Anda akan mengelola irama penyegaran karena penyegaran inkremental tidak native di Postgres. Meski begitu, ini cukup mudah saat Anda menggunakan dbt atau pola “hanya-tambah + bangun ulang berjendela” kustom.
6) Proyeksi “dokumen” JSON prahitung
Apa: Simpan blob JSON terdenormalisasi yang sesuai dengan kebutuhan API/UI Anda (mis., kartu produk), bersama indeks JSON terarah.
Kapan: API Anda menyajikan bentuk yang sama berulang kali, dan Anda menginginkan satu kali baca.
Opsi sinkronisasi: bangun ulang berbasis peristiwa saat produk/merek/kategori berubah, atau penyegaran batch sering untuk produk yang baru diperbarui.
Memilih strategi sinkronisasi (panduan cepat)
Saya menyarankan agar Anda selalu membangun observabilitas ke dalam solusi Anda. Tidak ada yang lebih buruk daripada tidak yakin apakah perubahan telah diproses atau direplikasi dengan benar. Ini juga dapat membantu Anda menghitung pemeriksaan drift dan skrip backfill.
Cara Menerapkan Denormalisasi Langkah demi Langkah
Seperti biasa, mengutak-atik basis data cukup berisiko. Saya sangat menyarankan Anda mereplikasi basis data dan mencoba menerapkan denormalisasi pada replika terlebih dahulu, untuk memastikan semuanya berperilaku seperti yang Anda harapkan. Jika Anda memiliki lingkungan pengembangan, lebih baik lagi!
Prosesnya cukup sederhana: ukur → ubah sesedikit mungkin → jaga tetap benar → verifikasi → pantau → ulangi.
1) Profilkan dan tetapkan target keberhasilan
2) Pilih denormalisasi terkecil yang berhasil
3) Rancang jalur sinkronisasi
Idempotensi tidak bisa ditawar. Pembaruan harus aman untuk diulang (mis.,
UPSERTdengan prahitung deterministik). Percayalah, Anda akan berterima kasih pada diri sendiri nanti.4) Buat, backfill, dan validasi
4. Perbaiki ketidakcocokan; jalankan ulang hingga nol (atau dalam anggaran galat yang disepakati).
5) Alihkan dengan aman
6) Pantau hal yang tepat
Buat dasbor kecil di alat observabilitas pilihan Anda:
7) Operasikan & iterasikan
Kelebihan dan Kekurangan Denormalisasi
Kita telah membahas detailnya di tabel Normalisasi vs Denormalisasi pada bagian sebelumnya, jadi berikut ringkasan cepat.
Yang Anda dapatkan
Biayanya
Alat dan Teknologi yang Mendukung Denormalisasi
Tidak semua basis data membantu Anda mendenormalisasi dengan cara yang sama. Beberapa memberi fitur kelas satu untuk mempersistenkan hasil kueri mahal sementara yang lain mengharapkan Anda membangunnya sendiri dengan pekerjaan, trigger, atau pipeline.
PostgreSQL
Postgres memberi Anda beberapa cara untuk mematerialisasi bentuk yang dioptimalkan baca.
Tampilan termaterialisasi mempersistenkan hasil kueri, sehingga baca menjadi instan. Anda memilih kapan menyegarkan (terjadwal atau ad hoc), dan Anda bahkan dapat menyegarkan secara bersamaan sehingga pembaca tidak diblokir.
Untuk duplikasi ringan, kolom ter-generasi dan indeks ekspresi memungkinkan basis data memelihara nilai turunan dan mempercepat filter umum tanpa menulis logika sinkronisasi ekstra.
Saat Anda memerlukan konsistensi kuat antara sumber dan salinan terdenormalisasi, Anda dapat menggunakan trigger untuk menjaga sinkron (namun ingat, trigger menambah pekerjaan pada jalur tulis!). Untuk penyegaran berbasis peristiwa atau batch, Anda dapat memasangkan Postgres dengan penjadwal (mis., pg_cron atau job runner eksternal) atau dengan replikasi logis/CDC untuk mengalirkan perubahan ke worker yang memperbarui tabel terdenormalisasi Anda secara asinkron.
Jika Anda ingin praktik langsung dan membuat basis data sendiri serta menguji denormalisasi, silasan lihat kursus PostgreSQL kami.
SQL Server
SQL Server sangat mengandalkan indexed views, semacam tampilan termaterialisasi yang selalu aktif, di mana mesin menjaga tampilan tetap sinkron pada setiap insert/update/delete, sehingga baca sangat cepat. Sisi lainnya adalah tulis kini memelihara tabel dasar dan tampilan, sehingga beban OLTP berat dapat melambat.
Selain itu, Anda dapat memanfaatkan kolom terhitung yang dipersistenkan untuk menutup atribut turunan baris tunggal.
Untuk pelaporan skala besar, indeks columnstore adalah alternatif denormalisasi: mereka mengompresi dan memindai dataset besar dengan sangat efisien, yang dapat menghilangkan kebutuhan menduplikasi data sejak awal.
Oracle
Materialized Views Oracle adalah opsi yang matang dengan penyegaran
FASTyang menggunakan log perubahan untuk memperbarui hanya yang berubah, atau penyegaranCOMPLETEsaat Anda baik-baik saja membangun ulang semuanya.Dengan Query Rewrite, pengoptimal dapat secara transparan menggunakan materialized view Anda saat seseorang mengkueri tabel dasar, sehingga tim mendapatkan peningkatan kecepatan tanpa mengubah SQL aplikasi. Seperti biasa, baca lebih cepat berarti ada pekerjaan ekstra di suatu tempat: memelihara log dan menyegarkan tampilan meningkatkan beban tulis dan operasional.
MySQL / MariaDB
MySQL tidak memiliki tampilan termaterialisasi native, jadi denormalisasi biasanya dibangun dengan tabel fisik plus pekerjaan terjadwal atau trigger untuk menjaganya tetap terbaru.
Kolom ter-generasi membantu untuk nilai turunan sederhana. Ini bekerja baik untuk dasbor dan ringkasan yang dapat diprediksi, namun sekali lagi, ada baiknya bersikap sengaja tentang irama penyegaran dan kompleksitas trigger agar Anda tidak tanpa sengaja membebani jalur tulis yang kritis. Banyak tim menggabungkannya dengan replika baca untuk sepenuhnya mengalihkan kueri pelaporan.
Snowflake / BigQuery (lapisan analitik)
Gudang kolumnar dibuat untuk data terdenormalisasi yang lebar dan ramah pemindaian. Anda biasanya memodelkan tabel fakta lebar dengan atribut bermanfaat yang sudah disematkan, lalu mengandalkan partisi/klastering untuk memangkas yang dipindai.
Kedua platform mendukung tampilan termaterialisasi dan tugas/kueri terjadwal untuk menjaga agregat tetap segar tanpa menyentuh sistem OLTP. Anda menukar biaya penyimpanan dan penyegaran untuk baca yang sangat prediktabel dan murah dalam skala besar. Ini sempurna untuk dasbor dan BI.
Ini sedikit lebih maju daripada kursus PostgreSQL kami, jadi jika Anda sudah nyaman dengan basis data silahkan coba pengantar pemodelan data dengan Snowflake.
dbt (pemodelan dan orkestrasi)
dbt dikenal sebagai lapisan “infrastruktur sebagai kode” untuk analitik terdenormalisasi. Anda mendefinisikan model sekali, memilih materialisasi (tabel, tampilan, inkremental), dan membiarkan dbt menangani build, dependensi, dan pengujian. Model inkremental sangat berguna untuk tabel ringkasan yang hanya perlu data baru ditambahkan dan digabung. Ini dengan mudah menjadi salah satu alat favorit saya!
CDC & pipeline data
Saat Anda ingin perubahan aplikasi mengalir ke struktur terdenormalisasi secara andal, Change Data Capture adalah andalannya. Alat seperti Debezium (self-hosted) atau konektor terkelola (Fivetran, Airbyte) mengalirkan perubahan tingkat baris dari OLTP ke worker atau gudang yang memperbarui tabel proyeksi, penghitung, atau agregat. Ini biasanya konsisten secara eventual (bagus untuk dasbor dan umpan), dan berskala jauh lebih baik daripada memasukkan semuanya ke dalam trigger. Buat pembaruan bersifat idempoten, pantau kelambatan, dan simpan jalur backfill untuk peristiwa yang terlambat atau terlewat.
Intisari & Pemikiran Akhir
Denormalisasi adalah optimasi pragmatis yang dilapiskan di atas desain ternormalisasi, bukan penolakannya. Anda menukar penyimpanan dan kompleksitas tulis untuk baca yang lebih cepat dan lebih sederhana pada jalur yang spesifik dan dipahami dengan baik.
Ingat: