DSN-Only Database Notifications: A Compatibility Boundary for #53
Shipped in SILO Server 20260903 via commit f1ba68358, this design resolves SILO issue #53 by establishing the compatibility boundary, legacy-migration behavior, and startup error handling for PostgreSQL and MySQL bucket-notification targets.
Decision
SILO retains PostgreSQL and MySQL notification targets and standardizes each on a single canonical configuration key:
- PostgreSQL requires a complete
connection_string. - MySQL requires a complete
dsn_string.
The pre-2020 five-field form—host, port, username, password, and database—remains unsupported by the KV configuration system. SILO does not register those five keys and does not synthesize a DSN from them during legacy JSON migration.
The legacy migration contract handles three input states:
| Legacy Target State | Migration Result |
|---|---|
| Disabled | Ignored; no KV target is emitted. |
Enabled with a non-empty connection_string or dsn_string |
Migrates only the canonical connection-string key and registered target settings. |
| Enabled with only discrete connection fields | Rejects migration and aborts server startup before activating the new configuration, logging an actionable error that names the subsystem and target without printing credentials. |
Context
SILO inherited two generations of database-notification configuration from upstream MinIO. The pre-KV JSON configuration could represent a database connection either as a single connection string or as five discrete fields:
The current KV configuration system exposes only the driver-native connection string:
Upstream deprecated the five discrete fields in RELEASE.2020-04-10T03-34-42Z and directed operators to migrate to connection_string or dsn_string. SILO’s CLI help tables, environment-variable documentation, and Console UI already use the complete connection string as the supported interface.
The Defect
Before f1ba68358, the legacy migration helpers SetNotifyPostgres and SetNotifyMySQL wrote both representations into the new KV configuration. Even when the legacy JSON target already specified a complete connection_string or dsn_string, the helpers also emitted all five discrete keys (typically with empty values).
The KV parser rejected those migrated entries because neither DefaultPostgresKVS nor DefaultMySQLKVS registers the five discrete keys, and CheckValidKeys validates key names regardless of whether their values are empty. Both legacy JSON forms therefore failed KV validation:
Notification initialization amplified the impact of that parser error. FetchEnabledTargets evaluates notification subsystems fail-fast: the first invalid subsystem returns an error and a nil target list. The caller logged the error and continued starting the object server, leaving healthy Webhook, Kafka, NATS, and other notification targets disabled as well.
Simply returning an error from SetNotifyPostgres and SetNotifyMySQL was not sufficient on its own. While that error propagates through readConfigWithoutMigrate and initConfig, initConfigSubsystem previously logged non-retriable configuration errors as "some features may be missing" and returned success. The server then started without assigning globalServerConfig, leaving not only notifications broken but also region, storage class, compression, identity, and other persisted settings unapplied. Conversely, marking the error as retriable would cause the server to loop indefinitely on a static configuration error. The migration failure therefore has to propagate as a typed, non-retriable, startup-fatal error (LegacyDatabaseTargetError).
This failure mode was particularly hazardous because S3 object reads and writes continued normally while every configured event pipeline was offline. In addition, the unregistered password key lacked secret-redaction metadata and could be included in plain text inside diagnostic bundles, whereas connection_string and dsn_string are registered as sensitive keys.
Why the First Fix Was Reverted
An initial fix in 20260804 (SN-2026-019) registered the five discrete keys in DefaultPostgresKVS and DefaultMySQLKVS and wired the parser to read them. While that allowed migrated targets to pass CheckValidKeys, it broke the documented connection_string interface.
The shared mcli admin config set KV tokenizer identifies key/value boundaries by scanning for registered key names and is not quote-aware. Once port was registered as a top-level key in notify_postgres, a valid libpq key/value connection string inside quotes looked like a second top-level KV parameter:
The tokenizer split the input at port= inside the quoted string, truncated connection_string to "host=db, and passed 5432 dbname=events user=app" to the integer port parser, failing with invalid port.
Under the shared KV tokenizer grammar, registering common words such as host, port, and password creates an irreconcilable grammar collision with key/value connection strings. That registration change was therefore reverted in favor of enforcing connection_string and dsn_string exclusively.
Product Judgment
Database notification targets provide a direct database-backed namespace view or access journal without requiring an external message broker, which remains valuable for deployments already operating PostgreSQL or MySQL.
By contrast, the five-field parameter model cannot express standard driver options such as TLS modes and CA paths, connect timeouts, application names, Unix domain sockets, multi-host PostgreSQL failover, or MySQL driver flags. Maintaining both forms also creates precedence, merging, and secret-redaction ambiguity. Keeping SILO responsible for notification delivery semantics while delegating connection syntax to the database driver’s native DSN string provides a clean boundary.
Goals
- Establish
connection_stringanddsn_stringas the sole supported configuration keys for PostgreSQL and MySQL notifications. - Allow legacy JSON targets that already contain a non-empty canonical connection string to migrate cleanly.
- Reject enabled discrete-only legacy targets before activating or persisting a partial KV configuration.
- Replace silent runtime notification failure with an explicit startup-time failure (
LegacyDatabaseTargetError). - Ensure no migration error, log line, health report, or diagnostic bundle leaks a database password.
- Remove all ten Postgres/MySQL exceptions from the
knownUnregisteredWritesaudit ratchet. - Document operator remediation paths clearly.
Non-Goals
- Supporting both DSN strings and discrete database fields in the KV interface.
- Synthesizing a DSN string from legacy discrete fields during migration.
- Rewriting the shared KV tokenizer grammar.
- Altering
FetchEnabledTargetsfail-fast semantics across notification subsystems. - Silently skipping an enabled invalid database target and starting with partial notification coverage.
- Removing PostgreSQL or MySQL notification support.
- Deleting the legacy Go struct fields used to unmarshal old JSON files and detect unsupported discrete-only configurations.
- Changing error handling for the other eight legacy notification setters outside PostgreSQL and MySQL.
Functional Requirements
Current Configuration
notify_postgresacceptsconnection_string;notify_mysqlacceptsdsn_string.- The five discrete keys (
host,port,username,password,database) remain unregistered and are rejected byadmin config set. - Complete connection strings continue to support native driver syntax, including embedded
host=,port=,user=,password=, anddbname=tokens. - Legacy environment variables
MINIO_NOTIFY_POSTGRES_HOST/PORT/USERNAME/PASSWORD/DATABASEand their MySQL equivalents remain unwired and unsupported.
Legacy Migration
SetNotifyPostgresandSetNotifyMySQLreturnnilwithout emitting a target when the legacy target is disabled (Enable == false).- When enabled,
SetNotifyPostgresrequires a non-emptyConnectionStringand writes only registered Postgres keys. If bothConnectionStringand discrete fields are present in the legacy JSON,ConnectionStringwins and the discrete values are discarded. SetNotifyMySQLapplies the identical rule toDSN.- Neither helper ever emits
host,port,username,password, ordatabase. - An enabled target with an empty canonical connection string returns a
LegacyDatabaseTargetErroridentifying the subsystem (notify_postgresornotify_mysql) and target ID without echoing field values:
cmd/config-migrate.gochecks and propagates errors from both database migration helpers before persisting or activating the migrated configuration.initConfigSubsystempropagatesLegacyDatabaseTargetErroras a non-retriable fatal error toserverMain.
Operator Remediation
Because legacy JSON migration output is only persisted after a successful migration pass, a deployment that still boots from an old JSON configuration file will re-enter migration on startup. Operators with enabled discrete-only database targets should follow one of four paths:
- Before migrating to SILO: On the prior release, replace the discrete fields with
connection_stringordsn_string, verify event delivery, and then upgrade. - Disable and recreate: Disable or remove the legacy database target, upgrade the server, and recreate the target using
connection_stringordsn_string. - Fresh installation: Configure
notify_postgresornotify_mysqldirectly withconnection_stringordsn_string. - Existing deployment reading legacy JSON: Back up the source configuration, then convert, disable, or remove the discrete database target before starting SILO Server 20260903 or later.
Availability Trade-Off
This design intentionally converts an unsupported legacy database target from a degraded startup (where S3 I/O works while all bucket notifications are silently offline) into an immediate startup abort. An object store that appears healthy while dropping every configured audit or pipeline event creates unrecoverable downstream data loss; failing fast at startup forces the configuration error to be resolved before traffic is served.
Security Requirements
LegacyDatabaseTargetErrornever formats the legacy struct or its field values into the error message.- Regression tests inject sentinel passwords and assert that neither returned errors nor captured logs contain the sentinel.
- Migrated KV output uses only the registered sensitive keys (
connection_stringanddsn_string). - Operators who exported diagnostic bundles from an affected pre-20260903 deployment with discrete database targets should rotate that database password.
Rejected Alternatives
Register and Parse the Five Discrete Fields
Benefit: Preserves the pre-2020 JSON field names and reuses existing struct fields.
Reason rejected: Registering host, port, and password causes the shared KV tokenizer to split quoted connection_string values containing port= or host=, breaking the primary documented interface.
Synthesize a Canonical Connection String During Migration
Benefit: Automatically converts discrete-only legacy configurations.
Reason rejected: Requires permanent code and test maintenance for PostgreSQL quoting rules, MySQL DSN escaping, Unix sockets, IPv6 brackets, and driver defaults for a format deprecated since 2020.
Skip Only the Unsupported Target
Benefit: Keeps S3 object service and other notification targets online.
Reason rejected: Silently dropping an enabled notification target causes unannounced event loss.
Change Global FetchEnabledTargets Fail-Fast Behavior
Benefit: Isolates failures between independent notification subsystems.
Reason rejected: Does not fix the broken database migration helper or the unredacted password key, and alters system-wide notification startup semantics outside the scope of #53.
Remove Database Notification Targets Entirely
Benefit: Eliminates all database-specific notification code.
Reason rejected: PostgreSQL and MySQL notification targets remain useful and self-contained; the defect lies solely in the obsolete five-field configuration format.
Implementation Scope
The implementation in f1ba68358 touches six files across internal/config/notify/legacy.go, cmd/config-migrate.go, and cmd/server-main.go:
- Update
internal/config/notify/legacy.goso the two database setters emit only canonical registered keys and reject enabled targets lacking a canonical string. - Update
cmd/config-migrate.goto propagate the two database helper errors with subsystem and target context. - Define
LegacyDatabaseTargetErrorand updatecmd/server-main.gosoinitConfigSubsystemreturns it as a non-retriable fatal error instead of logging and continuing. - Remove all ten Postgres/MySQL entries from
knownUnregisteredWrites.
Acceptance Criteria
- Legacy PostgreSQL targets with a non-empty
connection_stringmigrate cleanly, passCheckValidKeys, and round-trip throughGetNotifyPostgres. - Legacy MySQL targets with a non-empty
dsn_stringmigrate cleanly, passCheckValidKeys, and round-trip throughGetNotifyMySQL. - Enabled discrete-only targets for either database fail before target initialization with
LegacyDatabaseTargetErrornaming the subsystem and target ID, aborting server startup. - Missing-string and malformed-string errors never include sentinel host, username, password, database, or DSN values.
- Disabled discrete legacy targets emit no KVS entries and do not block startup.
- Migrated KVS output never contains any of the ten discrete keys, even with empty values.
- When a legacy target contains both a canonical string and conflicting discrete values, only the canonical string is migrated.
SetKVSregression tests againstDefaultPostgresKVSandDefaultMySQLKVSaccept quoted connection strings containingport=,host=, andpassword=.- Configurations pairing healthy Webhook, Kafka, or NATS targets with an invalid legacy database target fail in
readConfigWithoutMigratewithout persisting or activating partial state. initConfigSubsystemreturnsLegacyDatabaseTargetErrordirectly without logging-and-continuing or entering the retry loop.knownUnregisteredWritesremoves all ten Postgres/MySQL exceptions.- All unit, race, and startup suites pass:
Implementation Result
Commit f1ba68358 implements the design without expanding the public configuration surface. Cross-repository verification confirmed that pgsty/mc, pgsty/silo-pkg, and pgsty/silo-console require no changes because mcli forwards raw KV text and Console already serializes database forms into connection_string and dsn_string.
Release Compatibility Statement
SILO database notification targets require connection_string for PostgreSQL and dsn_string for MySQL. The pre-2020 discrete host/port/username/password/database form is not migrated; convert, disable, or remove such targets before upgrading to SILO Server 20260903 or later.
Design Review Calibration
During design review of the initial specification, four calibrations were incorporated before landing the commit:
- extending fatal error propagation through
initConfigSubsystemtoExitFunc(1)soserverMainterminates vialogger.FatalIfinstead of starting withoutglobalServerConfig; - documenting that existing deployments still booting from unmigrated legacy JSON files also hit the startup gate;
- specifying canonical-string precedence when a legacy JSON record contains both
ConnectionString/DSNand leftover discrete fields; - leaving error handling of the other eight non-database legacy notification setters unchanged to keep
#53tightly scoped.