<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="385592" data-post-id="385592">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sorenone" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sorenone/120/34656_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sorenone
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Oban Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p><a href="https://github.com/oban-bg/oban/releases" rel="noopener nofollow ugc">Oban v.2.21.0</a> has been released!</p>
<p>This release requires PostgreSQL 14+, adds a new <code>suspended</code> job state, includes targeted performance improvements for job execution and notifications, and a variety of bug fixes.</p>
<p>See the <a href="https://hexdocs.pm/oban/v2-21.html" rel="noopener nofollow ugc">Upgrade Guide</a> for upgrade instructions.</p>
<h2><a name="p-385592-suspended-state-1" class="anchor" href="#p-385592-suspended-state-1" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/suspension_railway.png?v=15" title=":suspension_railway:" class="emoji" alt=":suspension_railway:" loading="lazy" width="20" height="20"> Suspended State</h2>
<p>The new <code>suspended</code> state allows jobs to be held without processing until they are explicitly resumed. Unlike <code>scheduled</code> jobs that become <code>available</code> when their time comes, suspended jobs remain paused indefinitely until an external action resumes them.</p>
<p>While Oban itself doesn’t make use of suspended jobs, the state enables Pro workflows to defer execution without any workarounds or performance impact.</p>
<h2><a name="p-385592-performance-tweaks-2" class="anchor" href="#p-385592-performance-tweaks-2" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/straight_ruler.png?v=15" title=":straight_ruler:" class="emoji" alt=":straight_ruler:" loading="lazy" width="20" height="20"> Performance Tweaks</h2>
<p>Two targeted optimizations reduce overhead in high-throughput systems:</p>
<ul>
<li>
<p>Selective Compression — Notifications under 512 bytes skip gzip compression entirely, avoiding CPU overhead for typical small messages like queue signals and insert events. Encoding is 12x faster for small payloads (4μs → 0.3μs) and wire size is halved (80 → 41 bytes). Large payloads still compress with no regression.</p>
</li>
<li>
<p>Batched Process Metrics — Job execution telemetry now gathers memory and reduction metrics in a single <code>Process.info/2</code> call instead of two separate calls, cutting per-job measurement overhead in half.</p>
</li>
</ul>
<h2><a name="p-385592-v2210-2026-03-23-3" class="anchor" href="#p-385592-v2210-2026-03-23-3" aria-label="Heading link" rel="nofollow"></a>v2.21.0 — 2026-03-23</h2>
<h3><a name="p-385592-changes-4" class="anchor" href="#p-385592-changes-4" aria-label="Heading link" rel="nofollow"></a>Changes</h3>
<ul>
<li>
<p>[Oban] Support a minimum of PostgreSQL 14+</p>
<p>PG 12 was end of life in November 2024, and PG 13 was end of life in November 2025. We now support PG 14+, and with PG 19 due out in a few months, we’re dropping official support for older versions.</p>
</li>
</ul>
<h3><a name="p-385592-enhancements-5" class="anchor" href="#p-385592-enhancements-5" aria-label="Heading link" rel="nofollow"></a>Enhancements</h3>
<ul>
<li>
<p>[Oban] Add suspended job state</p>
<p>The suspended state allows jobs to be held without processing until they are explicitly resumed. It is accepted for unique and replace operations, and is part of the incomplete state group. The suspended state is used by Pro extensions rather than by Oban itself.</p>
</li>
<li>
<p>[Worker] Elevate <code>__opts__/0</code> to a documented callback</p>
<p>The <code>__opts__/0</code> function, which returns a worker’s compile-time options, is now a public callback with full documentation. This makes it easier to introspect worker configuration at runtime, such as checking the default queue, max attempts, or uniqueness settings for any worker module.</p>
</li>
<li>
<p>[Period] Document and publicize <code>Oban.Period</code> module</p>
<p>The <code>t:Period.t()</code> type is referenced by several public types and should be visible to users in documentation.</p>
</li>
<li>
<p>[Executor] Batch process info calls in executor measurements</p>
<p>Reduces system call overhead by combining separate memory and reductions queries into a single <code>Process.info/2</code> call per job execution.</p>
</li>
<li>
<p>[Notifier] Skip compression for small notification payloads</p>
<p>Notifications under 512 bytes are sent as plain JSON, avoiding gzip overhead for typical small messages like queue signals and insert events.</p>
<p>Encode is 12x faster for small payloads (4.08 μs → 0.34 μs) and decode is 6.7x faster (1.78 μs → 0.26 μs). Wire size is halved (80 → 41 bytes).</p>
<p>Large payloads retain compression with no performance regression.</p>
</li>
<li>
<p>[Notifier] Remove wrapper from notifier LISTEN/UNLISTEN</p>
<p>SimpleConnection uses the simple query protocol which handles multiple semicolon-separated statements directly, eliminating the need for a <code>DO $$BEGIN ... END$$</code> anonymous block. This makes the Postgres notifier <em>more</em> compatible with Postgres-compatible databases like PlanetScale.</p>
</li>
</ul>
<h3><a name="p-385592-bug-fixes-6" class="anchor" href="#p-385592-bug-fixes-6" aria-label="Heading link" rel="nofollow"></a>Bug Fixes</h3>
<ul>
<li>
<p>[Testing] Support snooze periods with testing helpers</p>
<p>The <code>perform_job/3</code> helper wasn’t aware of snooze periods and considered snoozing with a period an error.</p>
</li>
<li>
<p>[Oban] Correct type checking for <code>insert_all/3</code> streams</p>
<p>Some stream functions return a multi arity function rather than a Struct. This updates the <code>Oban.insert_all</code> guard to handle all stream variants properly. Thanks Elixir 1.20!</p>
</li>
<li>
<p>[Notifier] Fix Sonar and Midwife listener loss after Notifier crash</p>
<p>Both Sonar and Midwife register as listeners on the Notifier during init, but under the one_for_one supervisor strategy, a Notifier crash only restarts the Notifier rather than any siblings. The new Notifier starts with an empty listener map, silently breaking notification delivery.</p>
<p>For Sonar, this means pings are sent but never received back, causing it to degrade to :isolated status. For Midwife, signal notifications (queue start/stop) are never delivered.</p>
</li>
<li>
<p>[Stager] Protect from Notifier crash during staging</p>
<p>Prevent an exit from a dead or dying Notifier from cascading into a Stager crash.</p>
</li>
<li>
<p>[Job] Update <code>t:unique_option/0</code> to support state groups</p>
</li>
</ul> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="385592" data-batch-url="/posts/batch_likers">
                        5
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/oban-reliable-and-observable-job-processing/22449/318">Post #317</a>
	                </div>
	            </div>
              <div id="likers-container-385592" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="385592"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>