block / block/schemabot

feat(storage): make active-apply key deployment-aware

Open
#192 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Go
Stars
30
Forks
7
Avg merge
1d 7h
Merged PRs (30d)
316

Description

## Summary

Extend SchemaBot's active-apply identity from
`(database, type, environment)` to
`(database, type, environment, deployment)` so that two applies for the
same logical database+environment but different `deployment` routes no
longer block each other on the active-apply gate.

The change ships as a sequence of focused, individually revertable PRs:
additive schema → internal plumbing → behavior flip → docs → optional
schema cleanup. Each PR will link back to this issue.

## Overall context

SchemaBot has a first-class `environment` concept and a lower-level
`deployment` field today used mostly as a routing key for remote
execution. That isn't expressive enough for installations where the
same logical database and the same schema need to be applied to
multiple execution locations under one control plane — for example,
the same database deployed to several regions, or the same database
deployed across several tenant accounts.

There is ongoing discussion about introducing `deployments` as a
first-class server-side config dimension so that the deployment matrix,
ordering, and routing are owned by server config rather than expressed
indirectly through other fields. Likely follow-up areas (each will get
its own proposal and review):

- a config-level `deployments:` map and an
`environments..deployments.` nested form,
- a `--deployment` CLI flag and fan-out promotion semantics with
environment-major ordering,
- per-deployment GitHub check runs and an environment-aggregate check
that passes only when every deployment passes,
- DSN+table-aware locking for long-running schema changes that should
not block unrelated work on the same target.

Storage-side locking is the prerequisite for any of those: as long as
two applies for the same database+environment but different deployments
serialize through one advisory lock, the rest of the model can't make
forward progress. This issue tracks closing that gap.

## Illustrative config shape (one proposed shape, not this PR)

For context only — the actual config-schema change will be proposed
separately. Today a database routes to one deployment per environment:

```yaml
databases:
payments:
type: mysql
environments:
production:
target: payments
deployment: primary

tern_deployments:
primary:
production: tern-primary:9090
```

One direction under discussion would let a single logical database
route to multiple deployments per environment, with ordering owned by
the control plane:

```yaml
environment_order:
- staging
- production

deployment_order:
- region-a
- region-b
- region-c

deployments:
region-a: { label: A }
region-b: { label: B }
region-c: { label: C }

databases:
payments:
type: mysql
environments:
production:
deployments:
region-a: { deployment: payments-a, target: payments }
region-b: { deployment: payments-b, target: payments }
region-c: { deployment: payments-c, target: payments }

tern_deployments:
payments-a: { production: tern-a:9090 }
payments-b: { production: tern-b:9090 }
payments-c: { production: tern-c:9090 }
```

This issue does not deliver any of the above config shape — it only
closes the storage-side gap. With the current 3-tuple lock, three
parallel applies for `payments / production` would serialize on one
lock; with the 4-tuple lock they'll run independently.

## Why

The active-apply lock today is keyed by
`(database, type, environment)`. Two applies for the same logical
database and environment but different `deployment` routes block each
other on the same MySQL advisory lock and on the
`applies.idx_database_env` predicate. With shared storage across
multiple deployments of the same database, this is too coarse —
independent deployments end up serializing through one lock for no
operational reason.

`plans.deployment` and `applies.deployment` already persist the
deployment value, and the server-side resolver already produces a
`ResolvedDatabaseTarget.Deployment`. The gap is purely at the
active-apply gating layer.

## What changes (illustrative)

At the end of the multi-stage effort the active-apply identity becomes:

```sql
UNIQUE KEY (database_name, database_type, environment, deployment)
KEY (database_name, database_type, environment, deployment)
```

Empty `deployment` (`''`) is preserved as a synthetic default so
existing rows and existing single-deployment installations continue to
behave exactly as today.

Contributor guide

Open the contributing guide

Research direction

Start by locating the active-apply gating layer and the applies/plans schema paths referenced in the issue, then trace ResolvedDatabaseTarget.Deployment into the lock and index predicates. Done means the active-apply identity includes deployment, empty deployment remains the default, and separate deployments no longer serialize through the same gate.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, mysql
Domain
databases
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.