Module: Pgbus::Client::EnsureStreamQueue

Included in:
Pgbus::Client
Defined in:
lib/pgbus/client/ensure_stream_queue.rb

Overview

Idempotent stream-queue setup. Creates the PGMQ queue (delegating to ensure_queue which already handles schema bootstrap and dedup), overrides the NOTIFY throttle to 0 so every broadcast fires its own NOTIFY, and adds an msg_id index on the archive table that PGMQ does not ship with.

PGMQ's archive tables (pgmq.a_<name>) only carry an archived_at index by default. Client#read_after's replay query filters by WHERE msg_id > $1, which becomes a sequential scan once the archive grows past a few thousand rows. We add the index here, scoped to stream queues only, so users with chat-history-style retention don't hit a performance cliff.

Called from Pgbus.stream(name).broadcast(...) on first publish per stream and from the streamer on first subscription per stream.

Instance Method Summary collapse

Instance Method Details

#ensure_stream_queue(stream_name) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/pgbus/client/ensure_stream_queue.rb', line 21

def ensure_stream_queue(stream_name)
  full_name = config.queue_name(stream_name)

  with_stale_connection_retry do
    # Create the BARE queue directly. ensure_queue would fan out through
    # the priority strategy to _p0.._pN under priority_levels>1, leaving
    # the bare queue — the one the streamer NOTIFYs on and read_after
    # peeks — uncreated, and the enable_notify_if_needed below would then
    # raise on the missing bare table. Streams never use priority
    # sub-queues (issue #310).
    ensure_pgmq_schema
    ensure_single_queue(full_name)

    # PGMQ's default NOTIFY throttle is 250ms — meant to coalesce
    # high-frequency worker queue inserts. Streams are latency-
    # sensitive and need every broadcast to fire a NOTIFY, even
    # when several are batched within a single millisecond.
    # Override the throttle to 0 specifically for stream queues.
    # Use the idempotent path to avoid deadlocks when multiple
    # processes race to set up the same stream queue.
    synchronized { enable_notify_if_needed(full_name, 0) }
  end

  # CREATE INDEX IF NOT EXISTS is idempotent in Postgres but still
  # requires a roundtrip and a brief ACCESS SHARE lock on the archive
  # table. Broadcast-per-after_commit loops can hit this 1000x/sec on
  # the same stream, so memoize per-process after the first success.
  # The StreamQueue registration shares this memo — both are one-time
  # per stream per process and must both survive a first-broadcast.
  return if @stream_indexes_created[stream_name]

  sanitized = QueueNameValidator.sanitize!(full_name)
  sql = <<~SQL
    CREATE INDEX IF NOT EXISTS a_#{sanitized}_msg_id_idx
    ON pgmq.a_#{sanitized} (msg_id)
  SQL

  synchronized do
    with_raw_connection do |conn|
      conn.exec(sql)
    end
  end

  # Record the physical queue name so maintenance (stream-archive prune,
  # orphan sweep, compact_archives) and wildcard workers can tell this
  # queue apart from a job queue. No-ops on unmigrated installs.
  Pgbus::StreamQueue.record!(full_name)

  @stream_indexes_created[stream_name] = true
end