Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
136
cloud/maplepress-backend/pkg/leaderelection/interface.go
Normal file
136
cloud/maplepress-backend/pkg/leaderelection/interface.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Package leaderelection provides distributed leader election for multiple application instances.
|
||||
// It ensures only one instance acts as the leader at any given time, with automatic failover.
|
||||
package leaderelection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LeaderElection provides distributed leader election across multiple application instances.
|
||||
// It uses Redis to coordinate which instance is the current leader, with automatic failover
|
||||
// if the leader crashes or becomes unavailable.
|
||||
type LeaderElection interface {
|
||||
// Start begins participating in leader election.
|
||||
// This method blocks and runs the election loop until ctx is cancelled or an error occurs.
|
||||
// The instance will automatically attempt to become leader and maintain leadership.
|
||||
Start(ctx context.Context) error
|
||||
|
||||
// IsLeader returns true if this instance is currently the leader.
|
||||
// This is a local check and does not require network communication.
|
||||
IsLeader() bool
|
||||
|
||||
// GetLeaderID returns the unique identifier of the current leader instance.
|
||||
// Returns empty string if no leader exists (should be rare).
|
||||
GetLeaderID() (string, error)
|
||||
|
||||
// GetLeaderInfo returns detailed information about the current leader.
|
||||
GetLeaderInfo() (*LeaderInfo, error)
|
||||
|
||||
// OnBecomeLeader registers a callback function that will be executed when
|
||||
// this instance becomes the leader. Multiple callbacks can be registered.
|
||||
OnBecomeLeader(callback func())
|
||||
|
||||
// OnLoseLeadership registers a callback function that will be executed when
|
||||
// this instance loses leadership (either voluntarily or due to failure).
|
||||
// Multiple callbacks can be registered.
|
||||
OnLoseLeadership(callback func())
|
||||
|
||||
// Stop gracefully stops leader election participation.
|
||||
// If this instance is the leader, it releases leadership allowing another instance to take over.
|
||||
// This should be called during application shutdown.
|
||||
Stop() error
|
||||
|
||||
// GetInstanceID returns the unique identifier for this instance.
|
||||
GetInstanceID() string
|
||||
}
|
||||
|
||||
// LeaderInfo contains information about the current leader.
|
||||
type LeaderInfo struct {
|
||||
// InstanceID is the unique identifier of the leader instance
|
||||
InstanceID string `json:"instance_id"`
|
||||
|
||||
// Hostname is the hostname of the leader instance
|
||||
Hostname string `json:"hostname"`
|
||||
|
||||
// StartedAt is when this instance became the leader
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
|
||||
// LastHeartbeat is the last time the leader renewed its lock
|
||||
LastHeartbeat time.Time `json:"last_heartbeat"`
|
||||
}
|
||||
|
||||
// Config contains configuration for leader election.
|
||||
type Config struct {
|
||||
// RedisKeyName is the Redis key used for leader election.
|
||||
// Default: "maplefile:leader:lock"
|
||||
RedisKeyName string
|
||||
|
||||
// RedisInfoKeyName is the Redis key used to store leader information.
|
||||
// Default: "maplefile:leader:info"
|
||||
RedisInfoKeyName string
|
||||
|
||||
// LockTTL is how long the leader lock lasts before expiring.
|
||||
// The leader must renew the lock before this time expires.
|
||||
// Default: 10 seconds
|
||||
// Recommended: 10-30 seconds
|
||||
LockTTL time.Duration
|
||||
|
||||
// HeartbeatInterval is how often the leader renews its lock.
|
||||
// This should be significantly less than LockTTL (e.g., LockTTL / 3).
|
||||
// Default: 3 seconds
|
||||
// Recommended: LockTTL / 3
|
||||
HeartbeatInterval time.Duration
|
||||
|
||||
// RetryInterval is how often followers check for leadership opportunity.
|
||||
// Default: 2 seconds
|
||||
// Recommended: 1-5 seconds
|
||||
RetryInterval time.Duration
|
||||
|
||||
// InstanceID uniquely identifies this application instance.
|
||||
// If empty, will be auto-generated from hostname + random suffix.
|
||||
// Default: auto-generated
|
||||
InstanceID string
|
||||
|
||||
// Hostname is the hostname of this instance.
|
||||
// If empty, will be auto-detected.
|
||||
// Default: os.Hostname()
|
||||
Hostname string
|
||||
}
|
||||
|
||||
// DefaultConfig returns a Config with sensible defaults.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
RedisKeyName: "maplefile:leader:lock",
|
||||
RedisInfoKeyName: "maplefile:leader:info",
|
||||
LockTTL: 10 * time.Second,
|
||||
HeartbeatInterval: 3 * time.Second,
|
||||
RetryInterval: 2 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks if the configuration is valid and returns an error if not.
|
||||
func (c *Config) Validate() error {
|
||||
if c.RedisKeyName == "" {
|
||||
c.RedisKeyName = "maplefile:leader:lock"
|
||||
}
|
||||
if c.RedisInfoKeyName == "" {
|
||||
c.RedisInfoKeyName = "maplefile:leader:info"
|
||||
}
|
||||
if c.LockTTL <= 0 {
|
||||
c.LockTTL = 10 * time.Second
|
||||
}
|
||||
if c.HeartbeatInterval <= 0 {
|
||||
c.HeartbeatInterval = 3 * time.Second
|
||||
}
|
||||
if c.RetryInterval <= 0 {
|
||||
c.RetryInterval = 2 * time.Second
|
||||
}
|
||||
|
||||
// HeartbeatInterval should be less than LockTTL
|
||||
if c.HeartbeatInterval >= c.LockTTL {
|
||||
c.HeartbeatInterval = c.LockTTL / 3
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue