Kegagalan rilis pada sistem terdistribusi menuntut mekanisme pemulihan yang cepat, deterministik, dan bebas efek samping tak terduga. Pendekatan branching kondisional konvensional (seperti if-else atau case bersarang yang panjang) sering kali menyulitkan pengujian skenario rollback bertingkat. Artikel ini membahas penerapan konsep data-directed programming (SICP 2.4.3) di Haskell untuk mengorkestrasi status deployment dan aksi rollback otomatis via tabel dispatch terpusat.
Problem Desain: Monolithic Case Statements vs Data-Directed Dispatch
Saat pipeline rilis membesar, penanganan kegagalan deployment umumnya terjebak pada antipattern verifikasi status berulang:
-- Antipattern: Penanganan rilis procedural yang rentan regresi
handleFailure :: ReleaseStep -> StepStatus -> IO ()
handleFailure step status = case step of
DatabaseMigration -> case status of
Failed -> rollbackMigrations
_ -> pure ()
TrafficSwitch -> case status of
Degraded -> drainNewTraffic >> revertDNS
_ -> pure ()Pendekatan ini melanggar prinsip open-closed. Menambah status rilis atau strategi recovery baru memaksa modifikasi langsung pada percabangan inti. Solusinya adalah memisahkan deskripsi data operasi dari implementasi eksekusi melalui tabel dispatch eksplisit.
Model State Deployment dan Registry Typeclass
Langkah pertama adalah memodelkan fase rilis dan status kesehatan sistem sebagai tipe data aljabar murni (ADT). Hubungan antara tipe status dan penangan rollback dimodelkan via registry fungsi di dalam Data.Map atau dispatch dictionary.
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
module Release.Orchestrator where
import qualified Data.Map.Strict as Map
import Data.Text (Text)
data DeploymentPhase
= SchemaMigration
| CanaryRouting
| BaselineTraffic
deriving (Eq, Ord, Show)
data MetricStatus
= MetricHealthy
| MetricDegraded Double -- error rate percentage
| MetricFatal Text
deriving (Eq, Show)
type RollbackAction = IO (Either Text ())
type DispatchTable = Map.Map DeploymentPhase (MetricStatus -> RollbackAction)Konstruksi Dispatch Table untuk Data-Directed Rollback
Dalam data-directed programming, tabel 2 dimensi (Fase × Kondisi) menentukan prosedur yang dipanggil tanpa mengandalkan kontrol alur imperative. Berikut perakitan tabel routing rollback:
rollbackRegistry :: DispatchTable
rollbackRegistry = Map.fromList
[ (SchemaMigration, handleMigrationRollback)
, (CanaryRouting, handleCanaryRollback)
, (BaselineTraffic, handleFullTrafficRollback)
]
handleCanaryRollback :: MetricStatus -> RollbackAction
handleCanaryRollback status = case status of
MetricHealthy -> pure (Right ())
MetricDegraded rate -> do
putStrLn $ "Degradasi terdeteksi: " ++ show rate ++ "%. Mengurangi bobot trafik ke 0%."
-- Ponytail: Panggil runtime routing API di sini
pure (Right ())
MetricFatal err -> do
putStrLn $ "Fatal error: " ++ show err ++ ". Isolasi instans dan fallback ke versi stabil."
pure (Right ())
handleMigrationRollback :: MetricStatus -> RollbackAction
handleMigrationRollback status = case status of
MetricFatal _ -> do
putStrLn "Menjalankan migrasi database down-step deterministik."
pure (Right ())
_ -> pure (Right ())
handleFullTrafficRollback :: MetricStatus -> RollbackAction
handleFullTrafficRollback _ = pure (Right ())Eksekusi Teardown Deterministik Saat Incident Triage
Ketika sistem observabilitas mendeteksi anomali pada interval health check, engine rilis mengeksekusi aksi yang terdaftar pada registry. Jika fase tidak terdaftar dalam tabel, proses triage harus jatuh ke strategi teardown fallback yang aman.
executeRollback
:: DispatchTable
-> DeploymentPhase
-> MetricStatus
-> IO (Either Text ())
executeRollback registry phase status =
case Map.lookup phase registry of
Nothing -> do
putStrLn "PERINGATAN: Aksi tidak terdaftar. Menjalankan fallback hard-stop deterministik."
executeHardStop phase
Just actionRunner -> actionRunner status
executeHardStop :: DeploymentPhase -> IO (Either Text ())
executeHardStop phase = do
putStrLn $ "Mematikan alur kerja secara paksa pada fase: " ++ show phase
pure (Left "Aborted via generic safety net")Trade-off dan Pertimbangan Praktis
- Runtime vs Compile-time Safety: Pendekatan registry berbasis
Data.Mapmemvalidasi dispatch pada saat runtime (lookup mengembalikanMaybe). Jika exhaustive check mutlak diperlukan pada saat kompilasi, gunakan pendekatan representasi typeclass berparameter ganda (Multi-Param Typeclasses) atau GADTs murni. - State Persistence: Tabel di memori tidak tahan terhadap restart node orkestrator. Simpan fase terakhir dan status audit ke storage persisten (seperti PostgreSQL atau etcd) sebelum mengeksekusi
RollbackAction. - Idempotensi: Operasi rollback di dalam tabel dispatch harus bersifat idempoten untuk mengantisipasi eksekusi berulang saat network timeout.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!