10 Secret Optimization Patterns for Odoo at 10Mโ100M Rows
Premis Utama
- ORM dipakai di jalur yang salah
Menggunakan perulangan .write() ORM untuk memperbarui jutaan baris data, bukan injeksi Raw SQL yang 50x jauh lebih efisien.
- Compute chain terlalu panjang
Satu field ter-update memicu domino reaksi rantai recompute puluhan field lain hingga CPU/RAM macet terkunci.
- Record rule terlalu mahal
Mengecek relasi struktur organisasi (cost center, hierarki departemen, company) multi-level berulang kali pada setiap query baris list view.
- Smart button / counter menghitung live
Melakukan SELECT COUNT(*) untuk tabel mutasi raksasa pada saat user sekadar mengklik buka halaman profil form ringan biasa.
- Satu tabel menjadi terlalu gemuk
Tabel transaksional dijejali kolom payload text blob atau JSON log besar sehingga memakan toll I/O dan table bloat membengkak.
- Read analytics bercampur dengan transactional writes
Direktur Finance me-refresh laporan tahunan yang berat di tabel yang sama persis saat kasir lapangan menembak 50 transaksi SO per detik.
- Side effect mail.thread dan tracking tidak dikontrol
Melakukan bulk import API terhadap 1 juta data mutasi yang lalai mematikan context, memicu Odoo memalsukan 1 Juta jejak chatter pesan audit seketika.
- Query yang โcepatโ dipanggil ribuan kali
N+1 Query Issue: Sebuah query Postgres 2ms dipanggil 500.000 iterasi di dalam For/Loop Python Odoo, membuatnya terakumulasi menjadi sistem yang tercekik selama 1000 detik.
- name_search dan domain aktif memukul tabel panas
User mengetik huruf 'A' pada bar Odoo Searchbox, menghasilkan iterasi regex ILIKE '%A%' brutal pada tabel panas dengan 50 juta log riwayat.
- Data lama masih ikut bertarung dengan data aktif harian
Mutasi SO dari era tahun 2012 masih terekam bertumpuk sejajar dengan catatan mutasi menit ini, merusak Vacuum scan index yang tak dipartisi.
Master Architecture
Low Latency
Ribuan kali diklik user. Dilarang keras baca dari tabel transaksi! Jalur ini MURNI diarahkan (di-bypass) membaca data dari lapis pelindung ekor (Snapshot/Summary).
Mission Critical
Trigger transaksi mentah masuk menyerang server dari ribuan kasir secara konkuren di detik yang sama.
Memeriksa validasi Create & Write secara instan. Menolak komputasi berlebihan di tahap kritikal ini.
Menyimpan wujud murni transaksi dalam hitungan milidetik. Data usang langsung dipartisi agar tidak membuat tabel ini gembrot di tengah jalan.
Async Horizon
Notifikasi Mail.thread, Recompute harga LTV, dan Audit Track ditendang jauh-jauh dari Hot Path masuk ke kandang antrean outbox.
Pasukan robot pekerja keras. Bangun, baca antrean kotak pos, telan beban, dan muntahkan perubahannya dengan santai di latar belakang.
Lokasi tabel data pre-komputasi atau Snapshot hasil kunyahan Workers. Inilah yang akan disajikan siap makan untuk lapisan Read Path ke depannya!
Pattern 1 โ Hot Path Isolation: Pisahkan jalur transaksi dan jalur baca
Masalah
- create / confirm / validate
- dashboard & KPI
- smart button & aging
- list view dengan count
Prinsip
Semua beban baca berulang dipindahkan ke summary table, snapshot table, atau denormalized read model.
Solusi
docdoo_partner_financial_snapshot yang akan dicopas dan diupdate otomatis menggunakan Job/Queue. Dengan ini, form partner jauh lebih ringan.pythonclass PartnerFinancialSnapshot(models.Model): _name = class="text-green-400">'docdoo.partner.financial.snapshot' _description = class="text-green-400">'Partner Financial Snapshot' _rec_name = class="text-green-400">'partner_id' partner_id = fields.Many2one(class="text-green-400">'res.partner', required=True, index=True) unpaid_invoice_count = fields.Integer() unpaid_amount = fields.Monetary(currency_field=class="text-green-400">'currency_id') last_invoice_date = fields.Date(index=True)
Pattern 2 โ Rule Flattening: Ratakan access rule supaya domain murah
Masalah
ir.rule. Terlalu kompleksnya multi-company, hierarchy, owner membuat Rule menghasilkan subquery raksasa.Solusi
access_company_id, access_branch_id, visibility_bucket).python@api.depends(class="text-green-400">'company_id', class="text-green-400">'branch_id', class="text-green-400">'department_id') def _compute_access_fields(self): for rec in self: rec.access_company_id = rec.company_id rec.access_branch_id = rec.branch_id rec.owner_user_id = rec.create_uid
ir.rule super sederhana berbekal kolom ini saja.Pattern 3 โ Compute Deferral Matrix
Arahkan kursor Anda ke area di bawah untuk membedah strategi defleksi komputasi Odoo.
1. Immediate
Komputasi krusial yang mutlak dibutuhkan saat merubah data. Contoh: Pengurangan Grand Total secara live saat user mengubah Amount di keranjang belanja. Jika tak instant, aplikasi akan kacau.
2. Near Realtime
Delegasikan komputasi sekunder (seperti sinkronisasi API external, generate PDF Receipt, Pushing Notif) ke tabel Outbox untuk dikerjakan Queue Engine secara asinkron tanpa menahan klik layar user.
3. Batch
Kumpulkan puluhan ribu kalkulasi rumit seperti penyusutan aset bulanan, alokasi stok akhir bulan, atau rekonsiliasi jurnal menjadi satu aksi super raksasa yang berjalan senyap tengah malam.
4. Snapshot
Hitung profil berat (contoh: AR Aging Tier, LTV Partner) hanya tepat saat sebuah dokumen disahkan (Validate), lalu simpan wujud akhirnya. Jangan terobsesi hitung ulang dari nol tiap ada refresh halaman.
Pattern 4 โ Silent Bulk Mode: Bunuh side effect saat bulk/sync
mail.thread, tracking, followers, bus notifications.Kirim context khusus pemblokir
mail.thread saat melakukan insert mesin-ke-mesin / integrasi besar.pythondef _docdoo_silent_context(self): return { class="text-green-400">'tracking_disable': True, class="text-green-400">'mail_notrack': True, class="text-green-400">'mail_create_nosubscribe': True, class="text-green-400">'docdoo_bulk_mode': True, }
Pattern 5 โ Controlled Denormalization: Shadow fields untuk Hot Path
INDEX padanya.sqlclass="text-pink-400 font-bold">CREATE class="text-pink-400 font-bold">INDEX idx_ai_hot_open class="text-pink-400 font-bold">ON aggregation_invoice (channel, invoice_year, invoice_month, bill_cycle, partner_id);
Pattern 6 โ Staging + Merge Pipeline
1. Staging Table SQL
Tampung beban kotor secara masif
Jangan langsung memukul tabel Odoo! Buat tabel 'Dummy' murni di PostgreSQL (misal: docdoo_staging_invoice). Tembak ratusan ribu baris data mentah ke sini secepat kilat memakai COPY atau INSERT massal tanpa memicu constraint Python Odoo.
2. Validate & Deduplicate
Bersihkan residu dan duplikasi
Jalankan kueri DELETE berlapis menggunakan fitur ctid PostgreSQL untuk melenyapkan data ganda secara super-responsif. Tandai baris cacat (is_valid = False) via query SQL murni tanpa memicu iterasi for-loop ORM yang lambat.
3. Merge / Upsert Massal
Suntikkan data bersih ke jantung utama
Gunakan klausa sakti 'INSERT INTO ... ON CONFLICT DO UPDATE' untuk memindahkan leburan data dari Staging langsung bersandar pada Model Utama Odoo. Update 10 juta baris rampung dalam 15 detik, sesuatu yang mustahil dilakukan oleh ORM .create() / .write() biasa.
-- Kecepatan Insert Massal via PostgreSQL On Conflict Do UpdateINSERT INTO docdoo_invoice_line (...)
SELECT s.external_key, p.id ... FROM docdoo_staging_invoice_line s
ON CONFLICT (external_key) DO UPDATE SET amount = EXCLUDED.amount;Pattern 7 โ Fast Lookup Architecture: Percepat pencarian name_search
name_search yang mengobrak-abrik ilike '%text%' di sistem produksi adalah dosa. Pindahkan token pencarian ke tabel terpisah docdoo.partner.lookup lalu letakkan pg_trgm (GIN Index) padas teks gabungannya.Pattern 8 โ Active Window Indexing + Partition + Archive Switch
stock_move, account_move_line) sesuai dengan Date. Larang pencarian Odoo mendeteksi riwayat purba secara default.sqlclass="text-pink-400 font-bold">CREATE class="text-pink-400 font-bold">INDEX idx_move_open_recent class="text-pink-400 font-bold">ON stock_move (company_id, state, date) class="text-pink-400 font-bold">WHERE state IN (class="text-green-400">'confirmed', class="text-green-400">'waiting') class="text-pink-400 font-bold">AND date >= CURRENT_DATE - INTERVAL class="text-green-400">'180 days';
Pattern 9 โ Counter Cache Budgeting: Jangan hitung Live di Smart Buttons
docdoo.partner.counter, bukan mengeksekusi search_count() secara realtime ke tabel berjuta baris. Ini menjaga beban Database tetap dingin.Pattern 10 โ Physical Table Hygiene: Pisahkan Kolom Panas vs Dingin
hot columns (status, nominal) dengan warm/cold columns (log JSON transaksi pembayaran). Tujuannya agar efisiensi HOT Update (Heap Only Tuple) bisa bernafas tanpa melibatkan pergerakan keping disk yang panjang.Implementor Lain Tuning Odoo. Docdoo Mendesain Ulang Alirannya.
- Jangan baca analytics dari tabel transaksi live.
- Jangan biarkan record rule membaca struktur organisasi kompleks setiap query.
- Jangan paksa semua compute menjadi realtime.
- Jangan biarkan chatter dan tracking ikut bulk process.
- Jangan satukan data aktif dan histori dingin dalam pengalaman user harian.