Pengujian integrasi yang berinteraksi langsung dengan instance Oracle Database kerap mengalami flakiness akibat residu data antar-eksekusi test. Pendekatan konvensional yang mengandalkan rollback transaksi di level framework pengujian (seperti @Transactional pada Spring Test atau transaksi rollback pada pytest) sering kali gagal mempertahankan isolasi data pada arsitektur enterprise.

Akar Masalah: Mengapa Rollback Transaksi Gagal

Rollback transaksi di level aplikasi mengasumsikan seluruh operasi DML berada dalam cakupan sesi transaksi yang sama dan dapat dibatalkan melalui instruksi ROLLBACK. Asumsi ini runtuh dalam dua skenario umum pada Oracle:

  • Implicit Commit oleh DDL: Stored procedure yang mengeksekusi DDL secara dinamis melalui EXECUTE IMMEDIATE (misalnya pembuatan tabel staging, pemotongan partisi, atau pembuatan indeks sementara) memicu commit implisit sebelum dan sesudah statement DDL dieksekusi. Sesi transaksi terbuka langsung dipermanenkan ke database.
  • Autonomous Transaction: Kode PL/SQL yang dianotasi dengan PRAGMA AUTONOMOUS_TRANSACTION berjalan di luar konteks transaksi pemanggil. Commit yang dipanggil di dalam blok tersebut bersifat independen. Rollback dari runner pengujian tidak dapat menyentuh perubahan data ini, meninggalkan residu state pada tabel audit, log transaksi, atau tabel operasional.

Residu data ini menyebabkan dependensi urutan eksekusi antar-test suite, benturan constraint unik (ORA-00001), dan kegagalan asersi data yang acak.

Solusi: Guaranteed Restore Point (GRP)

Oracle Flashback Database bekerja pada level blok database menggunakan Flashback Logs di Fast Recovery Area (FRA). Berbeda dengan Normal Restore Point yang dapat terhapus saat ruang FRA menipis, Guaranteed Restore Point (GRP) menjamin database dapat dikembalikan persis ke titik waktu pembuatan GRP, terlepas dari operasi DDL, commit otonom, atau modifikasi skema.

Penggunaan GRP pada database container di environment Continuous Integration (CI) memastikan state bersih (baseline) dapat dipulihkan secara instan tanpa perlu membuang container dan membangun ulang skema dari nol.

Konfigurasi Database untuk Mendukung Flashback

Sebelum GRP dapat dibuat, instance Oracle harus berada dalam mode ARCHIVELOG dan memiliki Fast Recovery Area aktif. Eksekusi script SQL berikut menggunakan hak akses SYSDBA saat container pertama kali diinisialisasi:

-- Konfigurasi Fast Recovery Area jika belum diset
ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE = 10G SCOPE=BOTH;
ALTER SYSTEM SET DB_RECOVERY_FILE_DEST = '/opt/oracle/oradata/fast_recovery_area' SCOPE=BOTH;

-- Pindahkan database ke mode ARCHIVELOG
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;

Setup Baseline dan Pembuatan GRP

Setelah database siap, jalankan migration tool (Flyway, Liquibase, atau migration script internal) untuk membentuk skema dan memuat master data awal (seed). Segera setelah inisialisasi selesai, buat restore point:

-- Buat Guaranteed Restore Point bernama 'clean_baseline'
CREATE RESTORE POINT clean_baseline GUARANTEE FLASHBACK DATABASE;
Catatan: Status GRP dapat diverifikasi melalui query: SELECT NAME, GUARANTEE_FLASHBACK_DATABASE FROM V$RESTORE_POINT;

Automasi Reset State dalam Pipeline CI

Runner CI dapat memanggil script bash berikut setiap kali sebuah test suite atau test class selesai dieksekusi untuk mengembalikan database ke titik clean_baseline:

#!/usr/bin/env bash
set -euo pipefail

CONTAINER_NAME="oracle-ci-db"
ORACLE_SID="FREE" # sesuaikan dengan SID/SERVICE_NAME yang digunakan

echo "Memulai proses rollback ke GRP: clean_baseline..."

docker exec -i "${CONTAINER_NAME}" sqlplus -s / as sysdba <<EOF
WHENEVER SQLERROR EXIT FAILURE ROLLBACK;
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
FLASHBACK DATABASE TO RESTORE POINT clean_baseline;
ALTER DATABASE OPEN RESETLOGS;
EXIT;
EOF

echo "Database berhasil direset ke clean_baseline."

Perintah ALTER DATABASE OPEN RESETLOGS diperlukan untuk memulai cabang redo stream baru dari titik restore point tersebut, memungkinkan siklus pengujian berikutnya berjalan secara deterministik.

Perbandingan: Flashback vs Truncate/Re-seed Konvensional

ParameterTruncate & Re-seed ManualFlashback Database (GRP)
Overhead WaktuTinggi (meningkat linear sesuai jumlah tabel dan volume seed data)Rendah & Konstan (rerata 5-15 detik untuk shutdown, flashback, startup)
Foreign Key HandlingRentan gagal; membutuhkan penonaktifan dan pengaktifan kembali FK constraintNative; pemulihan terjadi pada level blok storage tanpa validasi metadata constraint
Isolasi DDLGagal memulihkan tabel, sequence, atau view yang termodifikasi saat testSempurna; seluruh modifikasi struktur objek skema ikut ter-rollback
Kompleksitas ScriptTinggi; memerlukan pemeliharaan script cleanup seiring evolusi skemaMinimal; satu set instruksi flashback berlaku universal

Trade-off dan Limitasi

  • Storage Fast Recovery Area: GRP menahan penghapusan flashback log dari FRA. Jika pengujian menghasilkan mutasi data masif dalam jumlah gigabyte tanpa pembersihan, disk FRA dapat penuh dan menghentikan database (ORA-19809). Pastikan ukuran FRA proporsional dengan alokasi runner CI.
  • Instance Restart: Eksekusi SHUTDOWN IMMEDIATE dan STARTUP MOUNT membutuhkan jeda beberapa detik. Terapkan reset state per test suite/test class, bukan per test case individual, guna menjaga total durasi pipeline tetap optimal.
  • Pembersihan GRP: Sebelum container dimatikan atau jika GRP tidak lagi dibutuhkan, hapus restore point menggunakan perintah: DROP RESTORE POINT clean_baseline; untuk membebaskan ruang blok log.