<?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>DEV Community: Dipankar Sethi</title>
    <description>The latest articles on DEV Community by Dipankar Sethi (@dipankar_sethi_35cc168b29).</description>
    <link>https://dev.to/dipankar_sethi_35cc168b29</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%2F3719723%2Fcc261321-5b72-49c5-a8f6-6656b59bc3e4.jpg</url>
      <title>DEV Community: Dipankar Sethi</title>
      <link>https://dev.to/dipankar_sethi_35cc168b29</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dipankar_sethi_35cc168b29"/>
    <language>en</language>
    <item>
      <title>👻 The Silent Ghost: How a Single Enum Broke Our Distributed Transaction</title>
      <dc:creator>Dipankar Sethi</dc:creator>
      <pubDate>Tue, 27 Jan 2026 14:50:21 +0000</pubDate>
      <link>https://dev.to/dipankar_sethi_35cc168b29/the-silent-ghost-how-a-single-enum-broke-our-distributed-transaction-2efo</link>
      <guid>https://dev.to/dipankar_sethi_35cc168b29/the-silent-ghost-how-a-single-enum-broke-our-distributed-transaction-2efo</guid>
      <description>&lt;p&gt;It was 3:00 AM on a Tuesday when PagerDuty decided my night was over.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User onboarding wasn’t down.&lt;/li&gt;
&lt;li&gt;No red dashboards.&lt;/li&gt;
&lt;li&gt;No obvious errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But something felt off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick query against production revealed the truth:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 Thousands of users existed in Auth, with no HR profiles.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No UI errors.&lt;/li&gt;
&lt;li&gt;No retries.&lt;/li&gt;
&lt;li&gt;No alarms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Just ghost users&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is a real production postmortem from a system built using Spring Boot, PostgreSQL, and the Saga Pattern — and how a single enum value quietly broke our data integrity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture Overview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We use an orchestration-based Saga, owned by the HR service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a new employee joins:&lt;/li&gt;
&lt;li&gt;Create Auth credentials&lt;/li&gt;
&lt;li&gt;Create an HR profile
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Both must succeed — or neither should exist.

Original Saga Flow
[ Client ]
    |
[ HR Service (Saga Orchestrator) ]
    |
    |-- (1) Create User --------------&amp;gt; [ Auth Service ] -&amp;gt; [ Auth DB ]
    |
    |-- (2) Save Profile -------------&amp;gt; [ HR DB ❌ CHECK constraint ]
    |
    |-- (3) Delete User (Compensation) -&amp;gt; [ Auth Service ]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The logs claimed compensation succeeded.&lt;/li&gt;
&lt;li&gt;Production data disagreed.&lt;/li&gt;
&lt;li&gt;The Trigger: A Harmless Enum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;We added support for remote work:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;AddressType&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;CURRENT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;OFFICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;PERMANENT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;WORK&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;CI passed.&lt;/li&gt;
&lt;li&gt;Deploy succeeded.&lt;/li&gt;
&lt;li&gt;Production failed silently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real Culprit: Schema Drift
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The HR database had a PostgreSQL CHECK constraint:

CHECK (address_type IN ('HOME','CURRENT','OFFICE','PERMANENT'))

So when WORK was saved:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
ERROR: violates check constraint "employee_addresses_address_type_check"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;But the Saga still didn’t roll back.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Why?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Invisible Failure: JPA Lazy Writes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onboard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;authClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;               &lt;span class="c1"&gt;// SUCCESS&lt;/span&gt;
        &lt;span class="n"&gt;profileRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Deferred&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;authClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deleteUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;   &lt;span class="c1"&gt;// Never reached&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Commit happens here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key detail:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;save() doesn’t hit the DB&lt;/li&gt;
&lt;li&gt;SQL runs at commit&lt;/li&gt;
&lt;li&gt;Exception thrown after try-catch exits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth user created      ✅&lt;/li&gt;
&lt;li&gt;HR insert fails        ❌&lt;/li&gt;
&lt;li&gt;Compensation skipped  👻&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix #1: Fail Fast with flush()
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onboardEmployee&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OnboardRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;authClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;employeeRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProfile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Force SQL execution NOW&lt;/span&gt;
        &lt;span class="n"&gt;employeeRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;authClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deleteUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Now:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DB errors surface immediately&lt;/li&gt;
&lt;li&gt;Saga becomes aware of failure&lt;/li&gt;
&lt;li&gt;Compensation actually runs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix #2: Repair the Schema
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;employee_addresses&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;employee_addresses_address_type_check&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;employee_addresses&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;employee_addresses_address_type_check&lt;/span&gt;
&lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address_type&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'HOME'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'CURRENT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'OFFICE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'PERMANENT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'WORK'&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Real Fix: State-Based Saga
&lt;/h2&gt;

&lt;p&gt;Delete-based rollback assumes perfect networks.&lt;br&gt;
They don’t exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Improved Flow
[ Client ]
    |
[ HR Service ]
    |
    |-- Create User (PENDING) --------&amp;gt; [ Auth DB ]
    |
    |-- Save Profile (FLUSH) ---------&amp;gt; [ HR DB ]
    |
    |-- Activate User (ACTIVE) -------&amp;gt; [ Auth DB ]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No deletes.&lt;br&gt;
Only state transitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Safety Net: Reconciliation Job&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Scheduled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cron&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0 0 2 * * *"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;cleanupOrphans&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;authRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAllByStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PENDING&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;hrService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;profileExists&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;authRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This job quietly saved us more than once.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lessons Learned&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java enums ≠ DB constraints&lt;/li&gt;
&lt;li&gt;Never trust save() in a Saga&lt;/li&gt;
&lt;li&gt;Flush early, fail fast&lt;/li&gt;
&lt;li&gt;Delete-based rollback is fragile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reconciliation jobs save careers&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distributed systems don’t always crash.&lt;/li&gt;
&lt;li&gt;Sometimes they corrupt your data while telling you everything is fine.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>distributedsystems</category>
      <category>sql</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
