Artikel ini tersedia dalam Bahasa Inggris

🇬🇧 Read in English

18 Mei 2026

🇮🇩 Bahasa Indonesia

Fleet Management Part 5: Desain Database — PostgreSQL, MySQL & Redis

Menguasai arsitektur database polyglot. PostgreSQL untuk telemetri, MySQL untuk data bisnis, Redis untuk caching. Desain schema, migrasi, dan optimasi query.

3 min read

Apa itu Database?

Jika Anda baru mengenal database, pikirkan sebagai lemari arsip terorganisir untuk data aplikasi Anda. Tanpa database, data hilang setiap kali server restart.

Database SQL (PostgreSQL, MySQL) = Spreadsheet terstruktur dengan aturan ketat

| id | nama | email | status | |-----|---------|-----------------|--------| | 1 | Ahmad | [email protected] | active | | 2 | Budi | [email protected] | idle |

Database NoSQL (Redis, MongoDB) = Penyimpanan key-value yang fleksibel

"driver:1" → { nama: "Ahmad", status: "active", lokasiTerakhir: {...} }

Mengapa Kita Pakai Tiga Database Berbeda

Kebanyakan tutorial pakai satu database untuk semuanya. Di dunia nyata, data berbeda punya kebutuhan berbeda. Pendekatan ini disebut polyglot persistence.

DatabaseApa yang Kita SimpanMengapa Database Ini
PostgreSQLKoordinat GPS, data sensor BBMPostGIS untuk query geospasial, excellent untuk time-series
MySQLDriver, kendaraan, invoice, jadwalDukungan transaksional matang, familiar tim
RedisCache, sesi, posisi kendaraan real-timePembacaan mikro-detik, sempurna untuk "Di mana truk X?"

Analoginya seperti rumah sakit:

  • Rekam medis (MySQL) — terstruktur, harus akurat, jarang berubah
  • Data monitor jantung (PostgreSQL) — volume tinggi, time-stamped, butuh query khusus
  • Display ruang saat ini (Redis) — harus update instan, data sementara

PostgreSQL: Data Telemetri

-- Telemetri GPS — menerima 1000+ baris per menit
CREATE TABLE telemetry_gps (
    id          BIGSERIAL PRIMARY KEY,
    truck_id    UUID NOT NULL,
    latitude    DECIMAL(10, 7) NOT NULL,
    longitude   DECIMAL(10, 7) NOT NULL,
    speed       SMALLINT DEFAULT 0,
    recorded_at TIMESTAMPTZ NOT NULL
);

-- Partisi per bulan untuk performa
CREATE TABLE telemetry_gps_2026_05 PARTITION OF telemetry_gps
    FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');

-- Cari semua truk dalam radius 5km dari gudang
SELECT truck_id, latitude, longitude
FROM telemetry_gps
WHERE ST_DWithin(
    ST_MakePoint(longitude, latitude)::geography,
    ST_MakePoint(106.8456, -6.2088)::geography,
    5000
) AND recorded_at > NOW() - INTERVAL '5 minutes';

MySQL: Data Bisnis

// Tabel deliveries — transaksi bisnis inti
Schema::create('deliveries', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->foreignUuid('vehicle_id')->constrained();
    $table->foreignUuid('driver_id')->constrained();
    $table->decimal('fuel_volume_liters', 10, 2);
    $table->enum('status', ['scheduled', 'in_transit', 'delivered', 'cancelled']);
    $table->timestamp('scheduled_at');
    $table->timestamps();
    $table->softDeletes(); // Jangan pernah hard-delete data bisnis

    $table->index(['status', 'scheduled_at']);
});

Redis: Cache & Real-Time

// Pattern 1: Cache query database yang mahal
async function getFleetStats(): Promise<FleetStats> {
  const cached = await redis.get('fleet:stats');
  if (cached) return JSON.parse(cached);

  const stats = await calculateFleetStats();
  await redis.setex('fleet:stats', 60, JSON.stringify(stats));
  return stats;
}

// Pattern 2: Simpan posisi kendaraan real-time
async function updateVehiclePosition(truckId: string, lat: number, lng: number) {
  await redis.hset(`truck:${truckId}:position`, { lat, lng, updatedAt: Date.now() });
  await redis.publish('fleet:position-update', JSON.stringify({ truckId, lat, lng }));
}

// Pattern 3: Rate limiting
async function checkRateLimit(clientIp: string): Promise<boolean> {
  const current = await redis.incr(`ratelimit:${clientIp}`);
  if (current === 1) await redis.expire(`ratelimit:${clientIp}`, 60);
  return current <= 100;
}

Kesalahan Database Umum

Kesalahan 1: Masalah N+1 Query

// ❌ Buruk — 1 query untuk driver + N query untuk kendaraan
$drivers = Driver::all();
foreach ($drivers as $driver) {
    echo $driver->vehicles->count(); // Setiap iterasi = 1 query lagi!
}

// ✅ Baik — 2 query total dengan eager loading
$drivers = Driver::with('vehicles')->get();

Kesalahan 2: Index yang Hilang

-- Tanpa index, query ini scan SETIAP baris
SELECT * FROM deliveries WHERE status = 'in_transit';

-- Tambah index dan hanya scan baris yang cocok
CREATE INDEX idx_deliveries_status ON deliveries (status, scheduled_at);

Kesalahan 3: Simpan Semua di Satu Database

Jangan simpan 10 juta koordinat GPS di instance MySQL yang sama yang menangani login user.


Selanjutnya

Di Part 6, kita akan menerapkan prinsip SOLID dan design pattern untuk me-refactor kode kita.


Ini adalah Part 5 dari seri Fleet Management System. Memahami kapan menggunakan database mana adalah skill yang membedakan developer senior.

Lanjut Membaca

Previous article thumbnail

← Artikel Sebelumnya

Fleet Management Part 4: Admin Panel & Business Logic with Laravel

Artikel Selanjutnya →

Fleet Management Part 6: SOLID Principles & Design Patterns in Practice

Next article thumbnail