Kembali ke Beranda
Odoo Performance Tuning & 100M Rows Architecture

Blueprint Arsitektur
Odoo ERP Multi-Million Rows

Optimasi Odoo level besar tidak dimenangkan oleh tuning PostgreSQL biasa saja. Ia dimenangkan oleh desain jalur transaksi, desain read model, desain side effect, dan desain lifecycle data.

10 Secret Optimization Patterns for Odoo at 10Mโ€“100M Rows

Premis Utama

Pada volume besar, bottleneck Odoo biasanya datang dari kombinasi ini:
  • 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.

Jadi solusi paling kuat adalah mengubah bentuk beban, bukan sekadar mengurangi durasi satu query.

Master Architecture

1. Read Path (UI)

Low Latency

๐Ÿ“ŠDashboards & Counters

Ribuan kali diklik user. Dilarang keras baca dari tabel transaksi! Jalur ini MURNI diarahkan (di-bypass) membaca data dari lapis pelindung ekor (Snapshot/Summary).

2. Hot Path (Transaction)

Mission Critical

๐ŸŒUsers / Endpoint

Trigger transaksi mentah masuk menyerang server dari ribuan kasir secara konkuren di detik yang sama.

๐Ÿ”ฎOdoo Flow

Memeriksa validasi Create & Write secara instan. Menolak komputasi berlebihan di tahap kritikal ini.

๐Ÿ—„๏ธRaw Postgres Transaction

Menyimpan wujud murni transaksi dalam hitungan milidetik. Data usang langsung dipartisi agar tidak membuat tabel ini gembrot di tengah jalan.

3. Deferred / Side Effects

Async Horizon

๐Ÿ“ซQueue / Outbox

Notifikasi Mail.thread, Recompute harga LTV, dan Audit Track ditendang jauh-jauh dari Hot Path masuk ke kandang antrean outbox.

โš™๏ธBack. Workers

Pasukan robot pekerja keras. Bangun, baca antrean kotak pos, telan beban, dan muntahkan perubahannya dengan santai di latar belakang.

๐Ÿ“ฆSummary Tables

Lokasi tabel data pre-komputasi atau Snapshot hasil kunyahan Workers. Inilah yang akan disajikan siap makan untuk lapisan Read Path ke depannya!

Kalau satu diagram ini dipahami dan dijalankan, separuh perang performa sudah selesai.

Pattern 1 โ€” Hot Path Isolation: Pisahkan jalur transaksi dan jalur baca

Masalah

Banyak implementasi Odoo memaksa satu tabel transaksi live menjadi sumber untuk:
  • create / confirm / validate
  • dashboard & KPI
  • smart button & aging
  • list view dengan count
Ini membuat setiap transaksi ikut menanggung beban baca yang berat.

Prinsip

Jalur transaksi harus sesingkat mungkin.
Semua beban baca berulang dipindahkan ke summary table, snapshot table, atau denormalized read model.

Solusi

Tambahkan tabel snapshot khusus, misalnya docdoo_partner_financial_snapshot yang akan dicopas dan diupdate otomatis menggunakan Job/Queue. Dengan ini, form partner jauh lebih ringan.
python
class 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

Pada dataset besar, banyak query yang lambat bukan karena model utamanya, tapi karena ir.rule. Terlalu kompleksnya multi-company, hierarchy, owner membuat Rule menghasilkan subquery raksasa.

Solusi

Tambahkan "Shadow Access Field" yang sudah dikomputasi awal (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
Lalu jadikan domain 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

Wajib Realtime

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

Tertunda 5โ€“30 detik (Event Outbox)

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

Cron Job / Antrean Massal

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

Dihitung saat event konfirmasi

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

Banyak proses import melambat karena mail.thread, tracking, followers, bus notifications.
Solusi:
Kirim context khusus pemblokir mail.thread saat melakukan insert mesin-ke-mesin / integrasi besar.
python
def _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

Jika fitur filter di Odoo selalu menagih rentetan panjang Region, Cabang, status terakhir, simpan ini secara denormalized. Tambahkan tabel Data Agregasi, dan letakkan INDEX padanya.
sql
class="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

Mencari lewat 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

Data 3โ€“6 bulan disebut Active Hot Window. Pecah partisi tabel histori (stock_move, account_move_line) sesuai dengan Date. Larang pencarian Odoo mendeteksi riwayat purba secara default.
sql
class="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

Biarkan Smart Buttons di Odoo melihat tabel 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

Pisahkan 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.

Kalau harus menyimpulkan kompesisi raksasa ini ke dalam 5 kalimat emas:
  1. Jangan baca analytics dari tabel transaksi live.
  2. Jangan biarkan record rule membaca struktur organisasi kompleks setiap query.
  3. Jangan paksa semua compute menjadi realtime.
  4. Jangan biarkan chatter dan tracking ikut bulk process.
  5. Jangan satukan data aktif dan histori dingin dalam pengalaman user harian.