From a38cc08809935ed8f21ca99dccef996aa05b74d5 Mon Sep 17 00:00:00 2001 From: anonpenguin Date: Sat, 9 Aug 2025 08:45:47 +0300 Subject: [PATCH] feat: skip leadership wait for rqlite nodes with existing state and add ports 4002/5002 to clear script --- clear-ports.sh | 2 +- pkg/database/rqlite.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/clear-ports.sh b/clear-ports.sh index 7869e03..12bfdb3 100755 --- a/clear-ports.sh +++ b/clear-ports.sh @@ -10,7 +10,7 @@ set -euo pipefail # Collect ports from args or use defaults PORTS=("$@") if [ ${#PORTS[@]} -eq 0 ]; then - PORTS=(4001 5001 7001 7002) + PORTS=(4001 4002 5001 5002 7001 7002) fi echo "Gracefully terminating listeners on: ${PORTS[*]}" diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index 74715f7..f3fef4a 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -139,11 +139,17 @@ func (r *RQLiteManager) Start(ctx context.Context) error { } r.connection = conn - // Wait for RQLite to establish leadership (for bootstrap nodes) + // Wait for RQLite to establish leadership (only on fresh bootstrap) if r.config.RQLiteJoinAddress == "" { - if err := r.waitForLeadership(ctx); err != nil { - r.cmd.Process.Kill() - return fmt.Errorf("RQLite failed to establish leadership: %w", err) + if !r.hasExistingState(rqliteDataDir) { + if err := r.waitForLeadership(ctx); err != nil { + r.cmd.Process.Kill() + return fmt.Errorf("RQLite failed to establish leadership: %w", err) + } + } else { + // Existing state implies this node may be part of a multi-voter cluster; quorum may be required. + // Do not fail startup if leadership is not immediately available. + r.logger.Info("Existing Raft state detected; skipping leadership wait (cluster may require quorum)") } } @@ -151,6 +157,22 @@ func (r *RQLiteManager) Start(ctx context.Context) error { return nil } +// hasExistingState returns true if the rqlite data directory already contains files or subdirectories. +func (r *RQLiteManager) hasExistingState(rqliteDataDir string) bool { + entries, err := os.ReadDir(rqliteDataDir) + if err != nil { + return false + } + for _, e := range entries { + // Any existing file or directory indicates prior state + if e.Name() == "." || e.Name() == ".." { + continue + } + return true + } + return false +} + // waitForReady waits for RQLite to be ready to accept connections func (r *RQLiteManager) waitForReady(ctx context.Context) error { url := fmt.Sprintf("http://localhost:%d/status", r.config.RQLitePort)