High Availability mode for Designer - #9416
Conversation
|
🟢 Cypress snapshots are up to date. No changes needed. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a High Availability (HA) mode for the Designer backend, enabling multiple instances to run concurrently with leader-elected, single-instance execution of coordinated/background operations (via DB-backed distributed locks). It also adds a public endpoint to expose leader status for load balancers and documents the feature.
Changes:
- Add DB-backed distributed locking + leader election services (PostgreSQL-only) and wire them into Designer startup and schedulers.
- Gate background/periodic work (deployments reconciliation/synchronization and periodic deployment/rescheduling actors) behind leadership/locks.
- Add
GET /api/app/leaderpublic endpoint, plus OpenAPI + migration guide/changelog updates and integration tests.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scalafix-rules/src/test/scala/utils/SchemaInSqlDetectorSpec.scala | Extends detector test coverage with SQL patterns used by distributed locks. |
| scalafix-rules/src/main/scala/utils/SchemaInSqlDetector.scala | Updates SQL token heuristics to reduce false positives for new lock SQL constructs. |
| docs/MigrationGuide.md | Documents the new optional ha { ... } configuration block and constraints. |
| docs/Changelog.md | Adds changelog entry describing HA mode and the new leader endpoint. |
| docs-internal/api/nu-designer-openapi.yaml | Documents /api/app/leader and its response schema in OpenAPI. |
| designer/server/src/test/scala/pl/touk/nussknacker/ui/process/periodic/flink/RescheduleFinishedActorTest.scala | Updates actor tests to include periodic locking behavior (skip when lock not acquired). |
| designer/server/src/test/scala/pl/touk/nussknacker/ui/process/periodic/flink/DeploymentActorTest.scala | Updates deployment actor tests for lock acquisition/renewal and in-flight behavior. |
| designer/server/src/test/scala/pl/touk/nussknacker/ui/ha/SlickDistributedLockSpec.scala | Adds Postgres-backed tests for acquire/renew/release semantics. |
| designer/server/src/test/scala/pl/touk/nussknacker/ui/ha/LeadershipServiceSpec.scala | Adds unit tests for leader election heartbeat, callbacks, and stop behavior. |
| designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceHaSpec.scala | Adds IT coverage for /api/app/leader in HA-enabled mode using Postgres DB. |
| designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceBusinessSpec.scala | Adds test for /api/app/leader when HA is disabled (no instanceId). |
| designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/NuItTestWithPostgres.scala | Introduces an IT base trait that starts the app with Postgres for HA endpoint testing. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/server/TapirHttpServiceFactory.scala | Wires Leadership into AppApiHttpService. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/process/periodic/SchedulingDependencies.scala | Threads PeriodicLock through periodic scheduling dependencies. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/process/periodic/RescheduleFinishedActor.scala | Adds periodic lock acquisition before rescheduling/marking failures. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/process/periodic/PeriodicLock.scala | Introduces a shared periodic-work lock wrapper with HA-aware resource lifecycle. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/process/periodic/PeriodicDeploymentManager.scala | Uses PeriodicLock when creating periodic actors. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/process/periodic/DeploymentActor.scala | Adds lock acquisition/renewal logic to periodic deployments to avoid multi-instance duplication. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/process/newdeployment/synchronize/DeploymentsStatusesSynchronizationScheduler.scala | Runs deployment status synchronization only on the elected leader. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/reconciliation/FinishedDeploymentsStatusesSynchronizationScheduler.scala | Runs finished-deployments reconciliation only on the elected leader. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/ha/Leadership.scala | Adds leader election service with heartbeat + callbacks + shutdown handling. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/ha/LeaderLock.scala | Adds a dedicated leader lock wrapper (name + acquire/renew/release). |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/ha/HaMode.scala | Adds HA config parsing + validation (durations/constraints). |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/ha/DistributedLock.scala | Implements Slick/Postgres distributed lock operations and NoOp fallback. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/factory/NussknackerAppFactory.scala | Triggers recovery on leadership acquisition (not only at startup). |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/factory/DomainServices.scala | Creates/wires DistributedLock, PeriodicLock, and Leadership into domain services and schedulers. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/config/DesignerConfig.scala | Adds haMode to resolved DesignerConfig. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/AppApiEndpoints.scala | Adds Tapir endpoint + DTO/encoder for leader status response. |
| designer/server/src/main/scala/pl/touk/nussknacker/ui/api/AppApiHttpService.scala | Exposes /api/app/leader and returns leader status + instanceId. |
| designer/server/src/main/resources/db/migration/postgres/V1_072__DistributedLocksTable.sql | Adds Postgres migration creating distributed_locks table. |
| designer/server/src/main/resources/db/migration/hsql/V1_072__DistributedLocksTable.sql | Adds HSQL no-op migration with explanation (HA requires Postgres). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 32 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
designer/server/src/test/scala/pl/touk/nussknacker/ui/ha/SlickDistributedLockSpec.scala:81
- The
Using(...)result is ignored here as well, which would hide failures inserting the expired lock and potentially make the test pass for the wrong reason. Append.getso a failed INSERT fails the test.
Using(testDbRef.db.createSession()) { session =>
session
.prepareStatement(
s"""INSERT INTO "${getSchemaName()}".distributed_locks (name, lock_until, locked_at, locked_by)
|VALUES ('$lockName', CURRENT_TIMESTAMP - INTERVAL '1 second', CURRENT_TIMESTAMP - INTERVAL '60 seconds', '$holder')""".stripMargin
)
.execute()
}
designer/server/src/test/scala/pl/touk/nussknacker/ui/ha/SlickDistributedLockSpec.scala:23
- The
Using(...)result is ignored, so if the TRUNCATE fails the exception will be swallowed and the test may continue with dirty state. Call.get(or otherwise rethrow) to ensure DB cleanup failures fail the test.
This issue also appears on line 74 of the same file.
Using(testDbRef.db.createSession()) { session =>
session.prepareStatement(s"""TRUNCATE TABLE "${getSchemaName()}".distributed_locks""").execute()
}
|
|
||
| ### 1.19.0 (Not released yet) | ||
|
|
||
| * [#9416](https://github.com/TouK/nussknacker/pull/9416) Feature: High Availability (HA) mode for the Designer. |
There was a problem hiding this comment.
HA mode's configuration should be described in documentation, along with new API endpoints
Describe your changes
Checklist before merge