LeaderElection.Postgres
1.1.0
dotnet add package LeaderElection.Postgres --version 1.1.0
NuGet\Install-Package LeaderElection.Postgres -Version 1.1.0
<PackageReference Include="LeaderElection.Postgres" Version="1.1.0" />
<PackageVersion Include="LeaderElection.Postgres" Version="1.1.0" />
<PackageReference Include="LeaderElection.Postgres" />
paket add LeaderElection.Postgres --version 1.1.0
#r "nuget: LeaderElection.Postgres, 1.1.0"
#:package LeaderElection.Postgres@1.1.0
#addin nuget:?package=LeaderElection.Postgres&version=1.1.0
#tool nuget:?package=LeaderElection.Postgres&version=1.1.0
LeaderElection.Postgres
A PostgreSQL-backed implementation of the distributed leader election pattern for .NET. This implementation uses extremely fast, native PostgreSQL "Advisory Locks" (pg_try_advisory_lock) to ensure safe, atomic leadership acquisition tied directly to the lifetime of the database connection.
Features
- Async/Await Support: Full async/await pattern support for better performance and scalability
- Comprehensive Error Handling: Robust error handling with retry logic and exponential backoff
- Structured Logging: Integration with Microsoft.Extensions.Logging for observability
- Configuration Options: Flexible configuration through options pattern
- Event-Driven: Leadership change and error events for reactive programming
- Graceful Shutdown: Proper cleanup and resource disposal
- Session-Level Locks: Fully avoids row-level contention and deadlocks by locking in shared memory
Quick Start
1. Install the Package
dotnet add package LeaderElection
dotnet add package LeaderElection.Postgres
2. Configure Services
using LeaderElection.Postgres;
var builder = WebApplication.CreateBuilder(args);
// Add Postgres leader election
builder.Services.AddPostgresLeaderElection(options =>
{
options.ConnectionString = "Host=localhost;Database=mydb;Username=myuser;Password=mypass";
options.LockId = 1337; // A unique 64-bit integer representing this specific lock
options.RetryInterval = TimeSpan.FromSeconds(5);
});
3. Use in Your Service
public class Worker : BackgroundService
{
private readonly ILeaderElection _leaderElection;
private readonly ILogger<Worker> _logger;
public Worker(ILeaderElection leaderElection, ILogger<Worker> logger)
{
_leaderElection = leaderElection;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Start the background election loop
await _leaderElection.StartAsync(stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
// Only runs if this instance currently holds the Postgres advisory lock
await _leaderElection.RunTaskIfLeaderAsync(async () =>
{
_logger.LogInformation("Instance is leader. Doing work...");
await Task.Delay(1000, stoppingToken);
}, stoppingToken);
await Task.Delay(5000, stoppingToken);
}
}
}
Configuration (PostgresSettings)
| Property | Default | Description |
|---|---|---|
ConnectionString |
(required) |
The connection string for the PostgreSQL database. |
LockId |
(required) |
The unique 64-bit advisory lock key to use for leader election. |
InstanceId |
Environment.MachineName |
Unique ID for this node. |
RetryInterval |
5s |
The interval to wait before retrying a failed leadership acquisition. |
EnableGracefulShutdown |
true |
If true, explicitly unlocks via pg_advisory_unlock on stop. |
PostgreSQL Specifics
Instead of executing slow UPDATE queries on rows with expiration timestamps, this package executes SELECT pg_try_advisory_lock(@LockId); under the hood.
This requests a session-level lock from PostgreSQL itself. If the application crashes, the host node dies, or the network connection drops, PostgreSQL instantly cleans up the connection and drops the lock. This allows other backup nodes to acquire leadership immediately without having to wait for traditional polling or lease-expiration timers.
Additionally, to prevent silent network partitions from locking up the system, the leader election class periodically executes a lightweight SELECT 1; to verify the physical connection is still healthy and the lock holds true.
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
License
MIT License
Copyright (c) 2025 Greg James
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Icon
"Business icon pack leader icon" by mr icons is licensed under CC BY 4.0
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- LeaderElection (>= 1.1.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
- Npgsql (>= 8.0.9)
-
net10.0
- LeaderElection (>= 1.1.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
- Npgsql (>= 10.0.2)
-
net8.0
- LeaderElection (>= 1.1.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
- Npgsql (>= 10.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.