Repository: openfga/openfga

Issue:
OpenFGA fails to start when using secondary db with db metrics enabled
### Checklist

- [x] I have looked into the [README](https://github.com/openfga/openfga/blob/main/README.md) and have not found a suitable solution or answer.
- [x] I have looked into the [documentation](https://openfga.dev/docs) and have not found a suitable solution or answer.
- [x] I have searched the [issues](https://github.com/openfga/openfga/issues) and have not found a suitable solution or answer.
- [x] I have upgraded to the [latest version](https://github.com/openfga/openfga/releases/latest) of OpenFGA and the issue still persists.
- [x] I have searched the [OpenFGA Community](https://openfga.dev/community) and have not found a suitable solution or answer.
- [x] I agree to the terms within the [OpenFGA Code of Conduct](https://github.com/openfga/.github/blob/main/CODE_OF_CONDUCT.md).

### Description

OpenFGA fails to start when configured to use secondary postgres DB and data store metrics enabled.

Steps to reproduce:
1. Enable postgres secondary DB (add `OPENFGA_DATASTORE_SECONDARY_URI` env var)
2. Set `OPENFGA_DATASTORE_METRICS_ENABLED` to `"true"`
3. Start OpenFGA

Observe
```
panic: initialize postgres datastore: configure secondary db: initialize metrics: duplicate metrics collector registration attempted
```


### OpenFGA version

v1.9.2

### How are you running OpenFGA?

In Docker

### What datastore are you using?

Postgres

### OpenFGA Flags

OPENFGA_DATASTORE_METRICS_ENABLED and OPENFGA_METRICS_ENABLED

### Logs

panic: initialize postgres datastore: configure secondary db: initialize metrics: duplicate metrics collector registration attempted



Discussion:

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 975c2f93..1fe94c10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ Try to keep listed changes to a concise bulleted list of simple explanations of
 - Fixed bug in how experimental ReverseExpand is handling duplicate TTUs. [#2589](https://github.com/openfga/openfga/pull/2589)
 - Fixed bug in how experimental ReverseExpand is handling duplicate edge traversals. [#2594](https://github.com/openfga/openfga/pull/2594)
 - Fixed logs in ListObjects weighted graph to include `store_id` and `authorization_model_id` through the context. [#2581](https://github.com/openfga/openfga/pull/2581)
+- Fixed bug where OpenFGA fail to start when both secondary DB and db metrics enabled. [#2598](https://github.com/openfga/openfga/pull/2598)
 
 ## [1.9.2] - 2025-07-24
 ### Added
diff --git a/pkg/storage/postgres/postgres.go b/pkg/storage/postgres/postgres.go
index d46dd79d..89a1bc83 100644
--- a/pkg/storage/postgres/postgres.go
+++ b/pkg/storage/postgres/postgres.go
@@ -119,7 +119,7 @@ func New(uri string, cfg *sqlcommon.Config) (*Datastore, error) {
 	return NewWithDB(primaryDB, secondaryDB, cfg)
 }
 
-func configureDB(db *sql.DB, cfg *sqlcommon.Config) (*sqlcommon.DBInfo, sq.StatementBuilderType, prometheus.Collector, error) {
+func configureDB(db *sql.DB, cfg *sqlcommon.Config, dbName string) (*sqlcommon.DBInfo, sq.StatementBuilderType, prometheus.Collector, error) {
 	var stbl sq.StatementBuilderType
 	policy := backoff.NewExponentialBackOff()
 	policy.MaxElapsedTime = 1 * time.Minute
@@ -139,7 +139,7 @@ func configureDB(db *sql.DB, cfg *sqlcommon.Config) (*sqlcommon.DBInfo, sq.State
 
 	var collector prometheus.Collector
 	if cfg.ExportMetrics {
-		collector = collectors.NewDBStatsCollector(db, "openfga")
+		collector = collectors.NewDBStatsCollector(db, dbName)
 		if err := prometheus.Register(collector); err != nil {
 			return nil, stbl, nil, fmt.Errorf("initialize metrics: %w", err)
 		}
@@ -153,7 +153,7 @@ func configureDB(db *sql.DB, cfg *sqlcommon.Config) (*sqlcommon.DBInfo, sq.State
 
 // NewWithDB creates a new [Datastore] storage with the provided database connection.
 func NewWithDB(primaryDB, secondaryDB *sql.DB, cfg *sqlcommon.Config) (*Datastore, error) {
-	primaryDBInfo, primaryStbl, primaryCollector, err := configureDB(primaryDB, cfg)
+	primaryDBInfo, primaryStbl, primaryCollector, err := configureDB(primaryDB, cfg, "openfga")
 	if err != nil {
 		return nil, fmt.Errorf("configure primary db: %w", err)
 	}
@@ -162,7 +162,7 @@ func NewWithDB(primaryDB, secondaryDB *sql.DB, cfg *sqlcommon.Config) (*Datastor
 	var secondaryStbl sq.StatementBuilderType
 	var secondaryCollector prometheus.Collector
 	if secondaryDB != nil {
-		secondaryDBInfo, secondaryStbl, secondaryCollector, err = configureDB(secondaryDB, cfg)
+		secondaryDBInfo, secondaryStbl, secondaryCollector, err = configureDB(secondaryDB, cfg, "openfga_secondary")
 		if err != nil {
 			return nil, fmt.Errorf("configure secondary db: %w", err)
 		}



Test patch:
diff --git a/pkg/storage/postgres/postgres_test.go b/pkg/storage/postgres/postgres_test.go
index bdd65d53..a726ea4f 100644
--- a/pkg/storage/postgres/postgres_test.go
+++ b/pkg/storage/postgres/postgres_test.go
@@ -33,14 +33,32 @@ func TestPostgresDatastore(t *testing.T) {
 	test.RunAllTests(t, ds)
 }
 
+func TestPostgresDatastoreStartup(t *testing.T) {
+	primaryDatastore := storagefixtures.RunDatastoreTestContainer(t, "postgres")
+	primaryURI := primaryDatastore.GetConnectionURI(true)
+
+	cfg := sqlcommon.NewConfig()
+	cfg.ExportMetrics = true
+
+	ds, err := New(primaryURI, cfg)
+	require.NoError(t, err)
+	defer ds.Close()
+
+	status, err := ds.IsReady(context.Background())
+	require.NoError(t, err)
+	require.True(t, status.IsReady)
+}
+
 func TestPostgresDatastoreStatusWithSecondaryDB(t *testing.T) {
 	primaryDatastore := storagefixtures.RunDatastoreTestContainer(t, "postgres")
-	primaryDatastore.CreateSecondary(t)
+	err := primaryDatastore.CreateSecondary(t)
+	require.NoError(t, err)
 
 	primaryURI := primaryDatastore.GetConnectionURI(true)
 
 	cfg := sqlcommon.NewConfig()
 	cfg.SecondaryURI = primaryDatastore.GetSecondaryConnectionURI(true)
+	cfg.ExportMetrics = true
 
 	ds, err := New(primaryURI, cfg)
 	require.NoError(t, err)