<?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: Paul Abib S. Camano</title>
    <description>The latest articles on DEV Community by Paul Abib S. Camano (@aebibtech).</description>
    <link>https://dev.to/aebibtech</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%2F80769%2F921b2966-8d50-49dd-a6ed-49c9e367d578.jpeg</url>
      <title>DEV Community: Paul Abib S. Camano</title>
      <link>https://dev.to/aebibtech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aebibtech"/>
    <language>en</language>
    <item>
      <title>Case Study: High-Throughput Bulk Import in .NET 9 &amp; PostgreSQL 17</title>
      <dc:creator>Paul Abib S. Camano</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:36:35 +0000</pubDate>
      <link>https://dev.to/aebibtech/case-study-high-throughput-bulk-import-in-net-9-postgresql-17-3oj2</link>
      <guid>https://dev.to/aebibtech/case-study-high-throughput-bulk-import-in-net-9-postgresql-17-3oj2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Scaling Worship Song Library Ingestion from 2,900 to 15,700 Items/Sec with FastIngest &amp;amp; Binary COPY&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; TeamChords v2 (Online ChordPro library and live setlist management for worship teams and gigging bands)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Stack:&lt;/strong&gt; ASP.NET Core 9, PostgreSQL 17, FastIngest .NET, EF Core 9, SignalR, Redis 7, React Native / Expo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date:&lt;/strong&gt; October 2026&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Shipped to Production&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;When musicians, band directors, and worship teams migrate to &lt;strong&gt;TeamChords v2&lt;/strong&gt;, they rarely start from scratch. They bring legacy song libraries containing anywhere from 200 to over 2,000 chord sheets formatted as ChordPro files (&lt;code&gt;.cho&lt;/code&gt;, &lt;code&gt;.pro&lt;/code&gt;), OnSong exports, CSV spreadsheets, and JSON archives.&lt;/p&gt;

&lt;p&gt;Initially, importing large libraries suffered from a classic architectural dilemma: &lt;strong&gt;client-side sequential imports&lt;/strong&gt; choked browser memory and network connections, while traditional &lt;strong&gt;Entity Framework Core batching&lt;/strong&gt; hit a hard performance ceiling around ~2,900 items/second due to entity tracking and SQL generation overhead.&lt;/p&gt;

&lt;p&gt;By adopting &lt;strong&gt;FastIngest&lt;/strong&gt;—a high-throughput .NET data ingestion library utilizing PostgreSQL’s native binary &lt;code&gt;COPY&lt;/code&gt; protocol (&lt;code&gt;COPY FROM STDIN (FORMAT BINARY)&lt;/code&gt;)—we transformed bulk library onboarding:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;5.4x Latency Reduction&lt;/strong&gt;: Ingestion of 1,000 chord sheets dropped from &lt;strong&gt;343.6 ms to 63.7 ms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5.4x Throughput Scaling&lt;/strong&gt;: Ingestion throughput jumped from &lt;strong&gt;2,910 items/sec to 15,710 items/sec&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Allocation Memory Footprint&lt;/strong&gt;: Bypassing ORM change tracking reduced server garbage collection thread allocations to negligible levels (&lt;strong&gt;0 to 106 KB&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilient User Experience&lt;/strong&gt;: Replaced fragile HTTP polling with real-time row progress and instant library synchronization via &lt;strong&gt;SignalR&lt;/strong&gt; and a &lt;strong&gt;Redis backplane&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The Architectural Challenge
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 The Domain &amp;amp; The Problem
&lt;/h3&gt;

&lt;p&gt;In live music performance software, a "Chord Sheet" is not a simple string. It is a structured document containing metadata (Title, Artist, Key, Tempo, Time Signature, Capo, CCLI #) alongside lyric lines interspersed with musical chords (&lt;code&gt;[G]Amazing [D]grace, how [Em]sweet the [C]sound&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{title: Amazing Grace}
{artist: John Newton}
{key: G}

[G]Amazing grace, how [C]sweet the [G]sound
That saved a [D]wretch like me...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a church or band registers a new organization on TeamChords, onboarding requires importing hundreds of &lt;code&gt;.cho&lt;/code&gt; files or multi-megabyte JSON/CSV dumps. If this process is slow, drops connections, or freezes the client interface, user trust is destroyed during the critical first five minutes of onboarding.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 The Dilemma: Client Memory vs. Server Capacity
&lt;/h3&gt;

&lt;p&gt;The initial frontend implementation attempted to protect client device memory (especially on mobile devices running React Native / Expo) by parsing files locally and firing sequential HTTP POST requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total Duration = N × (HTTP RTT + Server Processing Latency)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For 500 songs with an average 40 ms network round-trip, this meant &lt;strong&gt;over 25 seconds of loading spinners&lt;/strong&gt;, frequent mobile OS background kills, connection timeouts, and hundreds of individual database roundtrips.&lt;/p&gt;

&lt;p&gt;We needed a unified, high-throughput bulk import pipeline that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept multi-format inputs: Multipart Form-Data (file pickers), NDJSON streams, raw CSVs, or batch JSON arrays.&lt;/li&gt;
&lt;li&gt;Stream directly from the HTTP request into PostgreSQL without buffering gigabytes into memory.&lt;/li&gt;
&lt;li&gt;Provide live, granular progress updates back to the user interface.&lt;/li&gt;
&lt;li&gt;Maintain absolute tenant isolation and feature gate enforcement.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Evolution of Bulk Import Approaches
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    subgraph A1 ["Approach 1: Client Fan-Out"]
        direction LR
        C1["Client Web / Mobile"] --&amp;gt;|"1 request per song"| E1["POST /api/chordsheets"]
        E1 --&amp;gt;|"N network roundtrips"| DB1[("PostgreSQL")]
    end&lt;/code&gt;&lt;/pre&gt;





&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    subgraph A2 ["Approach 2: EF Core AddRange Batching"]
        direction LR
        C2["Client (Web / Mobile)"] --&amp;gt;|"Bulk JSON array"| E2["POST /api/chordsheets/bulk"]
        E2 --&amp;gt;|"Chunk 50 + AddRange"| EF["EF Core ChangeTracker"]
        EF --&amp;gt;|"Batched INSERT SQL"| DB2[("PostgreSQL")]
    end&lt;/code&gt;&lt;/pre&gt;





&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    subgraph A3 ["Approach 3: FastIngest Binary COPY"]
        direction LR
        C3["Client (Web / Mobile)"] --&amp;gt;|"NDJSON / CSV stream"| E3["POST /api/chordsheets/bulk"]
        E3 --&amp;gt;|"Streaming channel"| FI["FastIngest Pipeline"]
        FI --&amp;gt;|"NpgsqlBinaryImporter"| DB3[("PostgreSQL Binary Heap")]
        FI -.-&amp;gt;|"Live row progress"| HUB["SignalR + Redis"]
        HUB -.-&amp;gt;|"Real-time push"| C3
    end&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Approach 1: Client-Side Sequential Fan-Out
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mechanism:&lt;/strong&gt; Client loops over files in JavaScript/TypeScript, parses each ChordPro file, and issues individual &lt;code&gt;POST /api/chordsheets&lt;/code&gt; requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt; Trivial to write; uses existing single-entity endpoint and validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;Massive connection overhead (&lt;em&gt;N&lt;/em&gt; TLS handshakes, HTTP headers, authentication cycles).&lt;/li&gt;
&lt;li&gt;High risk of client timeouts on mobile or cellular connections.&lt;/li&gt;
&lt;li&gt;Vulnerable to rate-limiting middleware (authenticated permit limit is 600 req/min).&lt;/li&gt;
&lt;li&gt;Clutters server request logs and Sentry tracing with thousands of repetitive spans.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Approach 2: Server-Side EF Core Batching (&lt;code&gt;AddRange&lt;/code&gt; + &lt;code&gt;ChangeTracker.Clear()&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mechanism:&lt;/strong&gt; Client uploads a single JSON payload containing an array of DTOs. The server parses the payload and inserts records in batches of 50:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;dtos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;BatchSize&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;batch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dtos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BatchSize&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;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToEntity&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;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChordSheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRangeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangeTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Attempt to avoid memory explosion&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt; Single HTTP connection; transactional per-batch isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change Tracker Overhead:&lt;/strong&gt; Even with &lt;code&gt;ChangeTracker.Clear()&lt;/code&gt;, EF Core must instantiate snapshot state, evaluate foreign key relationships, and compile SQL parameter lists for every entity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL Query Overhead:&lt;/strong&gt; Every batch generates parameterized multi-row &lt;code&gt;INSERT INTO "ChordSheets" (...) VALUES (...), (...)&lt;/code&gt; statements that PostgreSQL must parse, plan, and execute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput Plateau:&lt;/strong&gt; Throughput strictly plateaued at &lt;strong&gt;~2,900 items/second&lt;/strong&gt;, regardless of whether batch size was set to 50, 100, or 250.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Approach 3: Zero-Allocation Streaming with FastIngest &amp;amp; PostgreSQL Binary COPY
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mechanism:&lt;/strong&gt; Replaced EF Core with &lt;code&gt;FastIngestPipeline&amp;lt;ChordSheetIngestRecord&amp;gt;&lt;/code&gt; and a custom &lt;code&gt;ChordSheetIngestionSink&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How It Works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The endpoint reads the incoming request stream directly (supporting NDJSON, CSV, or multipart file streams) without deserializing full collections into managed arrays.&lt;/li&gt;
&lt;li&gt;FastIngest pushes records through a bounded &lt;code&gt;System.Threading.Channels.Channel&amp;lt;T&amp;gt;&lt;/code&gt; with backpressure.&lt;/li&gt;
&lt;li&gt;A dedicated background ingestion sink acquires an underlying &lt;code&gt;NpgsqlConnection&lt;/code&gt; and opens a direct binary copy stream:
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;writer&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;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginBinaryImportAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="s"&gt;$"COPY \"ChordSheets\" (\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\") FROM STDIN (FORMAT BINARY)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Columns are mapped and written in PostgreSQL's native wire binary format, bypassing all SQL parsing and query planning.&lt;/li&gt;
&lt;li&gt;Live row counters update a SignalR hub context, broadcasting progress to the client via WebSockets.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Implementation Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 The Minimal API Endpoint (&lt;code&gt;tcv2.Api/Endpoints/ChordSheetEndpoints.cs&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The endpoint accepts &lt;code&gt;multipart/form-data&lt;/code&gt;, &lt;code&gt;application/x-ndjson&lt;/code&gt;, &lt;code&gt;text/csv&lt;/code&gt;, or standard JSON, auto-detecting the schema and streaming directly into the pipeline:&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="n"&gt;chordSheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/bulk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;IHubContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SetListHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISetListClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Authenticate &amp;amp; enforce multi-tenant organization boundary&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;auth&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;EndpointHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireOrgMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetOrgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Feature gate check (tier limits)&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;org&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Organizations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;targetOrgId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;gate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FeatureGate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckBulkUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gate&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Open raw database connection for binary COPY&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connStr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;npgsqlConn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NpgsqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connStr&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;npgsqlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Configure FastIngest Pipeline&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastIngestPipeline&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChordSheetIngestRecord&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&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="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"OrgId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Artist"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Content"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"CreatedAt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"UpdatedAt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientMutationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ClientMutationId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChordSheetIngestValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorStrategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ErrorStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CollectAndContinue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithBatchSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithChannelCapacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&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;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionId&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;msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Ingested &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowsSucceeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; songs..."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;BulkUploadProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowsProcessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowsProcessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&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;mappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetMappings&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;sink&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChordSheetIngestionSink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;npgsqlConn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetOrgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Execute binary ingestion under stopwatch &amp;amp; allocation telemetry&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalSw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&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;startAllocatedBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllocatedBytesForCurrentThread&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;result&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;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteToSinkAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;totalSw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&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;totalElapsedMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalSw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&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;throughput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalSw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; 
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalProcessed&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;totalSw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt; 
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&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;allocatedKb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllocatedBytesForCurrentThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;startAllocatedBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1024.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"[BENCHMARK] FastIngest bulk chord sheet upload finished for Org {OrgId}: {Total} items ({Success} succeeded) in {TotalMs:F2}ms ({Throughput:F2} items/sec). Memory: {MemoryAllocatedKb:F2} KB."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;targetOrgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalProcessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSucceeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalElapsedMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throughput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allocatedKb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 6. Broadcast dual completion notifications&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;summary&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;BulkUploadSummaryDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CreatedIds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;TotalProcessed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalProcessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Successful&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSucceeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Failed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalFailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalElapsedMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;ThroughputItemsPerSecond&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;throughput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;DbSaveMilliseconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalElapsedMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&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;hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HubGroupNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Organization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetOrgId&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;BulkUploadSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;);&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;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionId&lt;/span&gt;&lt;span class="p"&gt;))&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;hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;BulkUploadSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&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;hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;BulkUploadFinished&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&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;h3&gt;
  
  
  3.2 The PostgreSQL Ingestion Sink (&lt;code&gt;ChordSheetIngestionSink.cs&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The custom sink writes directly into the binary stream using &lt;code&gt;NpgsqlBinaryImporter&lt;/code&gt;:&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;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="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;WriteBatchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChordSheetIngestRecord&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;copySql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"COPY \"ChordSheets\" (\"&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;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\", \""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_columnNames&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;\") FROM STDIN (FORMAT BINARY)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;writer&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;_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginBinaryImportAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copySql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&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;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartRowAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&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;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&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;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_createdIds&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="n"&gt;id&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&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;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_targetOrgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"Various"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&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="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimestampTz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimestampTz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientMutationId&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;++;&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&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;h2&gt;
  
  
  4. Empirical Benchmark Methodology &amp;amp; Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Test Rig Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware:&lt;/strong&gt; Apple M-series Silicon, 16 GB unified memory, local NVMe SSD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL 17 Alpine in Docker container (&lt;code&gt;teamchords-postgres&lt;/code&gt;), port &lt;code&gt;5432&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache/Backplane:&lt;/strong&gt; Redis 7 Alpine in Docker container (&lt;code&gt;teamchords-redis&lt;/code&gt;), port &lt;code&gt;6379&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; 202 authentic ChordPro files from real worship libraries in &lt;code&gt;chordpro-sheets/&lt;/code&gt; (synthesized up to 1,000 unique records with realistic lyrics and musical notations).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Harness:&lt;/strong&gt; Automated Node.js runner (&lt;code&gt;scripts/benchmark_bulk_upload.mjs&lt;/code&gt;) establishing real-time SignalR WebSocket connections, dispatching HTTP POST payloads, measuring client wall-clock time, and capturing server-side telemetry.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2 Side-by-Side Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dataset Size&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Client Wall-Clock&lt;/th&gt;
&lt;th&gt;Server Total Latency&lt;/th&gt;
&lt;th&gt;Server DB Flush&lt;/th&gt;
&lt;th&gt;Server Throughput&lt;/th&gt;
&lt;th&gt;Client Throughput&lt;/th&gt;
&lt;th&gt;Success Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;50 items&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;EF Core (Baseline)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;21.5 ms&lt;/td&gt;
&lt;td&gt;16.5 ms&lt;/td&gt;
&lt;td&gt;15.9 ms&lt;/td&gt;
&lt;td&gt;3,031.7 /s&lt;/td&gt;
&lt;td&gt;2,321.6 /s&lt;/td&gt;
&lt;td&gt;50/50 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FastIngest (Binary)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.5 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.9 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.9 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6,315.8 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,561.2 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50/50 (100%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Delta&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-9.3%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-52.1% (2.1x)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-50.3%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;+108.3%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;+10.3%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;202 items&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;EF Core (Baseline)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;72.8 ms&lt;/td&gt;
&lt;td&gt;67.0 ms&lt;/td&gt;
&lt;td&gt;65.6 ms&lt;/td&gt;
&lt;td&gt;3,014.3 /s&lt;/td&gt;
&lt;td&gt;2,776.3 /s&lt;/td&gt;
&lt;td&gt;202/202 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FastIngest (Binary)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35.3 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21.1 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21.1 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9,555.7 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5,715.0 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;202/202 (100%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Delta&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-51.5%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-68.5% (3.2x)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-67.8%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;+217.0%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;+105.8%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;500 items&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;EF Core (Baseline)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;188.7 ms&lt;/td&gt;
&lt;td&gt;174.8 ms&lt;/td&gt;
&lt;td&gt;171.5 ms&lt;/td&gt;
&lt;td&gt;2,860.4 /s&lt;/td&gt;
&lt;td&gt;2,650.3 /s&lt;/td&gt;
&lt;td&gt;500/500 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FastIngest (Binary)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;63.5 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36.9 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36.9 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13,549.0 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7,868.1 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;500/500 (100%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Delta&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-66.3%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-78.9% (4.7x)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;-78.5%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;+373.7%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;+196.9%&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1,000 items&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;EF Core (Baseline)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;360.1 ms&lt;/td&gt;
&lt;td&gt;343.6 ms&lt;/td&gt;
&lt;td&gt;338.2 ms&lt;/td&gt;
&lt;td&gt;2,910.4 /s&lt;/td&gt;
&lt;td&gt;2,777.0 /s&lt;/td&gt;
&lt;td&gt;1000/1000 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FastIngest (Binary)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;114.5 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;63.7 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;63.7 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15,709.7 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8,736.9 /s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1000/1000 (100%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Delta&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-68.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-81.5% (5.4x)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-81.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+439.8%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+214.6%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server Ingestion Latency (1,000 Items):
EF Core:    [==================================================] 343.6 ms
FastIngest: [=========] 63.7 ms  &amp;lt;-- 5.39x Faster

Server Throughput (1,000 Items):
EF Core:    [=====] 2,910 items/sec
FastIngest: [==================================================] 15,710 items/sec  &amp;lt;-- 5.40x Higher Throughput
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.3 Memory Allocation &amp;amp; Garbage Collection Analysis
&lt;/h3&gt;

&lt;p&gt;Telemetry captured via &lt;code&gt;GC.GetAllocatedBytesForCurrentThread()&lt;/code&gt; revealed the most staggering advantage of binary COPY:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ingested Items&lt;/th&gt;
&lt;th&gt;EF Core Allocations (Heap Delta)&lt;/th&gt;
&lt;th&gt;FastIngest Allocations (Thread Delta)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~1.4 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.00 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;202&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~5.8 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.00 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~14.2 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;106.36 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~28.5 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.00 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why did FastIngest allocate virtually 0 KB?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero Intermediate Strings&lt;/strong&gt;: Strings are encoded directly into the UTF-8 binary stream without allocating temporary SQL parameter strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Entity Snapshots&lt;/strong&gt;: EF Core's ChangeTracker creates a dictionary entry, property snapshot, and relationship graph for every tracked object. FastIngest writes directly to the Npgsql buffer and flushes immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Array Pool Buffering&lt;/strong&gt;: FastIngest internally rents memory buffers from &lt;code&gt;ArrayPool&amp;lt;byte&amp;gt;.Shared&lt;/code&gt;, recycling them between chunks without generating Gen 0/Gen 1 GC garbage.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  5. Architectural Considerations &amp;amp; Trade-Offs
&lt;/h2&gt;

&lt;p&gt;While the performance gains of PostgreSQL binary &lt;code&gt;COPY&lt;/code&gt; are extraordinary, replacing an ORM in a production application introduces specific trade-offs that every systems architect must evaluate:&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Bypassing EF Core Lifecycle Interceptors
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
Binary &lt;code&gt;COPY&lt;/code&gt; writes directly into PostgreSQL heap tables. It does &lt;strong&gt;not&lt;/strong&gt; fire EF Core interceptors, &lt;code&gt;SaveChangesInterceptor&lt;/code&gt;, or client-side value generators.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mitigation in TeamChords:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Auto-generated audit fields (&lt;code&gt;CreatedAt&lt;/code&gt;, &lt;code&gt;UpdatedAt&lt;/code&gt;) must be populated explicitly in the sink (&lt;code&gt;DateTime.UtcNow&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;GUID primary keys (&lt;code&gt;Id&lt;/code&gt;) must be generated client-side or assigned explicitly in the sink before writing the row.&lt;/li&gt;
&lt;li&gt;Optimistic concurrency tokens (&lt;code&gt;Version = 1&lt;/code&gt;) must be explicitly initialized.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.2 Transaction Boundaries &amp;amp; Error Isolation
&lt;/h3&gt;

&lt;p&gt;Binary &lt;code&gt;COPY&lt;/code&gt; is fundamentally an all-or-nothing operation per stream. If a single row violates a database constraint (e.g., an illegal foreign key or null value in a non-nullable column), PostgreSQL aborts the entire COPY operation and invalidates the transaction.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastIngest Solution:&lt;/strong&gt;
FastIngest uses a two-tier validation approach:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pre-Copy Fluent Validation (&lt;code&gt;ChordSheetIngestValidator&lt;/code&gt;)&lt;/strong&gt;: Validates essential business rules (e.g., non-empty Title, valid Organization UUID) in memory before the row reaches the sink.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ErrorStrategy.CollectAndContinue&lt;/code&gt;&lt;/strong&gt;: Malformed rows are filtered out and appended to &lt;code&gt;result.Errors&lt;/code&gt; without entering the binary stream, allowing clean rows to ingest without aborting the batch.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.3 Database Triggers &amp;amp; Index Maintenance
&lt;/h3&gt;

&lt;p&gt;Unlike plain bulk inserts, binary &lt;code&gt;COPY&lt;/code&gt; still updates table indexes and fires database-level triggers (&lt;code&gt;BEFORE INSERT&lt;/code&gt; / &lt;code&gt;AFTER INSERT&lt;/code&gt;). However, foreign key checks and unique constraints are validated row-by-row.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For tables with heavy index counts (e.g. full-text search tsvectors), the bottleneck shifts from CPU/serialization to PostgreSQL index maintenance. In our testing on &lt;code&gt;"ChordSheets"&lt;/code&gt;, the presence of b-tree indexes on &lt;code&gt;OrgId&lt;/code&gt; and &lt;code&gt;Title&lt;/code&gt; had negligible impact up to 10,000 rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.4 Cross-Platform Client Considerations
&lt;/h3&gt;

&lt;p&gt;In client applications (React Native on iOS/Android and web browsers):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tabular Files (&lt;code&gt;.csv&lt;/code&gt;, &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;.ndjson&lt;/code&gt;):&lt;/strong&gt; Streamed directly as &lt;code&gt;FormData&lt;/code&gt; to prevent parsing large files in the JavaScript thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Folder / Multi-File Imports (ChordPro files):&lt;/strong&gt; The frontend worker parses files into lightweight NDJSON lines in chunks, piping them into a single HTTP POST request. This keeps the mobile UI thread completely unblocked and provides predictable memory consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.5 Tenant Isolation &amp;amp; Security
&lt;/h3&gt;

&lt;p&gt;Because bulk operations operate with raw database connections, standard EF Core global query filters (&lt;code&gt;HasQueryFilter(x =&amp;gt; x.OrgId == currentOrg)&lt;/code&gt;) do not automatically protect the query.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Defensive Design:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;The API strictly enforces &lt;code&gt;EndpointHelpers.RequireOrgMember&lt;/code&gt; prior to establishing the &lt;code&gt;NpgsqlConnection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The sink constructor forces an immutable &lt;code&gt;targetOrgId&lt;/code&gt; parameter. Every row written to PostgreSQL overwrites any client-supplied &lt;code&gt;OrgId&lt;/code&gt; with the authenticated session's verified organization ID, preventing cross-tenant privilege escalation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Practitioner Checklist: When to Use FastIngest
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Use Standard EF Core &lt;code&gt;AddRange&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Use FastIngest + Binary COPY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Importing &amp;lt; 20 records&lt;/td&gt;
&lt;td&gt;✅ Ideal&lt;/td&gt;
&lt;td&gt;❌ Overkill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relies heavily on EF Core cascade deletes/triggers&lt;/td&gt;
&lt;td&gt;✅ Ideal&lt;/td&gt;
&lt;td&gt;⚠️ Must replicate in SQL/Sink&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingesting 200 to 100,000+ records&lt;/td&gt;
&lt;td&gt;❌ Bottleneck (~2.9k/s)&lt;/td&gt;
&lt;td&gt;✅ Ideal (15k–50k/s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming directly from CSV, NDJSON, or Excel&lt;/td&gt;
&lt;td&gt;❌ Requires custom parsing&lt;/td&gt;
&lt;td&gt;✅ Native Built-in Feature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live row-level progress updates to WebSockets&lt;/td&gt;
&lt;td&gt;⚠️ Manual batch counters&lt;/td&gt;
&lt;td&gt;✅ Native &lt;code&gt;OnProgress&lt;/code&gt; hook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero GC pressure on microservices&lt;/td&gt;
&lt;td&gt;❌ High GC allocation&lt;/td&gt;
&lt;td&gt;✅ Minimal to zero GC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;By re-architecting the bulk import pipeline in TeamChords v2 from sequential client calls to FastIngest's streaming binary COPY pipeline, we eliminated one of the most frustrating bottlenecks in musical library management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1,000 songs are ingested into PostgreSQL in 63 milliseconds.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network wait time dropped by over 68%.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server memory allocations were reduced to virtually zero.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time SignalR progress keeps the user informed and confident.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For any modern ASP.NET Core application dealing with bulk uploads, migrating from EF Core batch loops to FastIngest and PostgreSQL binary COPY is one of the highest ROI optimizations available in the data engineering ecosystem.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>fastingest</category>
      <category>teamchords</category>
    </item>
    <item>
      <title>FastIngest: Zero-Allocation Bulk Ingestion Pipeline for .NET</title>
      <dc:creator>Paul Abib S. Camano</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:05:22 +0000</pubDate>
      <link>https://dev.to/aebibtech/fastingest-zero-allocation-bulk-ingestion-pipeline-for-net-29nf</link>
      <guid>https://dev.to/aebibtech/fastingest-zero-allocation-bulk-ingestion-pipeline-for-net-29nf</guid>
      <description>&lt;p&gt;Every time I built data-intensive systems in .NET, handling bulk data ingestion always felt fragmented. Different databases required completely different boilerplate, performance quirks, and error-handling strategies.&lt;/p&gt;

&lt;p&gt;So I spent the week solving it cleanly from first principles.&lt;/p&gt;

&lt;p&gt;I’m excited to introduce FastIngest: a modular, high-throughput bulk ingestion framework for .NET. Published completely open source under the permissive MIT license.&lt;/p&gt;

&lt;p&gt;Instead of writing provider-specific bulk copy hacks for every new project, FastIngest gives developers a unified, highly optimized API designed for heavy write loads and streaming pipelines.&lt;/p&gt;

&lt;p&gt;📦 Available today on NuGet (v0.3.2):&lt;/p&gt;

&lt;p&gt;Core Pipeline Engine: FastIngest.Core&lt;/p&gt;

&lt;p&gt;Relational Providers: SQL Server, PostgreSQL, MySQL, SQLite&lt;/p&gt;

&lt;p&gt;Document &amp;amp; Cloud Stores: Azure Cosmos DB, MongoDB&lt;/p&gt;

&lt;p&gt;Search &amp;amp; Indexing: Elasticsearch&lt;/p&gt;

&lt;p&gt;Extensions: Pipeline utilities &amp;amp; dependency injection helpers&lt;/p&gt;

&lt;p&gt;Without an official announcement, the suite organically crossed 1,600+ downloads across 9 packages in its first 48 hours.&lt;/p&gt;

&lt;p&gt;The project is free for both commercial and non-commercial use:&lt;/p&gt;

&lt;p&gt;📖 Documentation: &lt;a href="https://lnkd.in/gVKAs-Qt" rel="noopener noreferrer"&gt;https://lnkd.in/gVKAs-Qt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💻 GitHub (MIT License): &lt;a href="https://lnkd.in/gRgqzF3S" rel="noopener noreferrer"&gt;https://lnkd.in/gRgqzF3S&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📦 NuGet: Search FastIngest or find the packages under aebibtech&lt;/p&gt;

&lt;p&gt;If you are building data migration tools, event processors, or high-throughput write services in .NET, I’d love for you to take it for a spin. Feedback, issues, and PRs are welcome!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
