Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ PostgreSQL-native job processing and event bus for Rails, built on [PGMQ](https:

📖 **Documentation:** [pgbus.zoolutions.llc](https://pgbus.zoolutions.llc) — guides, flow diagrams, and a full configuration reference. (This README stays the canonical GitHub reference.)

[![Ruby](https://github.com/mhenrixon/pgbus/actions/workflows/main.yml/badge.svg)](https://github.com/mhenrixon/pgbus/actions/workflows/main.yml)
[![Ruby](https://github.com/zoolutions/pgbus/actions/workflows/main.yml/badge.svg)](https://github.com/zoolutions/pgbus/actions/workflows/main.yml)

## Table of contents

Expand Down Expand Up @@ -1220,9 +1220,9 @@ pgbus-health --port 9394 # or PGBUS_HEALTH_PORT=9394 pgbus-health
pgbus-health --port 9394 --path /livez --timeout 2
```

### Rolling restarts (Kamal, docker)
### Rolling restarts (dash, docker)

Kamal distributions with per-role health checks (for example the [`dash` branch](https://github.com/mhenrixon/kamal)) can rolling-restart a non-proxied job role: start the new container, poll its docker `HEALTHCHECK` until healthy, and only then `docker stop` the old one. Wire the pgbus container into that gate:
[dash](https://github.com/zoolutions/dash) (per-role health checks) can rolling-restart a non-proxied job role: start the new container, poll its docker `HEALTHCHECK` until healthy, and only then `docker stop` the old one. Wire the pgbus container into that gate:

```yaml
# config/deploy.yml
Expand Down Expand Up @@ -1253,7 +1253,7 @@ If the orchestrator's stop grace period is *shorter* than `shutdown_timeout`, do

**The overlap window is safe by construction.** Between "new container healthy" and "old container stopped", two supervisors run against the same database. Nothing double-fires: queue claims use `FOR UPDATE SKIP LOCKED`, `single_active_consumer` queues arbitrate via session-level advisory locks (released the instant a killed process's connection dies), two live recurring schedulers dedup on the `(task_key, run_at)` unique record, and dispatcher maintenance is idempotent. "One scheduler per deployment" is a steady-state rule; a deploy window may briefly violate it without consequence.

**What a hard kill still costs.** Jobs killed past the drain window are redelivered after their visibility timeout (at-least-once holds) — but PGMQ's `read_ct` increments exactly like a logical failure, so a long-running job that straddles *repeated* deploy kills can be pushed to the DLQ without its code ever raising. `zombie_detection` logs exactly this pattern (`read_ct > 1` with no recorded failure). Keep jobs shorter than `drain_timeout`, or raise it (and `stop_timeout`) for queues that can't be. For `idempotent!` event handlers there is a separate crash-window caveat tracked in [#385](https://github.com/mhenrixon/pgbus/issues/385).
**What a hard kill still costs.** Jobs killed past the drain window are redelivered after their visibility timeout (at-least-once holds) — but PGMQ's `read_ct` increments exactly like a logical failure, so a long-running job that straddles *repeated* deploy kills can be pushed to the DLQ without its code ever raising. `zombie_detection` logs exactly this pattern (`read_ct > 1` with no recorded failure). Keep jobs shorter than `drain_timeout`, or raise it (and `stop_timeout`) for queues that can't be. For `idempotent!` event handlers there is a separate crash-window caveat tracked in [#385](https://github.com/zoolutions/pgbus/issues/385).

### Boot diagnostics banner

Expand Down
4 changes: 2 additions & 2 deletions docs/.kamal/secrets → docs/.dash/secrets
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# credential Kamal needs is the registry token.
#
# Prefer the env var, else fall back to the gh CLI token:
# - CI: the deploy workflow sets KAMAL_REGISTRY_PASSWORD from the built-in
# - CI: the deploy workflow sets DASH_REGISTRY_PASSWORD from the built-in
# GITHUB_TOKEN, so the env branch is taken and `gh` is never called.
# - Local: the env var is unset, so it falls back to `gh auth token` — a bare
# `kamal deploy` / `kamal setup` works with no manual export.
Expand All @@ -13,4 +13,4 @@
# `${VAR:-default}` (that resolves to a malformed `:-<token>}` → `denied`).
# dotenv DOES run `$(...)` command substitution, so do the env-or-gh choice inside
# a shell command rather than with dotenv's unsupported `:-` operator.
KAMAL_REGISTRY_PASSWORD=$(test -n "$KAMAL_REGISTRY_PASSWORD" && printf %s "$KAMAL_REGISTRY_PASSWORD" || gh auth token)
DASH_REGISTRY_PASSWORD=$(test -n "$DASH_REGISTRY_PASSWORD" && printf %s "$DASH_REGISTRY_PASSWORD" || gh auth token)
1 change: 1 addition & 0 deletions docs/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

# CI / deploy / Docker meta — not part of the runtime image.
/.github/
/.dash/
/.kamal/
/Dockerfile*
/.dockerignore
Expand Down
52 changes: 49 additions & 3 deletions docs/config/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
# Kamal deploy → the oss-infrastructure server (Cloudflare Tunnel + kamal-proxy).
# dash deploy → the oss-infrastructure server (Cloudflare Tunnel + dash-proxy).
# `dash docs` / `dash docs proxy` is the always-current reference for every key.
# service/image = the repo OWNER/REPO so the ghcr package auto-links to the
# repo and GITHUB_TOKEN can push + pull it (no PAT). See docs-kit's README.
service: pgbus
image: zoolutions/pgbus

# dash 4 renamed the on-host proxy (kamal-proxy → dash-proxy) and migrates a
# host in place; an older CLI must not deploy this config.
minimum_version: 4.0.0

# A stateless docs site never rolls back far — keep the host tidy.
retain_containers: 2

# Status-named pages (public/502.html, 503, 504) the proxy serves in place of
# the app's during a deploy gap — paired with `proxy.intercept_errors` below.
error_pages_path: public

servers:
web:
hosts:
Expand All @@ -15,21 +27,55 @@ ssh:
proxy:
host: <%= ENV["DEPLOY_DOMAIN"] %>
app_port: 3000
# TLS terminates at Cloudflare; the tunnel reaches the proxy over plain HTTP.
ssl: false
healthcheck:
path: /up
interval: 5
timeout: 30

# --- dash-proxy per-app features ------------------------------------------
# zstd / br / gzip negotiated at the edge; responses the app already encoded
# (Thruster) pass through untouched.
compress: true

# RFC 9111 shared cache. Only responses the app marks `Cache-Control: public,
# max-age` are stored (Propshaft assets, /llms*.txt) — HTML carrying a session
# cookie is refused by design. `dash proxy cache stats` shows what it holds.
cache:
enabled: true
max_ttl: 300

# Security headers set once here instead of per app; drop server fingerprints.
headers:
response:
set:
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
remove:
- Server
- X-Powered-By

# Serve public/<status>.html instead of a bare "Bad Gateway" while a
# container is swapped or unhealthy.
intercept_errors:
- 502
- 503
- 504

# Keep the health probe out of the request histograms.
exclude_metrics_paths:
- /up

registry:
server: ghcr.io
username: mhenrixon
password:
- KAMAL_REGISTRY_PASSWORD
- DASH_REGISTRY_PASSWORD

# The build context is the REPO ROOT (gem + app): the docs app depends on the
# gem via `gem "pgbus", path: ".."`, which the Docker build can only resolve if
# the whole repo is in the context. Kamal runs from docs/, so `dockerfile` is
# the whole repo is in the context. dash runs from docs/, so `dockerfile` is
# relative to docs/ (just "Dockerfile") while `context: ..` points at the root.
builder:
arch: amd64
Expand Down
135 changes: 135 additions & 0 deletions docs/public/502.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<!doctype html>

<html lang="en">

<head>

<title>We're sorry, but something went wrong (500 Internal Server Error)</title>

<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, width=device-width">
<meta name="robots" content="noindex, nofollow">

<style>

*, *::before, *::after {
box-sizing: border-box;
}

* {
margin: 0;
}

html {
font-size: 16px;
}

body {
background: #FFF;
color: #261B23;
display: grid;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: clamp(1rem, 2.5vw, 2rem);
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: 400;
letter-spacing: -0.0025em;
line-height: 1.4;
min-height: 100dvh;
place-items: center;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
}

#error-description {
fill: #d30001;
}

#error-id {
fill: #f0eff0;
}

@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}

#error-description {
fill: #FF6161;
}

#error-id {
fill: #2c2c2c;
}
}

a {
color: inherit;
font-weight: 700;
text-decoration: underline;
text-underline-offset: 0.0925em;
}

b, strong {
font-weight: 700;
}

i, em {
font-style: italic;
}

main {
display: grid;
gap: 1em;
padding: 2em;
place-items: center;
text-align: center;
}

main header {
width: min(100%, 12em);
}

main header svg {
height: auto;
max-width: 100%;
width: 100%;
}

main article {
width: min(100%, 30em);
}

main article p {
font-size: 75%;
}

main article br {
display: none;

@media(min-width: 48em) {
display: inline;
}
}

</style>

</head>

<body>

<!-- This file lives in public/500.html -->

<main>
<header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m101.23 93.8427c-8.1103 0-15.4098 3.7849-19.7354 8.3813h-36.2269v-99.21891h103.8143v37.03791h-68.3984v24.8722c5.1366-2.7035 15.1396-5.9477 24.6014-5.9477 35.146 0 56.233 22.7094 56.233 55.4215 0 34.605-23.791 57.315-60.558 57.315-37.8492 0-61.64-22.169-63.8028-55.963h42.9857c1.0814 10.814 9.1919 19.195 21.6281 19.195 11.355 0 19.465-8.381 19.465-20.547 0-11.625-7.299-20.5463-20.006-20.5463zm138.833 77.8613c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" id="error-id"/><path d="m23.1377 68.9967v34.0033h-8.9162v-34.0033zm4.3157 34.0033v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm29.9913-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.5839v6.8671h5.2058v6.7564h-5.2058v8.307c0 1.9383.9415 2.769 2.6583 2.769.9414 0 1.9937-.2216 2.769-.5538v7.3654c-.9969.443-2.8798.775-4.8181.775-5.8703 0-9.1931-2.769-9.1931-9.0819zm32.3666-.1108h8.0301c-.8861 5.7597-5.2057 9.2487-11.6852 9.2487-7.6424 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.3165-13.0143 12.5159-13.0143 7.6424 0 11.9621 5.095 11.9621 12.5159v2.1598h-16.1156c.2769 2.9905 1.8275 4.5965 4.3196 4.5965 1.7722 0 3.1567-.7753 3.6551-2.4921zm-3.8212-10.0237c-2.0491 0-3.4336 1.2737-3.9874 3.5997h7.5317c-.1107-2.0491-1.3845-3.5997-3.5443-3.5997zm31.4299-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.599-.7753-2.382 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.381.443zm2.949 25.0318v-24.921h8.694v2.1598c1.385-1.5506 3.821-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.64v-14.2327c0-2.049-1.052-3.5443-3.267-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm50.371 0h-8.363v-1.274c-.83.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.433.7199-3.433 2.3813 0 1.7168 1.716 2.4367 3.433 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742-29.0191v35.997h-8.694v-35.997zm13.036 25.9178h9.248c.72 2.326 2.714 3.489 5.483 3.489 2.713 0 4.596-1.163 4.596-3.2674 0-1.6061-1.052-2.326-3.212-2.8244l-6.534-1.3845c-4.985-1.1076-8.751-3.7105-8.751-9.47 0-6.6456 5.538-11.0206 13.07-11.0206 8.307 0 13.014 4.5411 13.956 10.4114h-8.695c-.72-1.8829-2.27-3.3228-5.205-3.3228-2.548 0-4.265 1.1076-4.265 2.9905 0 1.4953 1.052 2.326 2.825 2.7137l6.645 1.5506c5.815 1.3845 9.027 4.5412 9.027 9.8023 0 6.9778-5.87 10.9654-13.291 10.9654-8.141 0-13.679-3.9322-14.897-10.6332zm46.509 1.3845h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm31.431-6.3134v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.382.443zm18.288 25.0318h-7.809l-9.47-24.921h8.861l4.763 14.288 4.652-14.288h8.528zm25.614-8.6947h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997zm31.43-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443zm13.703-8.9715h24.312v7.6424h-15.562v5.3165h14.232v7.4763h-14.232v5.8703h15.562v7.6978h-24.312zm44.667 8.9715v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm19.673 0v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm28.082-12.5713v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443z" id="error-description"/></svg>
</header>
<article>
<p><strong>We're sorry, but something went wrong.</strong><br> If you're the application owner check the logs for more information.</p>
</article>
</main>

</body>

</html>
Loading
Loading