{
    "componentChunkName": "component---src-templates-post-template-js",
    "path": "/en/how-soon-is-now-in-postgresql/",
    "result": {"data":{"post":{"id":"74c6b051-31b9-593d-ad2c-2c3be70905d1","html":"<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/38bff92c4f54acc9e02aab58e8a3d196/7032b/2026-05-25-cover.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAADGElEQVQozwXB2U/TBwDA8d/zkj0v29seFrO9EJdF3TI3Qgg6DItCRTkULZMiQoWCtJWz1q3j6LjK0XEMEShHD6C00l+hd6G1FAq0sYhMxjKftuwvWL77fARX30OsXXKMOiX6dhVPm5tQKNRU1DTR2Khm6Ukx1rYbTD0uYEp5jWcqCcaHVxisykYlOU/xzRs4XRt4/WHsDhfC7NP7XJH3c1nxjLt1Rtoe9WGzeqnQWqho6cVpqGWxr55JfQ3TnXKsA43M/1iOoSoT6dVMZhaXOHzzFtO8mRcuD8K8tpy8GgNRMchb+zKvJyf47ziJw+ymsK4Tc4eMGY0Uc+cDJjT3GGuV0l97lfHKizy4VcBmLMF+Ks3k1Azx3QMEc3sJWeWdrNo8nC5bCA6P4Rgz0aro4ONCHYOK66SWx0gnk6QT2yRiMWyzo2hl31GWn4PVLpJKH5NMHRHc3EYwqfLJlmq4XdWLommEnKphztzp53J1F81d42juF7Fut3D8178kkkccpE8Ih0MsL0zQUFWGUi4jEt1C9K7jWltDMGvLCPtXEZ1L+INuNkIxAonXWBwikd19upUVzP02yqs3f7KfPCQaS7BknmHVMsEvuhZMQx34nM9ZfK5nVN+EYGot5e/TKL+n/RynRP55d8LpH++w2VYw2V7Q11qLvraUSMCHN+Ble3sP3+oavw4Y0LW1EInsMm9aYHzESOX1SwhD1XnYex4z+KicHvUdYrMGQqsreH1hHM4NfqqX0VCSy6v4DodHJ+wlUri9fmLxPQJuN35PEL/oIbARordBijBQmcuaVo5Beo2Q8QmB7gZ66ytxOkTiL7exTxmJhKP4PGFSBym2AiHWgxE8/k3iO0k2/Vu4rHbCvjA9daUISsmX/JDzBXPNd9mb/hnd7VyyznxIec5ZbuV8jkZWwOb6GusrFhaHuzEPd+E0z2EzTeMVRXZCbkLiAvHgCkPqMoT2m1/RWPgNrp4G1Plfk/HR+3z2wXuUXPwEed5ZlJLz6KVZaIsuUJ39KcrvM1BLziG7lEFL8beMaWSMtN1DVyNBVZTJ/+tqXtqJpQ5WAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"cover\"\n        title=\"cover\"\n        src=\"/static/38bff92c4f54acc9e02aab58e8a3d196/a331c/2026-05-25-cover.png\"\n        srcset=\"/static/38bff92c4f54acc9e02aab58e8a3d196/36ca5/2026-05-25-cover.png 200w,\n/static/38bff92c4f54acc9e02aab58e8a3d196/a3397/2026-05-25-cover.png 400w,\n/static/38bff92c4f54acc9e02aab58e8a3d196/a331c/2026-05-25-cover.png 800w,\n/static/38bff92c4f54acc9e02aab58e8a3d196/8537d/2026-05-25-cover.png 1200w,\n/static/38bff92c4f54acc9e02aab58e8a3d196/7032b/2026-05-25-cover.png 1408w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p><strong>How soon is now? In PostgreSQL, it’s not always as soon as you’d think.</strong> I learned that the hard way recently, so you don’t have to.</p>\n<p>It took me hours and wasn’t easy to reproduce, even though the fix is one line. I found it in <a href=\"https://www.cybertec-postgresql.com/en/postgresql-now-vs-nowtimestamp-vs-clock_timestamp/\">a Cybertec post</a>, as I quite often do when I’m staring at something odd in PostgreSQL. I’m supposed to know my way around the database, but I missed it, which is another reason I want to write this down.</p>\n<p>I was working on distributed locking in <a href=\"https://github.com/event-driven-io/emmett\">Emmett</a>. When you scale a service horizontally, you can easily end up with two instances of the same message processor running at once. That’s bad. Both instances would pull the same events, both would write to the same projection storage, and we’d get duplicated side effects, overwritten state and broken checkpoints. So we need to guarantee that exactly one instance of each processor is active at any time. Emmett does that using two things working hand in glove: PostgreSQL advisory locks and a row in the <code class=\"language-text\">emt_processors</code> table. The row keeps the durable side of ownership: which instance currently holds the processor (<code class=\"language-text\">processor_instance_id</code>), when it last checked in (<code class=\"language-text\">last_updated</code>), and what state it’s in (<code class=\"language-text\">status</code>). I described the full design in <a href=\"/en/rebuilding_read_models_safely/\">Rebuilding Event-Driven Read Models in a safe and resilient way</a>, so I won’t bore you with the whole picture here.</p>\n<p>For this story, the part that matters is what happens when an instance crashes. The crashed processor’s connection is gone, so its advisory lock has already been released. A new instance can grab the advisory lock without resistance. But the row in <code class=\"language-text\">emt_processors</code> still says <code class=\"language-text\">status = 'running'</code> and still points to the previous owner, because the crash didn’t give anyone a chance to clean it up.</p>\n<p>From the outside, we can’t tell whether the previous owner has crashed or is just between heartbeats. So we wait. If the row’s <code class=\"language-text\">last_updated</code> is older than a configurable timeout, the new instance is allowed to claim ownership anyway. Anyone quiet for that long is treated as gone. To make this graceful, the lock acquisition runs inside a retry policy. A fresh instance starting just after a crash doesn’t fail straight away; it retries until the timeout window expires.</p>\n<h2 id=\"the-bug\" style=\"position:relative;\"><a href=\"#the-bug\" aria-label=\"the bug permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The bug</h2>\n<p>The takeover decision lives in the upsert against <code class=\"language-text\">emt_processors</code>. In the real function, that upsert sits inside a Common Table Expression (CTE) alongside a <code class=\"language-text\">pg_try_advisory_xact_lock</code> call.</p>\n<p>For the record: the snippets below skip that wrapping (and trim a couple of unused parameters) to keep the focus on the upsert, where the bug lives. The full version is in <a href=\"https://github.com/event-driven-io/emmett/blob/4c5909982313654f7df383a44b02a14a04f30b50/src/packages/emmett-postgresql/src/eventStore/schema/processors/processorsLocks.ts\">the source</a>.</p>\n<div class=\"gatsby-highlight\" data-language=\"sql\"><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">CREATE</span> <span class=\"token operator\">OR</span> <span class=\"token keyword\">REPLACE</span> <span class=\"token keyword\">FUNCTION</span> emt_try_acquire_processor_lock<span class=\"token punctuation\">(</span>\n    p_processor_id           <span class=\"token keyword\">TEXT</span><span class=\"token punctuation\">,</span>\n    p_processor_instance_id  <span class=\"token keyword\">TEXT</span><span class=\"token punctuation\">,</span>\n    p_lock_timeout_seconds   <span class=\"token keyword\">INT</span>\n<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">RETURNS</span> <span class=\"token keyword\">BOOLEAN</span>\n<span class=\"token keyword\">LANGUAGE</span> plpgsql\n<span class=\"token keyword\">AS</span> $$\n<span class=\"token keyword\">BEGIN</span>\n  <span class=\"token keyword\">INSERT</span> <span class=\"token keyword\">INTO</span> emt_processors <span class=\"token punctuation\">(</span>processor_id<span class=\"token punctuation\">,</span> processor_instance_id<span class=\"token punctuation\">,</span> <span class=\"token keyword\">status</span><span class=\"token punctuation\">,</span> last_updated<span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">VALUES</span> <span class=\"token punctuation\">(</span>p_processor_id<span class=\"token punctuation\">,</span> p_processor_instance_id<span class=\"token punctuation\">,</span> <span class=\"token string\">'running'</span><span class=\"token punctuation\">,</span> <span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">ON</span> CONFLICT <span class=\"token punctuation\">(</span>processor_id<span class=\"token punctuation\">)</span> <span class=\"token keyword\">DO</span> <span class=\"token keyword\">UPDATE</span>\n  <span class=\"token keyword\">SET</span> processor_instance_id <span class=\"token operator\">=</span> p_processor_instance_id<span class=\"token punctuation\">,</span>\n      <span class=\"token keyword\">status</span>                <span class=\"token operator\">=</span> <span class=\"token string\">'running'</span><span class=\"token punctuation\">,</span>\n      last_updated          <span class=\"token operator\">=</span> <span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">WHERE</span>   \n     <span class=\"token comment\">-- same instance reconnecting</span>\n     emt_processors<span class=\"token punctuation\">.</span>processor_instance_id <span class=\"token operator\">=</span> p_processor_instance_id                      \n     <span class=\"token comment\">-- previous owner stopped cleanly</span>\n     <span class=\"token operator\">OR</span> emt_processors<span class=\"token punctuation\">.</span><span class=\"token keyword\">status</span> <span class=\"token operator\">=</span> <span class=\"token string\">'stopped'</span>     \n     <span class=\"token comment\">-- previous owner timed out    </span>\n     <span class=\"token operator\">OR</span> emt_processors<span class=\"token punctuation\">.</span>last_updated\n        <span class=\"token operator\">&lt;</span> <span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span> <span class=\"token punctuation\">(</span>p_lock_timeout_seconds <span class=\"token operator\">||</span> <span class=\"token string\">' seconds'</span><span class=\"token punctuation\">)</span>::<span class=\"token keyword\">interval</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">RETURN</span> FOUND<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">END</span><span class=\"token punctuation\">;</span>\n$$<span class=\"token punctuation\">;</span></code></pre></div>\n<p>The last branch is the takeover. It reads naturally: if the previous owner hasn’t checked in for longer than the timeout, the new instance can replace them. All tests were green. Stop me if you think you’ve heard this one before. The problem surfaced through user feedback (thanks, <a href=\"https://www.linkedin.com/in/martindilger/\">Martin</a>!), and it took me a long time to reproduce; none of the existing tests covered the scenario that triggered it. <a href=\"https://github.com/event-driven-io/emmett/pull/339/changes#diff-c790c5d796e8e155d3e621f9b5bc2843503eda7c121c7715c232dbe9608a8964R741\">Once I had a new end-to-end test that pinpointed the symptom</a>, the rest was the usual, long, boring, debugging loop.</p>\n<p>To see why, open <code class=\"language-text\">psql</code> and run this:</p>\n<div class=\"gatsby-highlight\" data-language=\"sql\"><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">BEGIN</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">SELECT</span> <span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">AS</span> tx_now<span class=\"token punctuation\">,</span> clock_timestamp<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">AS</span> wall_clock<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">SELECT</span> pg_sleep<span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">SELECT</span> <span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">AS</span> tx_now<span class=\"token punctuation\">,</span> clock_timestamp<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">AS</span> wall_clock<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">COMMIT</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>You’ll get something like:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">            tx_now             |          wall_clock\n-------------------------------+-------------------------------\n 2026-05-25 10:00:00.123456+00 | 2026-05-25 10:00:00.124012+00\n\n            tx_now             |          wall_clock\n-------------------------------+-------------------------------\n 2026-05-25 10:00:00.123456+00 | 2026-05-25 10:00:02.131845+00</code></pre></div>\n<p>The first column is the same in both rows. The second one is two seconds apart. As it turns out, <code class=\"language-text\">now()</code> is a synonym for <code class=\"language-text\">transaction_timestamp()</code>: it returns the time the transaction began, and keeps returning that value for every statement inside the same transaction. A light that never goes out, in other words. <code class=\"language-text\">clock_timestamp()</code> reads the wall clock each time it’s called, so it advances as time does. Cybertec wrote <a href=\"https://www.cybertec-postgresql.com/en/postgresql-now-vs-nowtimestamp-vs-clock_timestamp/\">a good walkthrough</a> of the whole family of timestamp functions if you want the full picture.</p>\n<p>What difference does it make? For a column like <code class=\"language-text\">last_updated</code>, the constancy of <code class=\"language-text\">now()</code> is usually what you want: every row touched in the same transaction shares a single timestamp, which keeps audit logs and write batches coherent. For asking “has enough time passed?” inside the same transaction, the same constancy works against us.</p>\n<p>Now back to the retry. The consumer that calls <code class=\"language-text\">tryAcquire</code> looks roughly like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"ts\"><pre class=\"language-ts\"><code class=\"language-ts\">pool<span class=\"token punctuation\">.</span><span class=\"token function\">withTransaction</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>tx<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n  <span class=\"token function\">asyncRetry</span><span class=\"token punctuation\">(</span>\n    <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token function\">tryAcquireProcessorLock</span><span class=\"token punctuation\">(</span>tx<span class=\"token punctuation\">.</span>execute<span class=\"token punctuation\">,</span> options<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">{</span> retries<span class=\"token operator\">:</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span> minTimeout<span class=\"token operator\">:</span> <span class=\"token number\">200</span><span class=\"token punctuation\">,</span> maxTimeout<span class=\"token operator\">:</span> <span class=\"token number\">1000</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p><code class=\"language-text\">pool.withTransaction</code> opens a database transaction and passes its executor to the body. <code class=\"language-text\">asyncRetry</code> then repeatedly calls the stored procedure on that same executor, with a backoff between attempts. So even though the retries are spread out in real time, every call runs inside the same database transaction:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">withTransaction        (transaction starts at T)\n  └── asyncRetry\n       ├── call lock function    → now() = T\n       ├── call lock function    → now() = T   (200 ms later)\n       ├── call lock function    → now() = T   (400 ms later)\n       └── ...</code></pre></div>\n<p><code class=\"language-text\">last_updated &lt; now() - timeout</code> evaluates the same way every iteration. The predicate is effectively constant for the lifetime of that transaction. From the database’s perspective, no time was passing between attempts, even though the retries were spread across real seconds. (of course, the valid question is whether retries should happen inside a transaction, but let’s say that this is out of scope of today’s article, deal?).</p>\n<p>So what was the fix? Change the time source inside the function. PL/pgSQL lets you declare local variables, so I added one at the top, initialised from <code class=\"language-text\">clock_timestamp()</code>, and used it everywhere the function previously called <code class=\"language-text\">now()</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"sql\"><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">CREATE</span> <span class=\"token operator\">OR</span> <span class=\"token keyword\">REPLACE</span> <span class=\"token keyword\">FUNCTION</span> emt_try_acquire_processor_lock<span class=\"token punctuation\">(</span>\n    p_processor_id           <span class=\"token keyword\">TEXT</span><span class=\"token punctuation\">,</span>\n    p_processor_instance_id  <span class=\"token keyword\">TEXT</span><span class=\"token punctuation\">,</span>\n    p_lock_timeout_seconds   <span class=\"token keyword\">INT</span>\n<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">RETURNS</span> <span class=\"token keyword\">BOOLEAN</span>\n<span class=\"token keyword\">LANGUAGE</span> plpgsql\n<span class=\"token keyword\">AS</span> $$\n<span class=\"token keyword\">DECLARE</span>\n  v_current_time TIMESTAMPTZ :<span class=\"token operator\">=</span> clock_timestamp<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">BEGIN</span>\n  <span class=\"token keyword\">INSERT</span> <span class=\"token keyword\">INTO</span> emt_processors <span class=\"token punctuation\">(</span>processor_id<span class=\"token punctuation\">,</span> processor_instance_id<span class=\"token punctuation\">,</span> <span class=\"token keyword\">status</span><span class=\"token punctuation\">,</span> last_updated<span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">VALUES</span> <span class=\"token punctuation\">(</span>p_processor_id<span class=\"token punctuation\">,</span> p_processor_instance_id<span class=\"token punctuation\">,</span> <span class=\"token string\">'running'</span><span class=\"token punctuation\">,</span> v_current_time<span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">ON</span> CONFLICT <span class=\"token punctuation\">(</span>processor_id<span class=\"token punctuation\">)</span> <span class=\"token keyword\">DO</span> <span class=\"token keyword\">UPDATE</span>\n  <span class=\"token keyword\">SET</span> processor_instance_id <span class=\"token operator\">=</span> p_processor_instance_id<span class=\"token punctuation\">,</span>\n      <span class=\"token keyword\">status</span>                <span class=\"token operator\">=</span> <span class=\"token string\">'running'</span><span class=\"token punctuation\">,</span>\n      last_updated          <span class=\"token operator\">=</span> v_current_time\n  <span class=\"token keyword\">WHERE</span> emt_processors<span class=\"token punctuation\">.</span>processor_instance_id <span class=\"token operator\">=</span> p_processor_instance_id\n     <span class=\"token operator\">OR</span> emt_processors<span class=\"token punctuation\">.</span><span class=\"token keyword\">status</span> <span class=\"token operator\">=</span> <span class=\"token string\">'stopped'</span>\n     <span class=\"token operator\">OR</span> emt_processors<span class=\"token punctuation\">.</span>last_updated\n        <span class=\"token operator\">&lt;</span> v_current_time <span class=\"token operator\">-</span> <span class=\"token punctuation\">(</span>p_lock_timeout_seconds <span class=\"token operator\">||</span> <span class=\"token string\">' seconds'</span><span class=\"token punctuation\">)</span>::<span class=\"token keyword\">interval</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">RETURN</span> FOUND<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">END</span><span class=\"token punctuation\">;</span>\n$$<span class=\"token punctuation\">;</span></code></pre></div>\n<p><code class=\"language-text\">clock_timestamp()</code> ignores transaction boundaries. Every call to the stored procedure reads the wall clock fresh, so each retry sees a slightly later value than the last. After enough retries inside the wrapping transaction, the takeover predicate flips, and the new instance wins.</p>\n<p>The function uses the timestamp in two places: when setting <code class=\"language-text\">last_updated</code> on the new owner, and when comparing the previous owner’s <code class=\"language-text\">last_updated</code> to the timeout. I switched both to <code class=\"language-text\">v_current_time</code>, so the write and the check read from the same wall clock. Mixing <code class=\"language-text\">clock_timestamp()</code> on one side and <code class=\"language-text\">now()</code> on the other would leave a subtler version of the same bug.</p>\n<p>A cleaner option for the future is to move the retry one layer up, so each attempt opens its own transaction. That would remove the trap entirely and let the function go back to plain <code class=\"language-text\">now()</code>. For now, the local variable does the job.</p>\n<h2 id=\"why-my-tests-didnt-catch-it\" style=\"position:relative;\"><a href=\"#why-my-tests-didnt-catch-it\" aria-label=\"why my tests didnt catch it permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Why my tests didn’t catch it</h2>\n<p>Now to the testing side. I had a careful suite of integration tests for the stored procedure. Two instances racing for the lock. The same instance reconnects after a crash. Takeover after a custom timeout. They all passed, and they could not have caught this bug. Here’s the shape of a typical one:</p>\n<div class=\"gatsby-highlight\" data-language=\"ts\"><pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">await</span> pool<span class=\"token punctuation\">.</span><span class=\"token function\">withTransaction</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>connection<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n  lock<span class=\"token punctuation\">.</span><span class=\"token function\">tryAcquire</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> execute<span class=\"token operator\">:</span> connection<span class=\"token punctuation\">.</span>execute <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>That’s the shape: set up some state, call <code class=\"language-text\">tryAcquire</code> once, check the result. A single call to the stored procedure works fine with <code class=\"language-text\">now()</code> in the WHERE clause. There is only one timestamp involved per call, and the predicate evaluates correctly against it. The bug only shows up when several calls share one transaction, which happens when the consumer’s <code class=\"language-text\">withTransaction</code> wraps a retry policy. The stored-procedure tests never put those two together.</p>\n<p>The end-to-end consumer tests do go through the consumer’s <code class=\"language-text\">withTransaction</code> wrapper, but they covered the happy paths: clean start, clean stop, two consumers competing, and an instance reclaiming its own stale lock. None of them combined the three conditions that together expose the bug:</p>\n<ol>\n<li>a previous owner whose row still says <code class=\"language-text\">status = 'running'</code> (a crash, not a graceful stop),</li>\n<li>a new instance arriving with a different instance ID,</li>\n<li>a retry acquisition policy with a timeout short enough for the retries to outlast it inside the test’s deadline.</li>\n</ol>\n<p>Any one of those missing and the takeover predicate either succeeded on the first attempt (so the retry never fired), or the test finished before the retry’s failure to make progress was visible.</p>\n<p>So why did this slip through both layers? The stored-procedure tests never combined a retry policy with the stale-row state, so they never produced multiple calls inside one transaction. The end-to-end tests did exercise the retry path, but none of them happened to combine all three conditions above at once. Both layers had blind spots, and the bug lived exactly where they overlapped.</p>\n<p>The Pull Request that fixes the bug also adds a new family of tests that mount <code class=\"language-text\">tryAcquire</code> under the same transactional wrapper the real consumer uses, with the crash + new-instance + retry combination wired up on purpose. That’s the kind of test I should have had from the start.</p>\n<h2 id=\"what-im-taking-away\" style=\"position:relative;\"><a href=\"#what-im-taking-away\" aria-label=\"what im taking away permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>What I’m taking away</h2>\n<p>Two things about <code class=\"language-text\">now()</code>:</p>\n<ul>\n<li><strong><code class=\"language-text\">now()</code> is the right tool when you want every row touched in the same transaction to share a timestamp.</strong> <code class=\"language-text\">created_at</code>, <code class=\"language-text\">last_updated</code>, audit columns. That stability is a feature.</li>\n<li><strong><code class=\"language-text\">now()</code> is the wrong tool when you want to ask “has time moved on?” from inside a transaction.</strong> Use <code class=\"language-text\">clock_timestamp()</code> when you genuinely mean the wall clock.</li>\n</ul>\n<p>And one harder thing about tests. Inner tests for stored procedures give me a tight feedback loop and pinpoint failures, but only inside the scaffolding I build for them. End-to-end tests run the real wiring, but I can’t enumerate every combination of timeouts, instance IDs and crash states without the suite collapsing under its own weight. The combination where this bug lived wasn’t reachable from either side by accident; it needed a deliberate setup.</p>\n<p>I don’t have a clean rule for where to draw that line, and honestly, I don’t think there is one. My takeaway is to look at the seam: the spot where the inner test invokes the code differently from how the production caller invokes it. Here, it was a single-call test against a retry loop sharing one transaction. Wherever that gap sits, write a test there. Not at the unit level, not at the full end-to-end level, but in a setup that mirrors how the real caller actually drives the code and exercises the path you care about.</p>\n<h2 id=\"tldr\" style=\"position:relative;\"><a href=\"#tldr\" aria-label=\"tldr permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>TLDR</h2>\n<p><code class=\"language-text\">now()</code> returns the start of the current transaction, not the current moment. Inside a transaction it doesn’t change between statements. If you wrap a retry loop around a function that uses <code class=\"language-text\">now()</code> in a <code class=\"language-text\">WHERE</code> clause, and the retry loop runs inside one transaction, the predicate is frozen and the retries do nothing. Use <code class=\"language-text\">clock_timestamp()</code> when you mean “right now”. And pay extra attention to the seam between inner and end-to-end tests, because that’s where mismatches between how tests drive the code and how production drives it tend to hide.</p>\n<p>The fix and the new tests live in <a href=\"https://github.com/event-driven-io/emmett/pull/339\">Emmett Pull Request #339</a>.</p>\n<p>Uff. That bug was nasty.</p>\n<p><img src=\"/800452215e74cd33b63bafd5c96e24d1/2026-05-25-nasty.gif\" alt=\"\"></p>\n<p>Read also:</p>\n<ul>\n<li><a href=\"/en/rebuilding_read_models_safely/\">Rebuilding Event-Driven Read Models in a safe and resilient way</a>, with the locking design this bug lives inside,</li>\n<li><a href=\"https://www.architecture-weekly.com/p/distributed-locking-a-practical-guide\">Distributed Locking: A Practical Guide</a>,</li>\n<li><a href=\"/en/consumers_processors_in_emmett/\">Consumers, projectors, reactors and all that messaging jazz in Emmett</a>,</li>\n<li><a href=\"/en/checkpointing_message_processing/\">Checkpointing the message processing</a>,</li>\n<li><a href=\"https://www.cybertec-postgresql.com/en/postgresql-now-vs-nowtimestamp-vs-clock_timestamp/\">Cybertec: PostgreSQL <code class=\"language-text\">now()</code> vs <code class=\"language-text\">now()::timestamp</code> vs <code class=\"language-text\">clock_timestamp()</code></a>,</li>\n<li><a href=\"/en/is_keeping_utc_dates_best_solution/\">Is keeping dates in UTC really the best solution?</a>.</li>\n</ul>\n<p>Or my other articles about PostgreSQL:</p>\n<ul>\n<li><a href=\"/en/postgres_superpowers/\">Postgres Superpowers in Practice</a>,</li>\n<li><a href=\"https://www.architecture-weekly.com/p/postgresql-partitioning-logical-replication\">PostgreSQL partitioning, logical replication and other Q&#x26;A</a></li>\n<li><a href=\"https://www.architecture-weekly.com/p/postgresql-jsonb-powerful-storage\">PostgreSQL JSONB - Powerful Storage for Semi-Structured Data</a></li>\n<li><a href=\"/en/push_based_outbox_pattern_with_postgres_logical_replication/\">Push-based Outbox Pattern with Postgres Logical Replication</a></li>\n<li><a href=\"/en/how_to_get_all_messages_through_postgres_logical_replication/\">How to get all messages through Postgres logical replication</a></li>\n<li><a href=\"https://www.architecture-weekly.com/p/the-write-ahead-log-a-foundation\">The Write-Ahead Log: The underrated Reliability Foundation for Databases and Distributed systems</a></li>\n<li><a href=\"/en/ordering_in_postgres_outbox/\">How Postgres sequences issues can impact your messaging guarantees</a></li>\n</ul>\n<p>Cheers!</p>\n<p>Oskar</p>\n<p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to <a href=\"https://www.icrc.org/en/donate/ukraine\">Red Cross</a>, <a href=\"https://savelife.in.ua/en/donate/\">Ukraine humanitarian organisation</a> or <a href=\"https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone\">donate Ambulances for Ukraine</a>.</p>","excerpt":"How soon is now? In PostgreSQL, it’s not always as soon as you’d think. I learned that the hard way recently, so you don’t have to. It took…","fields":{"slug":"/how-soon-is-now-in-postgresql/","prefix":"2026-05-25","langKey":"en"},"frontmatter":{"title":"How soon is now in PostgreSQL?","author":"oskar dudycz","category":"PostgreSQL","disqusId":null,"useDefaultLangCanonical":null,"cover":{"childImageSharp":{"resize":{"src":"/static/38bff92c4f54acc9e02aab58e8a3d196/2a4de/2026-05-25-cover.png"}}}}},"authornote":{"id":"295c4649-a20b-5147-9ca5-5cb77fbcfe66","html":"<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f748655e118b2b9d5ce6b7dd6f9f4f85/d2429/2021-10-13-cover.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6cAAAOnAEHlFPdAAAChUlEQVQozyXOTU/acBwA4J+FFmgp/5RCX2AlFVgUgxTCYZNA6KZMooPSiAoaZ8bLjTmEbALRePAygyaLBEl0lyWePGji1YsnT8YDBxMTE+9+iiXu+QQPPDw8nJ2dtVqtZrNZr9cbjUYul8tkMvl8vt1un5yc3Nzc3N7eDofDu7u74+Pj+/v7x8fH6+vr8/NzGA6HOzs73W53f3+/0+mUSqVisbi3t3d0dNTv9w8PDy8uLnq93sHBQblcrtfrp6enl5eXrVarUqnA8/OzruvLy8uDwUDTtJmZmWw22+12Nzc3q9VqKpUqFovT09OFQsHr9SaTyZWVlVgspmlaoVCAl5cXRVE4jotGowghiqJIkpybm/N4PAsLCwghRVF0Xff5fHa7HQDi8Xg6nQaASCQCT09PwWAQXrEsa7VaZVlOp9NjY2Nut1uWZYIgWJYVRZEkSYZhLBaL0WgkCCIUCkGv1xMEAcMwo9EIABRFEQQhSVI0GhUEwWAw0DQ9OjqK4zhFUQzD2O12HMc5jhMEAa6urmRZRgglEgmHwzEyMuJyuXZ3d2u12tTUFAAQBOF0Onmep2mae2V6heM4bG1tIYR4nk8kEqIoAkAgEOh0OvF4XJIkADCZTDzPu1wuk8lks9kQQhiGmc1mkiTB65U5zun3+xFCPp+P5znaRquqGg6HcdyIYRiO4+VyeWlp6f+CpmmDwWAxm0VRAFX9kM3mVlfXJidDPv/bN24pHI4kkyrDMCRJjo8HJMnz6dNsq9W2WmmzhWRZB2W1BSaCmqbDxrfK9naj/r2az2fWvxTfvY98La2pakxRJuY/zy4u5jLZtPox/uPnxvp6YTalLurz7WZt8PvX3z/9f7APp/oT02NdAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"cover\"\n        title=\"cover\"\n        src=\"/static/f748655e118b2b9d5ce6b7dd6f9f4f85/a331c/2021-10-13-cover.png\"\n        srcset=\"/static/f748655e118b2b9d5ce6b7dd6f9f4f85/36ca5/2021-10-13-cover.png 200w,\n/static/f748655e118b2b9d5ce6b7dd6f9f4f85/a3397/2021-10-13-cover.png 400w,\n/static/f748655e118b2b9d5ce6b7dd6f9f4f85/a331c/2021-10-13-cover.png 800w,\n/static/f748655e118b2b9d5ce6b7dd6f9f4f85/d2429/2021-10-13-cover.png 805w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p>Through my window, I see the result of good plans but poor execution. Opposite my flat, there is a partially completed construction place. Buildings were supposed to be eye-catching Mediterranean style apartments.  Delivery date? Two years ago. Actual? More and more unknown.</p>\n<p>Some time ago, I heard that using Event Sourcing makes creating Event-Driven Architecture easier. The arguments were correct, that if we’re already publishing events to trigger business workflows, then at some point, we may want to also store events to not lose information. Agreed. However, I also heard that keeping the state as events will simplify things. We’ll have a source of truth with a record of the system behaviour. This will allow, e.g. to confront the results of the operations with the recorded state. I’d agree with that, with one distinction. It’s easier as long as you already know Event Sourcing.</p>\n<p>Many people in the DDD community claim that the essential is to properly break down the system into autonomous parts called bounded contexts. Once we have it, the rest is secondary and will sort itself out. For sure.</p>\n<p>Many seasoned programmers speak similarly about new technologies. They claim that they can translate past experience into new technologies. That’s true that by analogy, they can catch the big picture quicker. But isn’t it a bold assumption to say that Win.Forms specialist will learn Angular quickly?</p>\n<p>The end result may differ a lot from the initial ideas. I saw the plan of those buildings next to me. Now I can see the effects of the execution. Or actually, the lack.</p>\n<p>I believe that we should carefully acknowledge not only the point of view of our authorities but also their seating point. If we want to find out how to form a wall, do we ask an architect or a foreman? An architect may know the theory, but the practice is what we’re looking for. On the other hand, if you want to know where to put the wall, you prefer the architect to do measurements. At least if you don’t want to have the roof falling to your head.</p>\n<p>After I had torn a ligament in my knee, I went to two qualified orthopedists. One said I should have surgery and do a reconstruction. The second stated that there is no need for that; rehabilitation should be enough. Guess which one had a specialization in surgery and which in rehabilitation?</p>\n<p>People usually give us advice from the point where they’re currently standing. They are entitled to a biased view. An architect who rarely does programming will tend to downplay the value of implementation and tactical patterns. Midlevel developers will focus on technicalities instead of the global system impact. The team manager or consultant will emphasize the importance of soft skills (or esoteric techniques known only to them).</p>\n<p>The truth is that we need all of them. The excellent plan will fall on the bad execution. The best execution for the wrong case will be just a waste of time. We should carefully evaluate the advice considering what we need and what an expert can give us.</p>\n<p>Therefore, when we’re reading an article, watching a talk, let’s also pay attention to the place where the person is standing. The perspective from there may be much different from where we are right now. That can be good, as it may push us in the right direction. But it may also be misleading, as we accidentally take biases of this person without understanding the tradeoffs. Personally, I prefer to follow not only people from pedestal but also those that are closer to my position. A bit further in the journey, but not too far. That helps me to calibrate my view as those people are more relative to my daily struggles.</p>\n<p>Polish historical leader <a href=\"https://en.wikipedia.org/wiki/J%C3%B3zef_Pi%C5%82sudski\">Józef Piłsudzki</a> reportedly used to say: <em>“Right is like an ass, everyone has its own”</em>.</p>\n<p>Cheers!</p>\n<p>Oskar</p>"},"site":{"siteMetadata":{"facebook":{"appId":""}}}},"pageContext":{"slug":"/how-soon-is-now-in-postgresql/","lang":"en","langKey":"en","prev":{"id":"e95d157a-18c2-5bd5-938d-4561ffc9f983","fields":{"slug":"/on-mashing-up-modelling-techniques/","prefix":"2026-05-18","source":"posts","langKey":"en"},"frontmatter":{"title":"On mashing up modelling techniques for fun and profit","category":"Software Architecture"}},"source":"posts"}},
    "staticQueryHashes": ["2742854296"]}