<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Gabriel Ludwig Rivera</title>
    <description>The latest articles on Forem by Gabriel Ludwig Rivera (@defnotwig).</description>
    <link>https://forem.com/defnotwig</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3801068%2Fe4e09112-83bb-4d09-aa8b-41f847f9c063.jpeg</url>
      <title>Forem: Gabriel Ludwig Rivera</title>
      <link>https://forem.com/defnotwig</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/defnotwig"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Gabriel Ludwig Rivera</dc:creator>
      <pubDate>Mon, 02 Mar 2026 07:55:30 +0000</pubDate>
      <link>https://forem.com/defnotwig/-2f3a</link>
      <guid>https://forem.com/defnotwig/-2f3a</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/defnotwig" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3801068%2Fe4e09112-83bb-4d09-aa8b-41f847f9c063.jpeg" alt="defnotwig"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/defnotwig/pg-safe-migrate-stop-shipping-unsafe-postgres-migrations-1m5n" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;pg-safe-migrate — Stop Shipping Unsafe Postgres Migrations&lt;/h2&gt;
      &lt;h3&gt;Gabriel Ludwig Rivera ・ Mar 2&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#postgres&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#node&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#typescript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#database&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>postgres</category>
      <category>node</category>
      <category>typescript</category>
      <category>database</category>
    </item>
    <item>
      <title>pg-safe-migrate — Stop Shipping Unsafe Postgres Migrations</title>
      <dc:creator>Gabriel Ludwig Rivera</dc:creator>
      <pubDate>Mon, 02 Mar 2026 07:55:05 +0000</pubDate>
      <link>https://forem.com/defnotwig/pg-safe-migrate-stop-shipping-unsafe-postgres-migrations-1m5n</link>
      <guid>https://forem.com/defnotwig/pg-safe-migrate-stop-shipping-unsafe-postgres-migrations-1m5n</guid>
      <description>&lt;p&gt;&lt;em&gt;A safety-first PostgreSQL migration engine for Node.js that catches lock-heavy DDL, deploy races, and schema drift before they hit production.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’ve seen the same three Postgres migration failures repeat across teams, stacks, and companies. Not because people are careless—because migrations are deceptively easy to write and painfully easy to ship in a way that hurts production.&lt;/p&gt;

&lt;p&gt;First: the lock incident. Someone creates an index the “normal” way, Postgres takes a heavy lock, traffic queues, and suddenly everything feels like it’s on fire. Second: the deploy race. Two pipelines trigger, both attempt migrations, and you end up with a failed deploy and a questionable migration state. Third: the drift bug. A migration file gets edited after it already ran, the database no longer matches what the repo “says” it is, and you discover the mismatch weeks later in the worst possible way.&lt;/p&gt;

&lt;p&gt;Here’s the project: &lt;a href="https://github.com/defnotwig/pg-safe-migrate" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does (in plain terms)&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lints migrations with safety rules so risky SQL is flagged early
&lt;/li&gt;
&lt;li&gt;Uses Postgres advisory locks so only one migrator runs at a time
&lt;/li&gt;
&lt;li&gt;Stores SHA-256 checksums so edited migrations are detected as drift
&lt;/li&gt;
&lt;li&gt;Handles transaction policy realistically (some statements can’t run in a transaction)
&lt;/li&gt;
&lt;li&gt;Allows overrides, but makes them explicit and auditable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Safety linting (10 rules)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Before migrations run, pg-safe-migrate checks your SQL and fails fast on patterns that commonly cause outages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx pg-safe-migrate lint

PGSM003 &lt;span class="o"&gt;[&lt;/span&gt;ERROR] Index creation should be CONCURRENTLY to avoid blocking writes
  → migrations/20260302_120000_add_users_email_index.sql:3
  Fix: CREATE INDEX CONCURRENTLY IF NOT EXISTS ... 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point isn’t to be “strict for fun”—it’s to make the safe choice the default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy races: solved with advisory locks&lt;/strong&gt;&lt;br&gt;
Migrations acquire a Postgres advisory lock first. If another runner tries to migrate at the same time, it waits or fails (depending on config). The outcome is simple: no more two CI jobs stepping on each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema drift: solved with checksums&lt;/strong&gt;&lt;br&gt;
Every applied migration is stored with a SHA-256 checksum in the migration history table. If the file changes after it ran, pg-safe-migrate will catch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx pg-safe-migrate check

✗ Drift detected in 20260301_094512_add_posts.sql
  Applied: a1b2c3...
  Current: d4e5f6...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns a “mystery bug weeks later” into a CI failure you can address immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction policy that matches Postgres reality&lt;/strong&gt;&lt;br&gt;
Some operations (like CREATE INDEX CONCURRENTLY) can’t run inside a transaction. pg-safe-migrate supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction=auto (default): detects and runs non-transactional migrations safely

transaction=always: strict mode (fails if a migration includes non-transactional statements)

transaction=never: runs everything without wrapping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Overrides are allowed, but must be auditable&lt;/strong&gt;&lt;br&gt;
Sometimes you really do need to do a risky operation. Overrides work, but they require a reason:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- pgsm:allow PGSM001 reason="Removing deprecated table after 90 days" ticket="CHG-1042"
DROP TABLE IF EXISTS feature_flags;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way, the “risky thing” is intentional, reviewable, and documented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick start&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D pg-safe-migrate
npx pg-safe-migrate init
npx pg-safe-migrate create add-users-table
npx pg-safe-migrate lint
npx pg-safe-migrate up
npx pg-safe-migrate status
npx pg-safe-migrate check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CI gate (GitHub Action)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uses: defnotwig/pg-safe-migrate@v1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;with:&lt;br&gt;
    command: check&lt;br&gt;
    database_url: ${{ secrets.DATABASE_URL }}&lt;br&gt;
    dir: migrations&lt;/p&gt;

&lt;p&gt;If you’re interested, the docs and examples are in the repo, and PRs/issues are welcome:&lt;/p&gt;

&lt;p&gt;npm: &lt;a href="https://www.npmjs.com/package/pg-safe-migrate" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/pg-safe-migrate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;contributing: &lt;a href="https://github.com/defnotwig/pg-safe-migrate/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;https://github.com/defnotwig/pg-safe-migrate/blob/main/CONTRIBUTING.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What’s the most painful migration incident you’ve seen—and what guardrail would have prevented it?&lt;/p&gt;

&lt;p&gt;That’s what motivated me to build &lt;strong&gt;pg-safe-migrate&lt;/strong&gt;: a safety-first migration engine for Node.js that tries to catch these problems &lt;em&gt;before&lt;/em&gt; they reach production.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>node</category>
      <category>typescript</category>
      <category>database</category>
    </item>
  </channel>
</rss>
