<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Postgresql on Flavio Silva</title><link>https://fsilva.me/tags/postgresql/</link><description>Recent content in Postgresql on Flavio Silva</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>flavio1110@gmail.com (Flavio Silva)</managingEditor><webMaster>flavio1110@gmail.com (Flavio Silva)</webMaster><lastBuildDate>Tue, 14 Jul 2026 14:00:00 +0100</lastBuildDate><atom:link href="https://fsilva.me/tags/postgresql/feed.xml" rel="self" type="application/rss+xml"/><item><title>🤖 Building a propose-then-approve bot engine</title><link>https://fsilva.me/posts/the-autonomous-bot-engine/</link><pubDate>Tue, 14 Jul 2026 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/the-autonomous-bot-engine/</guid><description>&lt;img src="https://fsilva.me/posts/the-autonomous-bot-engine/autonomous-bot-engine.png" alt="Featured image of post 🤖 Building a propose-then-approve bot engine" /&gt;&lt;p&gt;After every race weekend on &lt;a class="link" href="https://apexclub.live" target="_blank" rel="noopener"
 &gt;apexclub.live&lt;/a&gt; I used to do the same three things by hand. Enter the starting grid, enter the final classifications, trigger the scoring calculation. Every step was a few clicks and a few minutes. The annoying part wasn&amp;rsquo;t the time. It was the &lt;em&gt;kind&lt;/em&gt; of work. It&amp;rsquo;s detectable, it&amp;rsquo;s repetitive, and getting it wrong silently corrupts a season&amp;rsquo;s standings. So I wanted it gone, but only on my terms.&lt;/p&gt;
&lt;p&gt;I wanted something autonomous enough that I didn&amp;rsquo;t have to remember. Deterministic enough that I could trust it. Small enough that I could still explain every line.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;TL;DR: A recurring worker tick runs a cheap &lt;code&gt;Detect&lt;/code&gt; query for each registered use case. If there&amp;rsquo;s nothing to do, no LLM runs. If there&amp;rsquo;s a candidate, the engine builds a proposal, persists it, sends me a Telegram message with Approve / Reject buttons, and only on my tap calls a typed executor to apply the change. The LLM proposes. The database and the human approve. The executor writes.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="the-shape-of-the-problem"&gt;The shape of the problem
&lt;/h2&gt;&lt;p&gt;The original setup wasn&amp;rsquo;t bad. Race results came from analytics on Monday. The application had a Telegram bot I already trusted for one-off commands. I had a database I could read and write to. The only thing missing was glue.&lt;/p&gt;
&lt;p&gt;A naive first attempt was to let the existing chat bot propose mutations during a conversation. Press a button, the model picks a tool, the tool runs. I tried it and closed it. The problem wasn&amp;rsquo;t Telegram. It was mixing recurring operations with a conversational tool loop. Most ticks have nothing to do: the race hasn&amp;rsquo;t finished, analytics hasn&amp;rsquo;t synced, results already exist, scores are already published. Paying for an LLM call to discover that &amp;ldquo;nothing is ready&amp;rdquo; is wasteful, and worse, &amp;ldquo;the model decided to call this tool&amp;rdquo; is not an audit record I want to publish against a league&amp;rsquo;s standings.&lt;/p&gt;
&lt;p&gt;I wanted a concrete payload, a visible proposal, a named human decision, and a deterministic function that could be retried safely. So I split the work in five pieces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;detect&lt;/strong&gt; step that is just SQL, cheap and easy to reason about;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;propose&lt;/strong&gt; step that builds a structured payload and a human-readable summary;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;persist&lt;/strong&gt; step that stores the proposal before anything is sent anywhere;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;human decision&lt;/strong&gt; delivered through a channel I already trust (Telegram, in my case);&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;typed executor&lt;/strong&gt; that applies an approved proposal and is required to be idempotent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each piece is small. The interesting bit is the contract between them.&lt;/p&gt;
&lt;h2 id="the-contract"&gt;The contract
&lt;/h2&gt;&lt;p&gt;A use case is anything that can detect a candidate, propose a payload, and apply it. The shape, in Go, is roughly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UseCase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;Candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Propose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Proposal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The four methods divide responsibility cleanly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Detect&lt;/code&gt; is a cheap precondition, usually a single &lt;code&gt;SELECT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Propose&lt;/code&gt; turns one candidate into a structured payload and an admin-facing summary.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Execute&lt;/code&gt; applies an approved action. Idempotency is required.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Kind&lt;/code&gt; is the dispatch and dedup key.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Schedule&lt;/code&gt; lives on the use case, not in central wiring.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The engine has three pieces that move a use case through that contract:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;runner&lt;/strong&gt; that ticks on schedule, calls &lt;code&gt;Detect&lt;/code&gt;, and for each candidate checks whether an action already exists for the same &lt;code&gt;kind&lt;/code&gt; + subject reference. If not, it asks &lt;code&gt;Propose&lt;/code&gt; to build a proposal and hands it to the coordinator.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;coordinator&lt;/strong&gt; that owns persistence and decisions. It stores the proposal, sends the notification, atomically claims an approval, dispatches the executor, and records the outcome.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;registry&lt;/strong&gt; that maps a kind such as &lt;code&gt;insert_race_results&lt;/code&gt; to its executor. Registering the same kind twice panics at startup, which is the right moment to discover a wiring mistake.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lifecycle of a persisted action is small enough to draw in text:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pending -&amp;gt; executing -&amp;gt; executed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; failed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pending -&amp;gt; rejected
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A partial unique index allows only one open action per &lt;code&gt;(kind, subject_ref)&lt;/code&gt;. That single index is what makes the runner safe to call every 30 seconds across multiple application instances, and what makes a second admin press of &amp;ldquo;Approve&amp;rdquo; a no-op instead of a duplicate write.&lt;/p&gt;
&lt;p&gt;The approval transport can be anything with two buttons. Telegram was the obvious choice because I was already living there for one-off commands. A web page could work just as well. It just has to call back into the same coordinator.&lt;/p&gt;
&lt;h2 id="the-flow-end-to-end"&gt;The flow, end to end
&lt;/h2&gt;&lt;p&gt;Here is the whole path of one piece of work, written the way the code actually runs it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;worker tick (every 30s)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; for each registered use case:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Detect() with SQL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; zero candidates: stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; candidate: is there already an action for (kind, subject)?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yes: stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; no: Propose(candidate) -&amp;gt; Proposal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Coordinator.Submit(proposal)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; persist action with status=pending
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; send approval message with action UID
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Telegram button press
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Coordinator.Approve(uid) (atomic: pending -&amp;gt; executing)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Registry.Dispatch(kind).Execute(action)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; mark action executed or failed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The decision to put deterministic work first is the entire insight. The LLM only runs after SQL has already said &amp;ldquo;there is something to do here&amp;rdquo;, and only to convert that &amp;ldquo;something&amp;rdquo; into a payload a human can review.&lt;/p&gt;
&lt;h2 id="why-this-set-of-qualities"&gt;Why this set of qualities
&lt;/h2&gt;&lt;p&gt;A few properties fall out of the shape, not from extra effort.&lt;/p&gt;
&lt;p&gt;When nothing needs doing, the worker runs only &lt;code&gt;SELECT&lt;/code&gt; queries. No model call, no proposal, no notification. I called that &amp;ldquo;cheap idle ticks&amp;rdquo; above, and I underestimated how much it mattered before I built it. Most ticks &lt;em&gt;are&lt;/em&gt; idle. Saving a model call on every idle tick is the difference between a system I forget exists and a system I worry about.&lt;/p&gt;
&lt;p&gt;Auditability is mostly free. Every action is a row: &lt;code&gt;kind&lt;/code&gt;, subject reference, JSONB payload, the human-readable summary, the Telegram message identifiers, who decided it, when, and the final outcome. That&amp;rsquo;s a complete answer to &amp;ldquo;why did the standings change on Sunday at 23:14?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The executor contract is &lt;code&gt;Execute(ctx, *Action)&lt;/code&gt;. Rerunning it with the same action must converge to the same outcome. That requirement is what makes a button-mash, a network retry, or a panic mid-flight all safe. The row in &lt;code&gt;pending&lt;/code&gt; / &lt;code&gt;executing&lt;/code&gt; is the source of truth, and the executor decides whether to insert, update, or do nothing.&lt;/p&gt;
&lt;p&gt;There is no global &amp;ldquo;AI: on&amp;rdquo; switch. The system is on whenever the worker is running with a configured approval channel. The absence of an admin to approve is the absence of writes.&lt;/p&gt;
&lt;p&gt;Failure modes are independent. Each use case has its own schedule and its own advisory lock. A broken results matcher doesn&amp;rsquo;t prevent scores from being checked.&lt;/p&gt;
&lt;h2 id="two-use-cases-end-to-end"&gt;Two use cases, end to end
&lt;/h2&gt;&lt;p&gt;The same engine runs two unrelated capabilities on apexclub.live. They&amp;rsquo;re not hardcoded into the engine. They&amp;rsquo;re ordinary use cases that happen to be the first ones I needed.&lt;/p&gt;
&lt;h3 id="insert-race-results"&gt;Insert race results
&lt;/h3&gt;&lt;p&gt;The use case owns kind &lt;code&gt;insert_race_results&lt;/code&gt; and schedule &lt;code&gt;*/30 * * * *&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Its &lt;code&gt;Detect&lt;/code&gt; query is &amp;ldquo;active-season races in the past with no row in &lt;code&gt;results&lt;/code&gt; yet&amp;rdquo;. When that returns nothing, no LLM runs.&lt;/p&gt;
&lt;p&gt;When it returns a candidate, the use case:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;matches the main race to a row in an analytics calendar (the LLM only picks from offered candidates, it can&amp;rsquo;t author the mapping);&lt;/li&gt;
&lt;li&gt;pulls qualifying pole and classified finishers from the analytics tables;&lt;/li&gt;
&lt;li&gt;resolves driver names to the application&amp;rsquo;s own grid entries (again, the LLM only picks from offered IDs);&lt;/li&gt;
&lt;li&gt;assembles a structured result plus the P1–P22 finishing order;&lt;/li&gt;
&lt;li&gt;refuses incomplete mappings and surfaces them as a manual-entry notice instead of a proposal;&lt;/li&gt;
&lt;li&gt;persists the payload for approval.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On approval, &lt;code&gt;Execute&lt;/code&gt; checks for an existing result before inserting and only writes final positions when none are already recorded. A retry after a partial failure converges without overwriting manual data.&lt;/p&gt;
&lt;h3 id="publish-scores"&gt;Publish scores
&lt;/h3&gt;&lt;p&gt;The second use case owns kind &lt;code&gt;publish_scores&lt;/code&gt;, also scheduled every 30 minutes. Its &lt;code&gt;Detect&lt;/code&gt; query is &amp;ldquo;an active-season race with a results row, not yet finalized, no scores row, and at least one real starting position recorded&amp;rdquo;. Same shape, a &lt;code&gt;SELECT&lt;/code&gt; that asks whether the next step&amp;rsquo;s preconditions are met.&lt;/p&gt;
&lt;p&gt;The proposal payload is just a race identifier. On approval, the executor runs the existing scoring code and flips a finalized flag. Re-running the executor upserts and sets the same boolean, which converges safely.&lt;/p&gt;
&lt;p&gt;There is no direct call between these two use cases. The database state is the hand-off: the results executor creates the data that makes scoring eligible on a later tick. Independent schedules, independent advisory locks, independent failure handling.&lt;/p&gt;
&lt;h2 id="the-traps-in-order"&gt;The traps, in order
&lt;/h2&gt;&lt;p&gt;Building the engine was less work than fixing the things I got wrong the first time. A few worth naming.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approval state can stick.&lt;/strong&gt; The first version moved an action from &lt;code&gt;pending&lt;/code&gt; to &lt;code&gt;executing&lt;/code&gt; before calling the executor, which prevented two button presses from racing. But if the executor panicked, the action stayed in &lt;code&gt;executing&lt;/code&gt;, and because &lt;code&gt;executing&lt;/code&gt; counted as open, the runner could never propose it again without a manual database update. The fix was to move the lifecycle into the coordinator: a panic is converted into an error, and &lt;code&gt;Approve&lt;/code&gt; is the only transition that marks an action &lt;code&gt;failed&lt;/code&gt;. Atomic claiming was only half the story. Every transition needs a recovery path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source identities are fuzzy.&lt;/strong&gt; The first attempt matched races by season and round number. Real data proved that wrong. For one British Grand Prix, the main calendar said round 11 while analytics said round 9, and analytics round 11 was a different race with no results yet. The LLM&amp;rsquo;s job in this system is to resolve exactly the kind of ambiguity that round numbers and slightly different race names cause. The authority stays narrow: the model picks from offered source rows, never authors them. Deterministic code then checks whether the chosen mapping is safe enough to propose.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approval message is part of the safety boundary.&lt;/strong&gt; A Telegram message that shows only the top three drivers is not a complete proposal if the scoring system needs the full finishing order. Anything required for approval belongs in deterministic presentation, not in prose the model is free to shorten. The pattern that worked: build the visible fields from the assembled payload in code, and append the model-generated summary as commentary. The fields are guaranteed. The commentary is colour.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preconditions encode data completeness.&lt;/strong&gt; Scoring requires both a result and a starting grid. A result without a grid produces a plausible-looking but silently-wrong score. The &lt;code&gt;Detect&lt;/code&gt; query for scoring was the right place to encode that requirement. The engine should refuse a valid-looking operation when its inputs would produce an incorrect result.&lt;/p&gt;
&lt;p&gt;These weren&amp;rsquo;t architectural mistakes. They were the price of admitting the second iteration. The contract didn&amp;rsquo;t have to change. The implementations got better at honouring it.&lt;/p&gt;
&lt;h2 id="tradeoffs-and-limits"&gt;Tradeoffs and limits
&lt;/h2&gt;&lt;p&gt;A few honest costs.&lt;/p&gt;
&lt;p&gt;Latency is human-shaped. A use case that detects at 30 seconds and a human who approves five minutes later means a five-minute gap between readiness and execution. For race weekend admin that&amp;rsquo;s fine. For anything time-sensitive, propose-then-approve is the wrong shape.&lt;/p&gt;
&lt;p&gt;Approval delivery is one channel. Today the only way to approve is Telegram. If the notification fails to send, the action is persisted in &lt;code&gt;pending&lt;/code&gt; and stays there until a human notices or a redelivery job is added. Persistence beats loss, but it&amp;rsquo;s not the same as a guaranteed delivery.&lt;/p&gt;
&lt;p&gt;One primary approver. Notifications go to the first ID in the allowlist. Every allowlisted user can press the buttons, but only one gets the message. A real multi-admin workflow needs a delivery and decision policy, not just an allowlist.&lt;/p&gt;
&lt;p&gt;Rejected actions don&amp;rsquo;t auto-retry. The repository layer suppresses future proposals for a rejected &lt;code&gt;(kind, subject_ref)&lt;/code&gt; to avoid nagging. There is no &amp;ldquo;retry this one&amp;rdquo; operation that doesn&amp;rsquo;t also require a policy or data change. For one-shot work that&amp;rsquo;s the right default. For evolving inputs it can feel stubborn.&lt;/p&gt;
&lt;p&gt;Fuzzy matching is bounded. The model can only pick from offered candidates. That&amp;rsquo;s a strong guardrail and also a real ceiling. When the input source gets a new spelling or a new driver, the matcher will refuse rather than guess. Human review remains mandatory.&lt;/p&gt;
&lt;h2 id="where-id-take-it-next"&gt;Where I&amp;rsquo;d take it next
&lt;/h2&gt;&lt;p&gt;The next changes I&amp;rsquo;d make are unglamorous:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A redelivery job for &lt;code&gt;pending&lt;/code&gt; actions whose Telegram message failed.&lt;/li&gt;
&lt;li&gt;A web approval page that calls the same coordinator. It doesn&amp;rsquo;t need to replace Telegram. It just needs to be a second transport.&lt;/li&gt;
&lt;li&gt;An explicit retry / reopen transition for rejected subjects, gated by policy rather than by data manipulation.&lt;/li&gt;
&lt;li&gt;More use cases. The shape proved out on results and scoring. The next two I&amp;rsquo;m eyeing are joker-round announcements and end-of-season standings exports. Both are detectable, both are one-shot, both have a payload I want to review before they go out.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The thing I would &lt;em&gt;not&lt;/em&gt; change is the order. Cheap detect, deterministic propose, human approve, typed execute. Each piece is replaceable. The order is the engine.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The engine is small because it refuses to solve every agent problem. It works for recurring, detectable, one-shot changes with a payload that can be reviewed before it&amp;rsquo;s applied. &amp;ldquo;Autonomous&amp;rdquo; here means the system notices and prepares work by itself. Authority to change league results still belongs to a person.&lt;/p&gt;
&lt;p&gt;What kind of recurring work would you trust it to propose, but not to approve?&lt;/p&gt;</description></item><item><title>🐔 What happened with my project on the Rinha de Backend challenge</title><link>https://fsilva.me/posts/rinha-de-backend/</link><pubDate>Mon, 28 Aug 2023 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/rinha-de-backend/</guid><description>&lt;img src="https://fsilva.me/posts/rinha-de-backend/rooster_fight.png" alt="Featured image of post 🐔 What happened with my project on the Rinha de Backend challenge" /&gt;&lt;p&gt;I participated in the &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3" target="_blank" rel="noopener"
 &gt;Rinha de backend (pt-BR)&lt;/a&gt; or &lt;a class="link" href="https://en.wikipedia.org/wiki/Cockfight" target="_blank" rel="noopener"
 &gt;&amp;ldquo;Backend Rooster Fight&amp;rdquo; in English&lt;/a&gt; challenge and I was super excited to see the results of my super hacky and performant solution.&lt;/p&gt;
&lt;p&gt;The results were scheduled to be published at 21:00 BRT (2 AM CEST my local time). So I stayed up til later that night grabbed popcorn, and waited for the results.&lt;/p&gt;
&lt;p&gt;Well, I wish I hadn&amp;rsquo;t waited&amp;hellip;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;TL;DR; My solution didn&amp;rsquo;t run on the official test server because I built the docker image to &lt;code&gt;linux/arm64&lt;/code&gt; instead of &lt;code&gt;linux/amd64&lt;/code&gt; 🤦🤦🤦 . I fixed the issue and ran the same tests afterward and I reached ~50% of points of the winner, mainly because in my solution I didn&amp;rsquo;t tolerate any risk of losing writes. Checkout my repository &lt;a class="link" href="https://github.com/flavio1110/rinha-de-backend" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;flavio1110/rinha-de-backend&lt;/strong&gt;&lt;/a&gt; with my solution.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="what-was-the-rinha-de-backend"&gt;What was the Rinha de Backend?
&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Before we get into the details, a brief explanation of what was the challenge.&lt;/em&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;From July 28th to August 25th, the Backend Fight was held, a tournament in which the API that supported the most load during a stress test would be the winner. Participants had to implement an API with endpoints to create, query and search for &amp;lsquo;people&amp;rsquo; (a kind of CRUD without UPDATE and DELETE). In the tournament, participants still had to deal with CPU and memory restrictions – each participant had to deliver the API in docker-compose format and could only use 1.5 CPU units and 3GB of memory. More details on technical aspects can be found in the instructions) – &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/tree/main#o-que-%C3%A9" target="_blank" rel="noopener"
 &gt;(translated from its repository in English)&lt;/a&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;On top of the description above, some things important to highlight are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can write your API with any language or framework, as long as you can build a docker image out of it&lt;/li&gt;
&lt;li&gt;The solution has to run two instances of the API in parallel.&lt;/li&gt;
&lt;li&gt;The requests will be balanced as you wish between these instances via Nginx.&lt;/li&gt;
&lt;li&gt;You have to use one of the following data stores: MySQL, PostgreSQL, or MongoDB.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It was a super fun, informal, practical, and a very good opportunity to exercise and learn new things.&lt;/p&gt;
&lt;h4 id="how-the-winners-were-determined"&gt;How the winners were determined?
&lt;/h4&gt;&lt;p&gt;To make the comparison easier and fun, the &lt;strong&gt;single parameter&lt;/strong&gt; used to determine the winner of the challenge was the &lt;strong&gt;number of people inserted&lt;/strong&gt; into the database.&lt;/p&gt;
&lt;h2 id="how-did-i-do"&gt;How did I do?
&lt;/h2&gt;&lt;figure class="aligncenter"&gt;
 &lt;img src="https://fsilva.me/images/rinha-2023-q3-meme.jpeg" alt="my solution explodes" /&gt;
 &lt;figcaption&gt;POV you are looking at me while I check the results.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I didn&amp;rsquo;t. I was disqualified because the container with my APIs didn&amp;rsquo;t start. Therefore I got nothing. 🤦🤦🤦&lt;/p&gt;
&lt;h3 id="wrong-platform"&gt;Wrong platform?
&lt;/h3&gt;&lt;p&gt;Yes, that&amp;rsquo;s true. My solution didn&amp;rsquo;t even run because &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/blob/main/resultados/primeira-fase/flavio1110/docker-compose.logs#L63" target="_blank" rel="noopener"
 &gt;it failed to start the APIs&lt;/a&gt;. I built the images for &lt;code&gt;linux/arm64&lt;/code&gt; instead of &lt;code&gt;linux/amd64&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It was a mistake on my end, and it made me super disappointed. I ran the tests on the &lt;a class="link" href="https://github.com/flavio1110/rinha-de-backend/actions/runs/5949338191" target="_blank" rel="noopener"
 &gt;CI pipelines&lt;/a&gt;, and it gave me confidence the image was properly built and I didn&amp;rsquo;t double-check the requirements. Shame on me.&lt;/p&gt;
&lt;p&gt;Well, I was disqualified but I was curious to see how it would perform on a server with the &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/blob/main/misc/lshw-aws" target="_blank" rel="noopener"
 &gt;same configuration as the one used for the official tests&lt;/a&gt;. So, I fixed the platform of the image, spun up an EC2 with the same configuration, and finally ran the tests. The results were a bit disappointing compared to the TOP 10.&lt;/p&gt;
&lt;h3 id="how-did-i-approach-the-problem"&gt;How did I approach the problem?
&lt;/h3&gt;&lt;p&gt;I had two constraints in mind while designing and building:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It has to work – each endpoint has to do what it is supposed to do.&lt;/li&gt;
&lt;li&gt;Keep things as simple as possible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of these points brought a few tradeoffs that ended up limiting the overall performance of the solution.&lt;/p&gt;
&lt;p&gt;You can check my solution on &lt;a class="link" href="https://github.com/flavio1110/rinha-de-backend" target="_blank" rel="noopener"
 &gt;flavio1110/rinha-de-backend&lt;/a&gt;. It&amp;rsquo;s built with Go and PostgreSQL.&lt;/p&gt;
&lt;p&gt;Given many APIs were performing super well, a few days before the last day of the challenge, &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/commit/be637c944434bdddfe4a92a31bd17cd528f68a38" target="_blank" rel="noopener"
 &gt;the load for the stress test was doubled&lt;/a&gt;, and it had a massive impact on the performance of my API. You can check the comparison below:&lt;/p&gt;
&lt;p&gt;Before&amp;hellip;&lt;/p&gt;
&lt;figure class="aligncenter"&gt;
 &lt;a href="https://fsilva.me/rinha-de-backend/rinhabackendsimulation-20230821183911665/" target="_blank"&gt;&lt;img src="https://fsilva.me/images/gh_results_before.png" alt="rooster fight" /&gt;&lt;/a&gt;
 &lt;figcaption&gt;Print of part of the report generated by Gatling of my solution run on Github BEFORE the load was doubled.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After&amp;hellip;&lt;/p&gt;
&lt;figure class="aligncenter"&gt;
 &lt;a href="https://fsilva.me/rinha-de-backend/rinhabackendsimulation-20230823091057409/" target="_blank"&gt;&lt;img src="https://fsilva.me/images/gh_results_after.png" alt="rooster fight" /&gt;&lt;/a&gt;
 &lt;figcaption&gt;Print of part of the report generated by Gatling of my solution run on Github AFTER the load was doubled.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="what-were-the-bottlenecks"&gt;What were the bottlenecks?
&lt;/h3&gt;&lt;p&gt;The inserts were the bottleneck. Because I didn&amp;rsquo;t tolerate the risk of losing writes and wanted to keep it simple, I missed the opportunity to introduce a distributed mechanism to validate the data before trying to insert it into the database and perform the inserts in batch.&lt;/p&gt;
&lt;h4 id="validating-the-uniqueness-of-a-field-apelido-nickname"&gt;Validating the uniqueness of a field &lt;em&gt;Apelido&lt;/em&gt; (Nickname)
&lt;/h4&gt;&lt;p&gt;On my designed solution, without a distributed cache, it was not possible to perform without the DB in 100% of the cases.&lt;/p&gt;
&lt;p&gt;I had an in-memory cache, but if the nickname wasn&amp;rsquo;t there because it wasn&amp;rsquo;t there because the entry was inserted via the other instance, that request would reach the DB, occupy a connection, take resources, etc.&lt;/p&gt;
&lt;p&gt;Introducing a distributed cache like &lt;a class="link" href="https://redis.uptrace.dev/" target="_blank" rel="noopener"
 &gt;Redis&lt;/a&gt; would enable me to add and check the entry in a single place. As a result, we could decrease the memory necessary to run the APIs and move to the cache.&lt;/p&gt;
&lt;h4 id="batching-inserts"&gt;Batching inserts
&lt;/h4&gt;&lt;p&gt;I inserted each entry per a valid request because I didn&amp;rsquo;t want to risk losing valid writes in case there was any error between the construction of the batch and its execution.&lt;/p&gt;
&lt;p&gt;This was a huge problem because it used too many DB connections and resources, and slowed down the creation request.&lt;/p&gt;
&lt;h3 id="did-i-like-it"&gt;Did I like it?
&lt;/h3&gt;&lt;p&gt;Despite my results, I loved the challenge! I learned quite some tricks with nginx config and some insteresting stuff like using &lt;a class="link" href="https://redis.io/docs/interact/pubsub/" target="_blank" rel="noopener"
 &gt;Redis&lt;/a&gt; or even &lt;a class="link" href="https://www.postgresql.org/docs/current/sql-notify.html" target="_blank" rel="noopener"
 &gt;PostgreSQL&lt;/a&gt; as a &amp;ldquo;PubSub&amp;rdquo;.
The interaction with the community via &lt;a class="link" href="https://twitter.com/flavio1110" target="_blank" rel="noopener"
 &gt;Twitter&lt;/a&gt; and Github was super nice!
I also liked the fact of working with something challenging and closer to reality. Loved it!&lt;/p&gt;
&lt;h3 id="what-could-i-have-done-differently"&gt;What could I have done differently?
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Build the image using the correct platform.&lt;/li&gt;
&lt;li&gt;A challenge is a challenge. I should have been more flexible with the writing.&lt;/li&gt;
&lt;li&gt;Tweak the Nginx configuration to load balance based on fewer connections and disable logs.&lt;/li&gt;
&lt;li&gt;Use a distributed cache and drop the read table.&lt;/li&gt;
&lt;li&gt;Batch inserts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next?
&lt;/h2&gt;&lt;p&gt;Check the &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3#resultados" target="_blank" rel="noopener"
 &gt;official results&lt;/a&gt; and look into the repositories of the participants. I guarantee I&amp;rsquo;ll learn something new.&lt;/p&gt;
&lt;p&gt;Stay tuned on &lt;a class="link" href="https://twitter.com/rinhadebackend" target="_blank" rel="noopener"
 &gt;@rinhadebackend&lt;/a&gt; for the next challenges, I can&amp;rsquo;t wait for the next ones.&lt;/p&gt;
&lt;p&gt;Other than that, I&amp;rsquo;ll apply the lessons learned to my solution and hopefully get better results. Watch &lt;a class="link" href="https://github.com/flavio1110/" target="_blank" rel="noopener"
 &gt;flavio1110/rinha-de-backend&lt;/a&gt; and see how it will evolve.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This challenge is officially over, but you can still do it. Challenge accepted?&lt;/em&gt;&lt;/p&gt;</description></item><item><title>📂 Go: Importig a CSV to PostgreSQL</title><link>https://fsilva.me/posts/csv-import-go-postgresql/</link><pubDate>Sun, 26 Mar 2023 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/csv-import-go-postgresql/</guid><description>&lt;img src="https://fsilva.me/posts/csv-import-go-postgresql/csv-rainbow.webp" alt="Featured image of post 📂 Go: Importig a CSV to PostgreSQL" /&gt;&lt;p&gt;If you are using Go ad PostgreSQL, and need to performa a bulk import a CSV,
it&amp;rsquo;s most likely you will find the &lt;code&gt;COPY&lt;/code&gt; protocol is the &lt;a class="link" href="https://www.postgresql.org/docs/current/sql-copy.html" target="_blank" rel="noopener"
 &gt;feature&lt;/a&gt; that suits you better.
In that direction, you will find examples using &lt;a class="link" href="https://github.com/jackc/pgx" target="_blank" rel="noopener"
 &gt;pgx&lt;/a&gt; &lt;a class="link" href="https://pkg.go.dev/github.com/jackc/pgx/v4#Conn.CopyFrom" target="_blank" rel="noopener"
 &gt;CopyFrom&lt;/a&gt; that relies on the native protocol, and it&amp;rsquo;s fairly easy to use.
&lt;strong&gt;However, depending how it&amp;rsquo;s used you can have an exponencial increase of memory consumption of your application making it unreliable and more expensive to run.&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;TL;DR;: &lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt; load the file in memory and use &lt;code&gt;pgx.CopyFromRows&lt;/code&gt;. Instead, use the &lt;code&gt;io.Reader&lt;/code&gt; of the file and implement a custom &lt;code&gt;pgx.CopyFromSource&lt;/code&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;a class="link" href="https://github.com/flavio1110/large-csv-to-pgsql" target="_blank" rel="noopener"
 &gt;Checkout the repository with examples and details presented here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="context"&gt;Context
&lt;/h3&gt;&lt;p&gt;Most of the examples out there, are either using &lt;a class="link" href="https://pkg.go.dev/github.com/jackc/pgx/v4#CopyFromRows" target="_blank" rel="noopener"
 &gt;CopyFromRows&lt;/a&gt; or &lt;a class="link" href="https://pkg.go.dev/github.com/jackc/pgx/v4#CopyFromSlice" target="_blank" rel="noopener"
 &gt;CopyFromSlice&lt;/a&gt;. However, the big problem is these two options require you to have the entire content in memory to use.
This is not a big deal when dealing with small files, there won&amp;rsquo;t be concurrent usage, or you have infinite memory 😅.&lt;/p&gt;
&lt;h3 id="how-big-is-the-problem"&gt;How big is the problem?
&lt;/h3&gt;&lt;p&gt;Comparing the memory consumption for the two distinct approaches importing a file with ~16MB (1M rows).&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Approach/metric&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;TotalAlloc&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Sys&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Stream file&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;61 MiB&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12 Mib&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Read entire file&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;84 MiB&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;58 Mib&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;&lt;strong&gt;+37.70%&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;&lt;strong&gt;+346.15%&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Yes,&lt;/strong&gt; you read it right! using &lt;code&gt;CopyFromRows&lt;/code&gt; obtained +346.15% of memory from the OS! 58 MiB instead of 12 MiB.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You can read more about the meaning of each metric on &lt;a class="link" href="https://golang.org/pkg/runtime/#MemStats" target="_blank" rel="noopener"
 &gt;https://golang.org/pkg/runtime/#MemStats&lt;/a&gt; and find the source code and details of the comparisson on &lt;a class="link" href="https://github.com/flavio1110/large-csv-to-pgsql" target="_blank" rel="noopener"
 &gt;this repository&lt;/a&gt;..&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="whats-the-most-efficient-way-for-using-the-copy-protocol-with-pgx"&gt;What&amp;rsquo;s the most efficient way for using the COPY protocol with pgx?
&lt;/h3&gt;&lt;p&gt;Instead of reading the entire in memory, the idea is to stream each line of the file directly to PostgreSQL. In this way, we only need to keep the current line in-memory as opposed to the entire file.&lt;/p&gt;
&lt;h4 id="how-can-we-do-it"&gt;How can we do it?
&lt;/h4&gt;&lt;p&gt;The &lt;a class="link" href="https://github.com/jackc/pgx/blob/master/copy_from.go#LL238C21-L238C21" target="_blank" rel="noopener"
 &gt;CopyFrom&lt;/a&gt; method receives an implementation of the interface &lt;a class="link" href="https://github.com/jackc/pgx/blob/master/copy_from.go#L68" target="_blank" rel="noopener"
 &gt;CopyFromSource&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s implement this interface using a CSV file with the followng three columns: first_name, last_name, and city.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&lt;/span&gt;&lt;span class="lnt"&gt;58
&lt;/span&gt;&lt;span class="lnt"&gt;59
&lt;/span&gt;&lt;span class="lnt"&gt;60
&lt;/span&gt;&lt;span class="lnt"&gt;61
&lt;/span&gt;&lt;span class="lnt"&gt;62
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newPeopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReuseRecord&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="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// reuse slice to return the record line by line&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FieldsPerRecord&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="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isBOF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// first line is header&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;peopleColumns&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isEOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isBOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEOF&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the order of the elements of the record array, must match with&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the order of the columns in passed into the copy method&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&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="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// when get to the end of the file return false and clean the error.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If it&amp;#39;s io.EOF we can&amp;#39;t return an error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="p"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEOF&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="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&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="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBOF&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBOF&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="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can now use this implementation in the &lt;code&gt;CopyFrom&lt;/code&gt; method. e.g.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pgxConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pgx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identifier&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;people&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;peopleColumns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newPeopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvStream&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="conclusion"&gt;Conclusion
&lt;/h3&gt;&lt;p&gt;Using the &lt;code&gt;CopyFrom&lt;/code&gt; with &lt;code&gt;CopyFromRows&lt;/code&gt; or &lt;code&gt;CopyFrom&lt;/code&gt; will significantly increase the memory comsultion of your application. The high memory usage can bring several problems like OOM errors, increase of costs, unavailability, etc.&lt;/p&gt;
&lt;p&gt;By using a custom implementation of &lt;a class="link" href="https://github.com/jackc/pgx/blob/master/copy_from.go#L68" target="_blank" rel="noopener"
 &gt;CopyFromSource&lt;/a&gt; will make your application much more efficient, reliable, and cheaper to ru.&lt;/p&gt;
&lt;p&gt;You can find the entire source code of the examples above on &lt;a class="link" href="https://github.com/flavio1110/large-csv-to-pgsql" target="_blank" rel="noopener"
 &gt;this repository&lt;/a&gt;. There you will also find more deatils about the comparisson and the not-so-great implementation.&lt;/p&gt;</description></item></channel></rss>