Alif Akbar.
Proyek/Studi Kasus

Studi Kasus RTIMS: Membangun Sistem Manajemen Inventaris Terpusat

Sistem Manajemen Informasi Real-Time menggunakan Go dan WebSockets untuk sinkronisasi data instan.

Ringkasan Eksekutif: RTIMS adalah Sistem Manajemen Informasi Real-Time tingkat enterprise yang mengimplementasikan arsitektur Hub-and-Spoke Golang untuk WebSocket. Dengan memigrasikan status klien sinkron dari REST polling ke WebSocket di memori Go, sistem mencapai sinkronisasi inventaris instan lintas multi-gudang sambil mengurangi latensi API dan beban koneksi database sebesar 90%.

Masalah Bisnis: Kematian karena Polling

Dalam lingkungan perusahaan logistik modern, data yang usang bukan hanya sekadar gangguan; itu adalah kerugian finansial langsung. Sebelum intervensi saya, dasbor inventaris di seluruh gudang bergantung pada arsitektur REST tradisional yang digabungkan dengan HTTP long-polling.

Saat itu, aplikasi klien berbasis React/Vue me-refresh (poll) endpoint REST '/api/inventory' setiap 3 detik. Dengan lebih dari 500 staf aktif, server secara konsisten menerima 166 permintaan per detik yang sama sekali tidak diperlukan. 95% dari permintaan ini mengembalikan status 304 Not Modified atau memuat ulang data yang sama, tetapi mereka tetap mengunci koneksi thread PHP-FPM dan mengonsumsi IOPS PostgreSQL.

Akibatnya, ketika lonjakan lalu lintas gudang nyata terjadi (seperti kedatangan barang bulk), database melampaui batas max_connections, yang mengarah ke efek berantai (cascading failure) dan downtime selama 15 menit.

Tumpukan Teknologi

  • Backend: Go (Golang) dipilih karena model konkurensinya yang ringan (goroutines), menjadikannya ideal untuk mengelola ribuan koneksi WebSocket secara paralel.
  • Frontend: React.js digunakan untuk antarmuka berkinerja tinggi, memanfaatkan pembaruan status parsial berdasarkan payload WebSocket masuk.
  • Database: PostgreSQL sebagai penyimpanan kebenaran absolut, dioptimalkan untuk kueri pembacaan cepat di bawah beban konkurensi.

Arsitektur Hub-and-Spoke Go

Untuk menyelesaikan masalah ini, saya memutuskan untuk memutuskan sepenuhnya ketergantungan membaca data dari database PostgreSQL utama dan menggeser beban ke koneksi TCP yang terus terbuka (WebSockets) yang dikelola oleh layanan mandiri berbasis Go.

graph TD
    ClientA[Warehouse A Client] -->|WebSocket| GoHub((Go WebSocket Hub))
    ClientB[Warehouse B Client] -->|WebSocket| GoHub
    ClientC[Mobile Client] -->|WebSocket| GoHub
    
    Mutator[Admin Terminal] -->|HTTP POST Update| Laravel[Laravel Core API]
    Laravel -->|SQL Write| PG[(PostgreSQL)]
    Laravel -->|Event Trigger| Redis[(Redis Pub/Sub)]
    Redis -->|Subscribe| GoHub
    
    GoHub -.->|Broadcast Delta State| ClientA
    GoHub -.->|Broadcast Delta State| ClientB
    GoHub -.->|Broadcast Delta State| ClientC

Sistem backend Laravel yang lama tetap bertindak sebagai Single Source of Truth yang menangani mutasi dan keamanan otorisasi. Namun, setiap kali data diubah, Laravel melempar event ke Redis Pub/Sub. Layanan Go berlangganan (subscribe) ke Redis, mengambil delta perubahan, dan menyiarkannya (broadcast) ke seluruh ribuan klien WebSocket yang terhubung dalam bentuk goroutine thread-safe yang sangat ringan.

Implementasi Inti (Snippet Go)

Berikut adalah contoh bagaimana channel Go mengelola status klien tanpa menyebabkan race condition pada level memori server:

// Hub maintains the set of active clients and broadcasts messages to the clients.
type Hub struct {
    // Registered clients.
    clients map[*Client]bool

    // Inbound messages from the Redis Pub/Sub to broadcast.
    broadcast chan []byte

    // Register requests from the clients.
    register chan *Client

    // Unregister requests from clients.
    unregister chan *Client
}

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
            }
        case message := <-h.broadcast:
            // Broadcast state delta to all connected clients
            for client := range h.clients {
                select {
                case client.send <- message:
                default:
                    // If send buffer is full, disconnect client to prevent memory leaks
                    close(client.send)
                    delete(h.clients, client)
                }
            }
        }
    }
}

Momen Kritis: Goroutine Leaks & Tantangan Produksi

Tentu saja, penerapan produksi pertama tidak berjalan mulus. Dalam 48 jam pertama, konsumsi memori server Go meroket dari 50MB ke 4GB sebelum dihentikan oleh manajer OOM (Out of Memory) Linux. Ini adalah momen kepanikan total.

Melalui pembuatan profil (profiling) menggunakan tool bawaan Go (pprof), saya menemukan masalah 'Goroutine Leak'. Klien dengan koneksi jaringan yang buruk terputus tanpa mengirim frame penutup TCP. Server Go tetap mempertahankan memori goroutine yang mencoba menulis (write) ke channel yang diblokir selamanya. Perbaikan yang saya lakukan (terlihat di baris 'default:' pada kode di atas) adalah implementasi timeout non-blocking channel: jika buffer pengiriman klien penuh atau terblokir, hub akan secara paksa menghapus memori dan memutus koneksi.

Hasil yang Terukur (Benchmarks)

MetricBefore (REST Polling)After (Go WebSocket)Improvement
Database CPU Load85% - 95%5% - 15%Massive reduction in read IOPS
Sync Latency3,000ms+ (polling rate)~45msNear real-time capability
Server Memory Footprint1.2GB (PHP-FPM processes)85MB (Go Binary)92% reduction
Downtime Incidence2-3 times / week0 times / year100% Stability