<?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: Deepanshu Kumar</title>
    <description>The latest articles on DEV Community by Deepanshu Kumar (@dev-deepanshu-kumar).</description>
    <link>https://dev.to/dev-deepanshu-kumar</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2071142%2Fb180a611-a0e2-4197-ad6b-59587b2291f8.png</url>
      <title>DEV Community: Deepanshu Kumar</title>
      <link>https://dev.to/dev-deepanshu-kumar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dev-deepanshu-kumar"/>
    <language>en</language>
    <item>
      <title>"Error Loading Dashboard" with Valid Session on First Visit: The Cookie Wasn't There Yet</title>
      <dc:creator>Deepanshu Kumar</dc:creator>
      <pubDate>Tue, 18 Aug 2026 13:36:05 +0000</pubDate>
      <link>https://dev.to/dev-deepanshu-kumar/error-loading-dashboard-with-valid-session-on-first-visit-the-cookie-wasnt-there-yet-1j3k</link>
      <guid>https://dev.to/dev-deepanshu-kumar/error-loading-dashboard-with-valid-session-on-first-visit-the-cookie-wasnt-there-yet-1j3k</guid>
      <description>&lt;p&gt;Twenty-one clients. Same error. Same week.&lt;/p&gt;

&lt;p&gt;The support tickets all said the same thing: blank dashboard, "Error loading Dashboard," reproducible in incognito. Main app working fine. Just the dashboard, just blank.&lt;/p&gt;

&lt;p&gt;That pattern — multiple unrelated clients, same symptom, same narrow window — usually means something happened around thirty days earlier. Not a code change. Not a deployment. Something that was set once and then expired.&lt;/p&gt;

&lt;p&gt;I ran an initial triage with Claude. What came back looked like a complete root cause analysis within a few minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The theory that fit perfectly
&lt;/h2&gt;

&lt;p&gt;When a user first opened the new dashboard, the system wrote two cookies. One was the main session token, refreshed on every request via a sliding window. The other was a secondary token specifically for the dashboard service, written once at first visit with a fixed thirty-day expiry.&lt;/p&gt;

&lt;p&gt;The sliding session kept active users logged in indefinitely. The dashboard token did not slide. It was written once and never touched again.&lt;/p&gt;

&lt;p&gt;After thirty days, the dashboard token expired. The main session was still valid. From the browser's perspective nothing had changed. But the dashboard service looked for its token, found nothing, and rejected every request.&lt;/p&gt;

&lt;p&gt;The multi-client timing confirmed it. All twenty-one affected clients had been enabled for the new dashboard around the same time, roughly thirty days earlier. Their initial tokens all expired in the same week. That explained why support saw a flood of identical tickets with no obvious trigger.&lt;/p&gt;

&lt;p&gt;There was one more detail that made the theory feel airtight. The controller action that renders the dashboard had a guard on the token-write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"dashboard_token"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;SetDashboardToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IsNullOrWhiteSpace&lt;/code&gt; means: write the token only if it's completely absent. If it's present but stale, the condition is false and the token is left alone. So the cookie existed in the browser, the check saw it and skipped the refresh, and the token sat there expiring with no mechanism to renew itself.&lt;/p&gt;

&lt;p&gt;I opened a branch. The fix: replace the presence check with a value comparison so the token gets refreshed whenever it no longer matches the live session. Affected users were told to log out and back in — that clears both tokens and resets everything on next visit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The new user
&lt;/h2&gt;

&lt;p&gt;A few weeks later another report came in. Different client. Same blank dashboard. Same error.&lt;/p&gt;

&lt;p&gt;This one was a user who had never visited the new dashboard before.&lt;/p&gt;

&lt;p&gt;That ruled out the thirty-day theory completely. A token that was never set cannot expire.&lt;/p&gt;

&lt;p&gt;I pulled up the browser state from the screenshot attached to the ticket. The main session token was present. The dashboard token was completely absent — not expired, not stale, never written. The dashboard service had received a request with no usable credentials and rejected it.&lt;/p&gt;

&lt;p&gt;So the question became: why wasn't the token written before the request went out?&lt;/p&gt;

&lt;p&gt;I ran the analysis again with Claude, this time with the browser screenshot as evidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  What was actually happening
&lt;/h2&gt;

&lt;p&gt;The dashboard is a Vue component hosted inside the main application. When the route loads, the component mounts and &lt;code&gt;onMounted&lt;/code&gt; fires. Inside &lt;code&gt;onMounted&lt;/code&gt;, it immediately calls the dashboard service to fetch user preferences and widget configuration.&lt;/p&gt;

&lt;p&gt;At the same time, the host application makes a server-side call that renders a partial view containing layout configuration. That server-side call also sets the dashboard token, writing it to the response as a &lt;code&gt;Set-Cookie&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;These two things happen concurrently. The &lt;code&gt;onMounted&lt;/code&gt; API call goes out immediately on mount. The server-side partial view returns with the &lt;code&gt;Set-Cookie&lt;/code&gt; header. The browser stores the token after parsing the response.&lt;/p&gt;

&lt;p&gt;The browser processes &lt;code&gt;Set-Cookie&lt;/code&gt; after the response arrives, not before the next outbound request. If the &lt;code&gt;onMounted&lt;/code&gt; call is already in flight when the partial view response comes back, that request carries no dashboard token. The dashboard service rejects it. The component renders blank.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route loads
 └─ Vue component mounts
      ├─ onMounted() fires
      │    └─ GET /api/dashboard/preferences  ← no dashboard_token yet
      │
      └─ Host requests partial view (concurrent)
           └─ Response: Set-Cookie: dashboard_token=...
                └─ Browser stores token  ← arrives after the API call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For new users, the timing was almost always wrong. The &lt;code&gt;onMounted&lt;/code&gt; call consistently outran the cookie write.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the first fix actually did
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;IsNullOrWhiteSpace&lt;/code&gt; fix wasn't wrong. It solved a real problem.&lt;/p&gt;

&lt;p&gt;A subset of users really did hit the thirty-day expiry path. Those users visited the dashboard on day one, got the token written correctly, used the app actively for a month, and then hit the dashboard again with a stale token. For them, the presence check was blocking the refresh. The fix helped them.&lt;/p&gt;

&lt;p&gt;It did nothing for anyone hitting the timing race on first visit. Those users never had the token at all.&lt;/p&gt;

&lt;p&gt;Two different failure modes, same symptom. The first hypothesis fit the evidence available at the time: the simultaneous multi-client reports, the thirty-day gap, the specific guard in the code. It just didn't account for the case where the token was never written in the first place.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part I can't account for
&lt;/h2&gt;

&lt;p&gt;The ticket was validated, the dashboard loads correctly now. Something fixed the timing race.&lt;/p&gt;

&lt;p&gt;What I'm less sure about is how many of the twenty-one clients were actually hitting the race versus the expiry. Most of them were given the log-out-and-back-in workaround before the code fix deployed. Logging out clears both tokens. Their next visit recreated everything fresh. By the time the fix was live, their sessions were clean.&lt;/p&gt;

&lt;p&gt;There's no clean way to split those two populations after the fact. The symptoms were identical and the workaround reset both failure modes simultaneously.&lt;/p&gt;

&lt;p&gt;Some of those twenty-one clients were probably hitting the thirty-day expiry path. Some were probably hitting the race. The first fix shipped confident it had the right diagnosis. It did — for part of the problem.&lt;/p&gt;




&lt;p&gt;Until next time,&lt;br&gt;
&lt;strong&gt;Deepanshu&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Backend engineer writing about production bugs, distributed systems, and engineering patterns learned the hard way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deepanshu-kumar.dev" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://medium.com/@dev-deepanshu-kumar" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/deepanshu-kumar-dev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://github.com/Dev-Deepanshu-Kumar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>csharp</category>
      <category>debugging</category>
      <category>vue</category>
    </item>
    <item>
      <title>Entity Framework N+1 in a Report Loop: The Pre-Fetch Fix Was Going to Crash Large Tenants</title>
      <dc:creator>Deepanshu Kumar</dc:creator>
      <pubDate>Mon, 03 Aug 2026 17:11:03 +0000</pubDate>
      <link>https://dev.to/dev-deepanshu-kumar/entity-framework-n1-in-a-report-loop-the-pre-fetch-fix-was-going-to-crash-large-tenants-pdm</link>
      <guid>https://dev.to/dev-deepanshu-kumar/entity-framework-n1-in-a-report-loop-the-pre-fetch-fix-was-going-to-crash-large-tenants-pdm</guid>
      <description>&lt;p&gt;A report that had been slow since the day it shipped. Not slow like "takes a few seconds." Slow like fifteen minutes. Support tickets going back years, all saying the same thing: the expenditure summary report just doesn't load. Users would click generate, walk away, come back, and it still wasn't done.&lt;/p&gt;

&lt;p&gt;I picked it up expecting a missing index or a bad query plan. What I found was a loop inside a loop, each iteration firing multiple database round-trips, each round-trip returning the entire tenant's data table so the application could filter it down to one row.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the code was doing
&lt;/h2&gt;

&lt;p&gt;The report listed every location in the tenant. For each location, it broke down work order costs by category: labor, parts, equipment. Simple enough conceptually. The implementation had an outer loop over locations, an inner loop over categories, and inside that inner loop, several data retrieval calls.&lt;/p&gt;

&lt;p&gt;Here's a genericized version of the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;workOrders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_workOrderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;workOrders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;labors&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_laborService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllLabors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// full table&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_partsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// full table&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;equipment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_equipmentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllEquipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// full table&lt;/span&gt;

        &lt;span class="c1"&gt;// filter down to this location + category&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;locationLabors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkOrderId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// ... same for parts, equipment&lt;/span&gt;
        &lt;span class="c1"&gt;// ... calculate totals&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data retrieval methods returned &lt;code&gt;IQueryable&amp;lt;T&amp;gt;&lt;/code&gt; backed by table-valued functions. Each call invoked the TVF and returned the full tenant table. The filtering happened afterward in memory.&lt;/p&gt;

&lt;p&gt;For a small tenant this was fine. Slow, but fine. For a tenant with hundreds of locations and several WO categories each, the math became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N work order queries (one per location)&lt;/li&gt;
&lt;li&gt;N × C labor queries (one per location × category combination)&lt;/li&gt;
&lt;li&gt;N × C parts queries&lt;/li&gt;
&lt;li&gt;N × C equipment queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A tenant with 200 locations averaging 4 categories each: roughly 2,600 database calls per report run. A larger tenant with 500 locations: over 6,000. Each call returning the full labor table, the full parts table, the full equipment table, hundreds of thousands of rows per call, discarded immediately after the filter ran.&lt;/p&gt;

&lt;p&gt;The 15-minute report times made sense once I saw this. The mystery was why nobody had fixed it yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  The obvious fix, and why it would have been worse
&lt;/h2&gt;

&lt;p&gt;The first instinct was a global pre-fetch. Run each query once before the loop, hold the results in memory, filter from there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pull everything once&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allLabors&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_laborService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllLabors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allParts&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_partsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allEquipment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_equipmentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllEquipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;workOrders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_workOrderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;workOrders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;locationLabors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;allLabors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkOrderId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// etc.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works. Three database calls instead of thousands. The report that took fifteen minutes would finish in seconds.&lt;/p&gt;

&lt;p&gt;I had the branch open, the change written, and was about to push it when I thought about the larger tenants.&lt;/p&gt;

&lt;p&gt;The labor and parts tables hold the full history for the tenant: every work order ever completed, every part ever used, going back years. For a small tenant that's manageable. For a large one with tens of thousands of work orders per year, multiple sites, years of history, calling &lt;code&gt;.ToList()&lt;/code&gt; with no predicate could pull millions of rows into the application server's memory in a single shot.&lt;/p&gt;

&lt;p&gt;The TVF sources took only a culture ID parameter. No way to pass a filter into them. They returned the full tenant table and relied on the caller to narrow things down, so calling &lt;code&gt;.ToList()&lt;/code&gt; with no predicate before it meant pulling everything regardless.&lt;/p&gt;

&lt;p&gt;For the tenants with the worst report times, the ones that had been waiting years for this fix, a global pre-fetch might replace a 15-minute report with an out-of-memory crash. That's not a fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The approach that actually worked
&lt;/h2&gt;

&lt;p&gt;The constraint was: we can't pass a predicate into the TVF, and we can't fetch everything at once either. But we also can't fetch per-loop. That was the original problem.&lt;/p&gt;

&lt;p&gt;The answer was to split it into two phases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1&lt;/strong&gt; — run the location loop normally, collecting work order IDs as you go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;woIdList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;workOrders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_workOrderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetWorkOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;woIdList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workOrders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;woIdList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;woIdList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still N database calls, one per location, no change there. But now we have a bounded list of exactly the work order IDs that appear in this report, scoped to the date range the user selected. Not the full tenant. Not all history. Just the IDs relevant to this specific report run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2&lt;/strong&gt; — one pre-fetch per data type, filtered to that ID list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allLabors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_laborService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllLabors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;woIdList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkOrderId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allParts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_partsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;woIdList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkOrderId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allEquipment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_equipmentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllEquipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cultureId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;woIdList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkOrderId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF6 translates &lt;code&gt;.Contains()&lt;/code&gt; on an integer list to a SQL &lt;code&gt;IN&lt;/code&gt; clause. The database does the filtering. You get back only the rows for work orders that actually appear in the report.&lt;/p&gt;

&lt;p&gt;Then the inner category loop runs against in-memory collections. Zero additional database calls.&lt;/p&gt;

&lt;p&gt;Total queries: N (location work order fetches) + 3 (the scoped pre-fetches). For a tenant with 200 locations that's 203 queries instead of 2,600. For a tenant with 500 locations it's 503 instead of 6,000-plus.&lt;/p&gt;




&lt;h2&gt;
  
  
  The EF6 parameter limit
&lt;/h2&gt;

&lt;p&gt;One more thing to watch: EF6 generates &lt;code&gt;IN&lt;/code&gt; clauses as inline values, not parameterized. SQL Server has a 2,100-parameter limit on parameterized queries, but inline &lt;code&gt;IN&lt;/code&gt; lists bypass that. The risk is query plan caching. Different ID lists produce different SQL strings, so the plan cache can't reuse them. For this use case that was acceptable. The report runs infrequently enough that plan cache pressure wasn't a concern.&lt;/p&gt;

&lt;p&gt;If the ID list ever grew large enough to cause problems, the right move would be passing the IDs as a table-valued parameter and joining inside the query. That gives the plan cache something stable to reuse. For the typical report run the &lt;code&gt;IN&lt;/code&gt; clause approach was clean and measurably fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it looked like after
&lt;/h2&gt;

&lt;p&gt;Before the fix: the main data-collection endpoint averaged around two minutes on the test environment, and the test environment was more powerful than production. In production, the same report ran five to fifteen minutes depending on the tenant.&lt;/p&gt;

&lt;p&gt;After: four to eight seconds.&lt;/p&gt;

&lt;p&gt;The fix had been obvious once the pattern was visible. The loop-inside-a-loop, the TVF sources that returned full tables, the in-memory filtering that discarded almost everything. All of it was right there in the code. The report had shipped like that and nobody had looked closely until the runtime climbed high enough to generate support tickets.&lt;/p&gt;

&lt;p&gt;I don't know how many tenants had been quietly tolerating slow report times without filing tickets. The ones that filed them waited an average of eight months from first report to fix. The ones that didn't file tickets are harder to account for.&lt;/p&gt;




&lt;p&gt;Until next time,&lt;br&gt;
&lt;strong&gt;Deepanshu&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Backend engineer writing about production bugs, distributed systems, and engineering patterns learned the hard way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deepanshu-kumar.dev" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://medium.com/@dev-deepanshu-kumar" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/deepanshu-kumar-dev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://github.com/Dev-Deepanshu-Kumar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>entityframework</category>
      <category>database</category>
      <category>csharp</category>
      <category>backend</category>
    </item>
    <item>
      <title>SQL Server "Login Does Not Exist" via AG Listener After Windows Auth Migration: The Server Name Is a Lie</title>
      <dc:creator>Deepanshu Kumar</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:40:28 +0000</pubDate>
      <link>https://dev.to/dev-deepanshu-kumar/sql-server-login-does-not-exist-via-ag-listener-after-windows-auth-migration-the-server-name-is-1of0</link>
      <guid>https://dev.to/dev-deepanshu-kumar/sql-server-login-does-not-exist-via-ag-listener-after-windows-auth-migration-the-server-name-is-1of0</guid>
      <description>&lt;p&gt;There's an address in our connection strings that looks like a server name but isn't one: &lt;code&gt;ag1-listener.sd.local&lt;/code&gt;. We'd seen it for years. It never mattered. When we switched to SQL Auth, it mattered a lot.&lt;/p&gt;

&lt;p&gt;For context: the app had always used Windows Authentication to connect to SQL Server. One flag, no credentials, nothing to manage. When services moved to Linux containers on EKS, that stopped working — Linux has no Kerberos domain context. We needed SQL Auth: explicit username and password. Store credentials in AWS Secrets Manager, fetch at runtime, substitute into the connection string.&lt;/p&gt;

&lt;p&gt;Seemed mechanical. It worked locally. It worked across every integration environment. In production-like testing, every connection failed with the same error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Login failed for user 'svc_app'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First thing to check — whether the login actually exists on the node you're connected to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_principals&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'svc_app'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty result set. The login wasn't there. But it existed in every other environment. That's when it got interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  The connection string that caused it
&lt;/h2&gt;

&lt;p&gt;The app is multi-tenant. Each client has its own database. Connection strings are stored centrally. When a request arrives, the app reads the stored connection string, injects credentials fetched at runtime, and connects.&lt;/p&gt;

&lt;p&gt;A stored connection string looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;data&lt;/span&gt; &lt;span class="py"&gt;source&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ag1-listener.sd.local;&lt;/span&gt;
&lt;span class="err"&gt;initial&lt;/span&gt; &lt;span class="py"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ClientDatabase;&lt;/span&gt;
&lt;span class="err"&gt;user&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;{{UserId}};&lt;/span&gt;
&lt;span class="py"&gt;password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;{{Password}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The credentials come from Secrets Manager at runtime. The question is: which secret do you fetch?&lt;/p&gt;

&lt;p&gt;In lower environments, the stored addresses were direct server names — &lt;code&gt;sql-dev-01&lt;/code&gt;, &lt;code&gt;sql-dev-02&lt;/code&gt;. One secret per server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;/dev/sql-dev&lt;/span&gt;&lt;span class="mi"&gt;-01&lt;/span&gt;&lt;span class="err"&gt;_credentials&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"svc_app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;/dev/sql-dev&lt;/span&gt;&lt;span class="mi"&gt;-02&lt;/span&gt;&lt;span class="err"&gt;_credentials&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"svc_app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the connection string said &lt;code&gt;sql-dev-01&lt;/code&gt;, the provider fetched the matching secret. Clean.&lt;/p&gt;

&lt;p&gt;In the production-like environment, the stored connection strings said &lt;code&gt;ag1-listener.sd.local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;ag1-listener.sd.local&lt;/code&gt; is not a server name.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a listener actually is
&lt;/h2&gt;

&lt;p&gt;An Availability Group listener is a virtual DNS name that always points to whoever is currently the primary SQL Server node in a cluster. Today it routes to &lt;code&gt;sql-server-01&lt;/code&gt;. After a failover, it routes to &lt;code&gt;sql-server-02&lt;/code&gt;. The app never has to change anything — the listener handles it transparently.&lt;/p&gt;

&lt;p&gt;That's the design. The listener exists specifically so you don't know or care which physical server you're on.&lt;/p&gt;

&lt;p&gt;SQL Auth breaks this.&lt;/p&gt;

&lt;p&gt;With Windows Authentication, your identity was a domain account with rights on every SQL Server node. "Which server?" was irrelevant — the same Kerberos token worked everywhere.&lt;/p&gt;

&lt;p&gt;With SQL Auth, credentials are server-level objects. A SQL Login is created on a specific SQL Server instance. If the login exists on &lt;code&gt;sql-server-01&lt;/code&gt; but not &lt;code&gt;sql-server-02&lt;/code&gt;, it authenticates on one and fails on the other.&lt;/p&gt;

&lt;p&gt;So when we tried to extend the per-server credential approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/prod/sql-server-01_credentials  →  works if listener routes here
/prod/sql-server-02_credentials  →  works if listener routes here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We needed to know which server the listener was pointing to before choosing which secret to fetch. But to find that out, you have to connect. To connect, you need credentials. To get credentials, you need to know which server the listener points to.&lt;/p&gt;

&lt;p&gt;That's a circle. You can't break into it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why lower environments never showed this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local:&lt;/strong&gt; If you're running locally, your connection string provider likely short-circuits to &lt;code&gt;Integrated Security=True&lt;/code&gt; against &lt;code&gt;localhost&lt;/code&gt;. No SQL Auth, no listener, no Secrets Manager. Zero signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration environments:&lt;/strong&gt; No Availability Groups. Single standalone SQL Server instances. Direct server names in the connection strings. Per-server credential lookup works exactly as designed. Still zero signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production-like environment:&lt;/strong&gt; Real Availability Groups. Real listeners in the connection strings. SQL Auth enabled. All three conditions together for the first time. The bug had always been there — it just had nowhere to show up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The conversation with the DBA
&lt;/h2&gt;

&lt;p&gt;When it broke, we spent time in the application code first. The provider looked correct. The secret naming looked right. It took the DBA explaining AG architecture for the root cause to land.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The listener is intentionally abstract. After any failover it points to a different physical server. Any credential mapping that depends on knowing the server name will break the moment a failover happens."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So per-server credentials were the wrong model entirely, not just broken in this one case.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The SQL login needs to exist on all nodes with the same username and password. Then it doesn't matter which node the listener routes to — the credential is always valid."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That reframed the whole problem. The listener answers &lt;em&gt;where do I connect?&lt;/em&gt; The credential needs to answer &lt;em&gt;how do I authenticate?&lt;/em&gt; — independently of where. Those two concerns have to be separated.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;A SQL Login is a server-level object. It has to be created on each SQL Server instance individually — it doesn't replicate automatically across AG nodes.&lt;/p&gt;

&lt;p&gt;Create the login on every node in the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Run on each node in the Availability Group&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;LOGIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;svc_app&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your-password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inside each database on that node, create the corresponding user and grant access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ClientDatabase&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;svc_app&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;LOGIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;svc_app&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sp_addrolemember&lt;/span&gt; &lt;span class="s1"&gt;'db_datareader'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'svc_app'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sp_addrolemember&lt;/span&gt; &lt;span class="s1"&gt;'db_datawriter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'svc_app'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the credentials once — one secret, fetched at runtime, applied to every connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"svc_app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connection string template stays the same. At runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;data&lt;/span&gt; &lt;span class="py"&gt;source&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ag1-listener.sd.local;&lt;/span&gt;
&lt;span class="err"&gt;initial&lt;/span&gt; &lt;span class="py"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ClientDatabase;&lt;/span&gt;
&lt;span class="err"&gt;user&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;svc_app;&lt;/span&gt;
&lt;span class="py"&gt;password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...;&lt;/span&gt;
&lt;span class="py"&gt;MultiSubnetFailover&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The listener routes to whichever node is currently primary. That node has the login. Authentication succeeds. If a failover happens and the listener routes to a different node, same result — the login is there too.&lt;/p&gt;




&lt;h2&gt;
  
  
  The scenario that still breaks it
&lt;/h2&gt;

&lt;p&gt;One gap remains after this is working.&lt;/p&gt;

&lt;p&gt;A new SQL Server cluster gets added to the environment. DB-ops provisions it, adds databases, starts assigning clients to it. But nobody ran &lt;code&gt;CREATE LOGIN&lt;/code&gt; on the new nodes.&lt;/p&gt;

&lt;p&gt;The login exists on every older node. Not on the new cluster. Clients on the new cluster fail with the exact same error. Everyone else is fine.&lt;/p&gt;

&lt;p&gt;From the outside it looks like a data problem. Some clients broken, some not. Seemingly random. The diagnosis is one query on the suspected node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_principals&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'svc_app'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Empty = login not created on this node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix: run &lt;code&gt;CREATE LOGIN&lt;/code&gt; and &lt;code&gt;CREATE USER&lt;/code&gt; on each new node before any clients land there. Simple operation. The risk is forgetting it exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  What would have caught this earlier
&lt;/h2&gt;

&lt;p&gt;One question during design: &lt;em&gt;"What format are the connection strings stored in production?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Answer: listener addresses.&lt;/p&gt;

&lt;p&gt;That single fact collapses the per-server credential model immediately. You can't map credentials to a virtual DNS name — you need credentials that work on whatever physical server the name resolves to. That conversation should have happened in the design meeting. It happened in production testing instead.&lt;/p&gt;

&lt;p&gt;The lower environments used standalone servers with direct names, so everything appeared to work. If production-like connection strings had been used in integration testing — or if the DBA had been in the design review — this would have been an hours-long problem. It wasn't.&lt;/p&gt;

&lt;p&gt;Infrastructure assumptions tend to be invisible until the environment changes. Windows Auth worked everywhere because the domain handled it silently. SQL Auth makes every assumption explicit. The assumption that you can map credentials to a server name was always there — it just never mattered until the listener made "server name" mean something ambiguous.&lt;/p&gt;




&lt;p&gt;Until next time,&lt;br&gt;
&lt;strong&gt;Deepanshu&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Backend engineer writing about production bugs, distributed systems, and engineering patterns learned the hard way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deepanshu-kumar.dev" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://medium.com/@dev-deepanshu-kumar" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/deepanshu-kumar-dev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://github.com/Dev-Deepanshu-Kumar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sqlserver</category>
      <category>debugging</category>
      <category>database</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Why the Same Bug Keeps Coming Back - And How to Stop It</title>
      <dc:creator>Deepanshu Kumar</dc:creator>
      <pubDate>Tue, 21 Jul 2026 10:31:41 +0000</pubDate>
      <link>https://dev.to/dev-deepanshu-kumar/why-the-same-bug-keeps-coming-back-and-how-to-stop-it-4hd4</link>
      <guid>https://dev.to/dev-deepanshu-kumar/why-the-same-bug-keeps-coming-back-and-how-to-stop-it-4hd4</guid>
      <description>&lt;p&gt;A few months ago, I fixed a bug on a Friday afternoon. Status filter on a dashboard widget returning empty results for some users. Found the problem, added one line, tests passed, shipped it. Felt good about it.&lt;/p&gt;

&lt;p&gt;The following Tuesday someone filed another ticket. Different widget, same symptom. Assignee filter, empty results, same users affected. I opened the file, found the missing line in about thirty seconds, and just sat there for a moment.&lt;/p&gt;

&lt;p&gt;I hadn't fixed the bug. I fixed one place where the bug lived. The same broken pattern was sitting in three other handlers, and I hadn't thought to check.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;The app was a multi-tenant SaaS where users have permissions scoped to specific warehouses. Dashboard widgets query orders, but only the ones the user is allowed to see. Simple enough.&lt;/p&gt;

&lt;p&gt;We had six of these query handlers. Someone had written the first one correctly, copy-pasted it five times as new widgets were added, and somewhere along the way one important line kept getting dropped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Priority widget handler — written correctly&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetPrioritiesQuery&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;permittedWarehouses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_warehouseService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPermittedWarehouseIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;permittedWarehouses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// include orders with no warehouse assigned&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPriorities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permittedWarehouses&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Status widget handler — the one that broke&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetStatusesQuery&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;permittedWarehouses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_warehouseService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPermittedWarehouseIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// that null append never made it here&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStatuses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permittedWarehouses&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;null&lt;/code&gt; append matters because some orders aren't assigned to any warehouse. Without it, those orders get filtered out entirely. For users whose entire order history happened to be unassigned, the widget showed nothing at all.&lt;/p&gt;

&lt;p&gt;One line. Easy fix. But I only fixed it in the one handler that had broken visibly.&lt;/p&gt;

&lt;p&gt;When I finally audited all six:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GetOrderStatuses     → ❌ missing null append
GetOrderPriorities   → ✅ has null append  
GetOrderAssignees    → ❌ missing null append
GetOrderLocations    → ✅ has null append
GetOrderCategories   → ❌ missing null append
GetOrderCosts        → ✅ has null append
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three broken, three fine. The broken ones just hadn't surfaced yet, either because fewer users hit those widgets or because the affected users had quietly assumed the data wasn't there and moved on without filing a ticket.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this keeps happening
&lt;/h2&gt;

&lt;p&gt;The code didn't start inconsistent. Someone wrote the first handler carefully. The inconsistency came from copy-paste and the very reasonable assumption that "this is basically the same as that other handler." Nobody set out to leave three bugs behind.&lt;/p&gt;

&lt;p&gt;The trap is how bugs announce themselves. They show up as individual tickets, individual screens, individual user complaints. The natural instinct is to find the broken thing and fix it. That instinct is right, but it stops one step too early.&lt;/p&gt;

&lt;p&gt;In my case the pattern was "handlers that filter by warehouse permission don't consistently include unassigned orders." I fixed one handler. I should have searched for the pattern and fixed the category.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one that never got reported
&lt;/h2&gt;

&lt;p&gt;After I found the other three broken handlers, I thought about &lt;code&gt;GetOrderCategories&lt;/code&gt;. It had been broken since the original commit. Same missing line, same bug. No ticket had ever been filed for it.&lt;/p&gt;

&lt;p&gt;Two explanations. Either none of our users had an order history composed entirely of unassigned-warehouse orders filtered by category. Or some of them had, looked at the empty widget, assumed the data wasn't there, and closed the tab.&lt;/p&gt;

&lt;p&gt;I don't know which. Probably both, at different customers, at different times. The second one bothers me more than the bug itself. A real user looked at a broken screen, formed the wrong mental model of their own data, and moved on without telling anyone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before you close the PR
&lt;/h2&gt;

&lt;p&gt;The habit that changed how I work: before writing the fix, write down what's wrong in one sentence of plain English. Not which file, not which method. The rule that got violated.&lt;/p&gt;

&lt;p&gt;"GetOrderStatuses doesn't append null to the warehouse list" is a file reference. "Warehouse-permission queries don't consistently include null to catch unassigned orders" is a pattern. The second version tells you what to search for everywhere else.&lt;/p&gt;

&lt;p&gt;Then search:&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="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"GetPermittedWarehouseIds"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.cs"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read every result. For each one: should this also have the fix? If yes, it goes in the same PR. Not a follow-up ticket, not later. The same PR, while the context is fresh and the intent is clear.&lt;/p&gt;

&lt;p&gt;The audit took me twenty minutes. The follow-up ticket that would have found &lt;code&gt;GetOrderAssignees&lt;/code&gt; and &lt;code&gt;GetOrderCategories&lt;/code&gt; would have taken two separate Tuesdays. That math is easy.&lt;/p&gt;




&lt;p&gt;The bug that got reported was the one someone noticed. The ones that didn't get reported were waiting for the right user, the right data, or someone patient enough to sit with empty results and eventually wonder why.&lt;/p&gt;

&lt;p&gt;Until next time,&lt;br&gt;
&lt;strong&gt;Deepanshu&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Backend engineer writing about production bugs, distributed systems, and engineering patterns learned the hard way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deepanshu-kumar.dev" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://medium.com/@dev-deepanshu-kumar" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/deepanshu-kumar-dev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://github.com/Dev-Deepanshu-Kumar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>softwareengineering</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
