@@ -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
7886var 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.
333371func (db * Notifier ) CreateSchema () error {
0 commit comments