Skip to content

Commit d7e192d

Browse files
authored
perf(postgres): install notification triggers lazily on first Subscribe (#1935)
Signed-off-by: Evan <evanyan@sign.global>
1 parent ca3b723 commit d7e192d

4 files changed

Lines changed: 303 additions & 16 deletions

File tree

token/services/storage/db/sql/postgres/driver.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ func newTokenStoreProvider(dbProvider fscPostgres.DbProvider, tableNamesConfig c
106106
if err != nil {
107107
return nil, err
108108
}
109+
if o.SkipCreateTable {
110+
notifier.skipSchemaManagement()
111+
}
109112

110113
// db
111114
p, err := NewTokenStoreWithNotifier(dbs, tableNames, notifier)
@@ -116,9 +119,6 @@ func newTokenStoreProvider(dbProvider fscPostgres.DbProvider, tableNamesConfig c
116119
if err := p.CreateSchema(); err != nil {
117120
return nil, err
118121
}
119-
if err := notifier.CreateSchema(); err != nil {
120-
return nil, err
121-
}
122122
}
123123

124124
return p, nil
@@ -151,20 +151,14 @@ func newIdentityStoreProvider(dbProvider fscPostgres.DbProvider, tableNamesConfi
151151
if err != nil {
152152
return nil, err
153153
}
154-
155-
// Get notifier for schema creation
156-
notifier, err := NewIdentityNotifier(dbs, tableNames, o.DataSource)
157-
if err != nil {
158-
return nil, err
154+
if o.SkipCreateTable {
155+
p.notifier.skipSchemaManagement()
159156
}
160157

161158
if !o.SkipCreateTable {
162159
if err := p.CreateSchema(); err != nil {
163160
return nil, err
164161
}
165-
if err := notifier.CreateSchema(); err != nil {
166-
return nil, err
167-
}
168162
}
169163

170164
return p, nil
@@ -197,6 +191,9 @@ func newTransactionStoreProvider(dbProvider fscPostgres.DbProvider, tableNamesCo
197191
if err != nil {
198192
return nil, err
199193
}
194+
if o.SkipCreateTable {
195+
notifier.skipSchemaManagement()
196+
}
200197

201198
// db
202199
p, err := NewTransactionStoreWithNotifier(dbs, tableNames, notifier)
@@ -207,9 +204,6 @@ func newTransactionStoreProvider(dbProvider fscPostgres.DbProvider, tableNamesCo
207204
if err := p.CreateSchema(); err != nil {
208205
return nil, err
209206
}
210-
if err := notifier.CreateSchema(); err != nil {
211-
return nil, err
212-
}
213207
}
214208

215209
return p, nil

token/services/storage/db/sql/postgres/identity.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import (
2121
// IdentityStore wraps common.IdentityStore to add advisory lock to schema creation
2222
type IdentityStore struct {
2323
*sqlcommon.IdentityStore
24-
writeDB *sql.DB
25-
lockID int64
24+
writeDB *sql.DB
25+
lockID int64
26+
notifier *IdentityNotifier
2627
}
2728

2829
// GetSchema overrides the base GetSchema to prefix with advisory lock
@@ -62,6 +63,7 @@ func NewIdentityStore(dbs *scommon.RWDB, tableNames sqlcommon.TableNames, dataSo
6263
IdentityStore: baseStore,
6364
writeDB: dbs.WriteDB,
6465
lockID: createTableLockID("identity"),
66+
notifier: notifier,
6567
}, nil
6668
}
6769

token/services/storage/db/sql/postgres/notifier.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ type Notifier struct {
7373
// channelName is the name of the channel on which to receive notifications.
7474
// It must be smaller than 63 characters per Postgres limit
7575
channelName string
76+
// ensureSchema installs the notification trigger on first subscription.
77+
// Set by NewNotifier to CreateSchema; nil skips all runtime DDL and the
78+
// deployment must pre-create the trigger (SkipCreateTable).
79+
ensureSchema func() error
80+
// startupErr records a lazy-startup failure: the trigger installation
81+
// failed or the notifier was closed mid-install. Written once inside
82+
// startOnce; the failure is final and every Subscribe returns it.
83+
startupErr error
7684
}
7785

7886
var logger = logging.MustGetLogger()
@@ -139,6 +147,7 @@ func NewNotifier(
139147
closed: false,
140148
channelName: channelName,
141149
}
150+
n.ensureSchema = n.CreateSchema
142151

143152
// attach handler that calls the subscribers
144153
n.listener.Handle(channelName, &notificationHandler{
@@ -191,6 +200,23 @@ func (db *Notifier) Subscribe(callback driver.TriggerCallback) error {
191200
db.startOnce.Do(func() {
192201
justStarted = true
193202
logger.Debugf("First subscription for notifier of [%s]. Notifier starts listening...", db.table)
203+
// The notification trigger is installed on first subscription rather
204+
// than at store creation: tables nobody subscribes to must not pay
205+
// the per-row pg_notify cost (NOTIFY serializes transaction commits
206+
// on a global queue lock).
207+
if db.ensureSchema != nil {
208+
if err := db.ensureSchema(); err != nil {
209+
db.startupErr = errors.Wrapf(err, "failed creating notification schema for [%s]", db.table)
210+
211+
return
212+
}
213+
}
214+
// the notifier may have been closed while the schema was installing
215+
if err := db.ctx.Err(); err != nil {
216+
db.startupErr = err
217+
218+
return
219+
}
194220
db.listenerWg.Go(func() {
195221
if err := db.listener.Listen(db.ctx); err != nil {
196222
// Send error to both the error channel and log it
@@ -204,6 +230,12 @@ func (db *Notifier) Subscribe(callback driver.TriggerCallback) error {
204230
})
205231
})
206232

233+
// startOnce.Do guarantees the write inside the closure is visible here.
234+
// A startup failure is final: every subscription returns the same error.
235+
if db.startupErr != nil {
236+
return db.startupErr
237+
}
238+
207239
if justStarted {
208240
// Wait a bit to see if it fails immediately
209241
timer := time.NewTimer(100 * time.Millisecond)
@@ -328,6 +360,12 @@ func (db *Notifier) GetSchema() string {
328360
)
329361
}
330362

363+
// skipSchemaManagement disables the lazy trigger installation; the deployment
364+
// is expected to pre-create the notification schema.
365+
func (db *Notifier) skipSchemaManagement() {
366+
db.ensureSchema = nil
367+
}
368+
331369
// CreateSchema creates the notification objects in the database.
332370
// It returns an error if the schema creation fails.
333371
func (db *Notifier) CreateSchema() error {

0 commit comments

Comments
 (0)