Skip to content

Operator skips cluster update when changing controller ID #3151

Open

Description

@baarde
  • Which image of the operator are you using? ghcr.io/zalando/postgres-operator:v2.0.1
  • Where do you run it? Azure Kubernetes Service
  • Are you running Postgres Operator in production? yes
  • Type of issue? Bug report

Hello,

I'm currently experimenting with running two instances of Postgres Operator. After changing a cluster's controller ID, I've noticed that the Postgres Operator does not update the cluster immediately.

Description

When changing the acid.zalan.do/controller annotation on a Postgres cluster manifest, the corresponding Postgres Operator should update the cluster accordingly. However, the update is skipped and the cluster is updated during the next resync.

Reason

The postgresqlUpdate method checks both the previous and the current manifests using postgresqlCheck.

func (c *Controller) postgresqlUpdate(prev, cur interface{}) {
pgOld := c.postgresqlCheck(prev)
pgNew := c.postgresqlCheck(cur)
if pgOld != nil && pgNew != nil {

As the previous manifest's annotation doesn't match the operator's controller ID, the check fails and the update is skipped.

func (c *Controller) postgresqlCheck(obj interface{}) *acidv1.Postgresql {
pg, ok := obj.(*acidv1.Postgresql)
if !ok {
c.logger.Errorf("could not cast to postgresql spec")
return nil
}
if !c.hasOwnership(pg) {
return nil
}

Suggested fix

I think the postgresqlCheck method should ignore ownership when checking the previous manifest.

I think postgresqlUpdate should enqueue an EventSync when the previous manifest's check fails (we cannot use EventUpdate here because the K8s objects may or may have been created by the other Postgres Operator, which may or may not exist).

Note: I realize we may run into a race condition because we don't know for sure that the other Postgres Operator is done with the cluster (waiting for the resync does not prevent the race condition, though it is less likely). So maybe the appropriate response should be "don't change theacid.zalan.do/controller annotation"?

Activity

  1. omkar619-dev commented on Oct 2, 2026

    @omkar619-dev

    @baarde I'd like to pick this up if nobody else is on it. I think your analysis holds on current master (80b7a074, v2.0.3) — postgresqlCheck returns nil when hasOwnership fails, and postgresqlUpdate's if pgOld != nil && pgNew != nil has no else, so adopting a cluster queues nothing at all and it waits for the resync.

    Your EventSync suggestion looks like the right call too, and I think it's more idiomatic than your hedging suggests: queueClusterEvent already takes a nil old spec — its branch for that is commented // add, sync — Clone() is nil-safe, and postgresqlAdd already calls queueClusterEvent(nil, pg, EventAdd). So queueClusterEvent(nil, pgNew, EventSync) on the adoption path fits what's there rather than working around it.

    On the race you flagged: I don't think it can be fully closed though from this side, since we can't know whether the other operator has finished. But sync seems like the right primitive precisely because of that — it reconciles to the desired state rather than diffing against an old spec we have no reason to trust. Resync has the same exposure today, so this makes the window smaller rather than introducing a new one.

    One thing not in the report: the mirror case is dropped too. When a cluster moves away (pgOld != nil, pgNew == nil) the same missing else means the operator never sees the handover and keeps whatever it had until resync. I'd leave that out of this change — stopping management is a different decision from starting it — but flagging it so it's a choice rather than an oversight.

    For the test, clusterEventStores/clusterEventQueues are built in initController rather than NewController, so I'd add a small harness to postgresql_test.go that sets up a single worker queue, then assert the adoption path queues EventSync with a nil OldSpec. Happy to go another way if you or a maintainer prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions