<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0" -->

---
title: pgx.CollectRows: Nice APIs Don't Have To Be Slow | daily.dev
description: A deep dive into the performance characteristics of pgx's CollectRows and AppendRows convenience APIs for Go. The author benchmarks these APIs against a...
canonical: https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: pgx.CollectRows: Nice APIs Don't Have To Be Slow | daily.dev
og:description: A deep dive into the performance characteristics of pgx's CollectRows and AppendRows convenience APIs for Go. The author benchmarks these APIs against a...
og:url: https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0
og:image: https://api.daily.dev/og/posts/wFMGvGSg0.png
og:image:alt: pgx.CollectRows: Nice APIs Don't Have To Be Slow
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# pgx.CollectRows: Nice APIs Don't Have To Be Slow

**[Awesome Go](https://daily.dev/sources/awego)** · 14 min read · 3 upvotes · 0 comments

## Summary

A deep dive into the performance characteristics of pgx's CollectRows and AppendRows convenience APIs for Go. The author benchmarks these APIs against a hand-written loop, finds they are 1.8× slower and allocate 10× more memory, then explains why: repeated reflection-based struct field lookups, heap escapes of temporary values, and per-row allocation of scan target slices. The post covers patches submitted to pgx (landed in v5.6) that added caching, and introduces pgx-collect, a drop-in compatible library that redesigns the core abstraction from a per-row RowToFunc to a Scanner interface with separate Initialize and ScanRowInto methods. This allows per-query setup to run once, scan targets to be reused, and data to be written directly into the output slice — reducing overhead from 180% to near-baseline. Key lessons: benchmark convenience APIs, choose abstractions that match the concrete work, cache reflection metadata by type, and minimize heap allocations.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://zolstein.substack.com/p/pgxcollectrows-nice-apis-dont-have>

## Questions this post answers

### Why is pgx.CollectRows slower than manually scanning rows in Go?

Because RowToStructByName uses reflection to look up struct field mappings on every row and the RowToFunc pattern forces a heap allocation for each returned value that then gets copied into the slice. Profiling showed 19% of CPU time in field lookup and roughly 27% in allocation plus garbage collection, making AppendRows run about 1.8x slower and allocate 10x more memory than a hand-written loop.

_Anyone tuning database access layers in Go can track these performance write-ups on daily.dev before shipping to production._

### What performance improvement did pgx 5.6 add for RowToStructByName?

Version 5.6 introduced caching of the (type, columns) field mapping so reflection-based struct field lookup only happens once per unique query shape instead of on every row. This single change roughly halved the overhead of AppendRows compared to earlier pgx versions, though repeated allocations and cache lookup costs still remained.

_Go developers weighing a pgx upgrade can follow driver-level performance changes like this on daily.dev._

### How does pgx-collect reduce overhead compared to standard pgx AppendRows?

It replaces the per-row RowToFunc callback with a query-scoped Scanner interface that separates one-time setup (Initialize) from per-row work (ScanRowInto), scans directly into a pointer to the slice element instead of returning a value that escapes to the heap, and reuses an allocated scanTargets slice across rows. Together these cut overhead versus baseline from 180% to 60%.

_Developers comparing Go database libraries for hot-path query performance can dig into trade-offs like this on daily.dev._

## Similar posts on daily.dev

- [Rebuilding Postgres for 300x faster analytics: batching, operator fusion, and SIMD](https://daily.dev/posts/rebuilding-postgres-for-300x-faster-analytics-batching-operator-fusion-and-simd-tgtpnmlyi) · Hacker News · 0 upvotes · 0 comments
- [The hidden cost of PostgreSQL arrays](https://daily.dev/posts/the-hidden-cost-of-postgresql-arrays-wcuzttsgw) · Lobsters · 2 upvotes · 0 comments
- [Go 1.26 iterators turned the simplest language into functional spaghetti](https://daily.dev/posts/go-1-26-iterators-turned-the-simplest-language-into-functional-spaghetti-wlsdlr14u) · Awesome Go · 5 upvotes · 0 comments
- [PgColumnar: The Postgres Analytics engine](https://daily.dev/posts/pgcolumnar-the-postgres-analytics-engine-ja78b3lbx) · Planet PostgreSQL · 3 upvotes · 1 comments
- [10X more data, same 4 seconds: single-query scaling in Redpanda SQL on 1TB](https://daily.dev/posts/10x-more-data-same-4-seconds-single-query-scaling-in-redpanda-sql-on-1tb-qj6rt0fu2) · Redpanda · 0 upvotes · 0 comments

---

Tags: [#performance](https://daily.dev/tags/performance), [#golang](https://daily.dev/tags/golang), [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"pgx.CollectRows: Nice APIs Don't Have To Be Slow","url":"https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0"},"datePublished":"2026-08-05T07:19:25.967Z","dateModified":"2026-09-13T19:55:33.289Z","description":"A deep dive into the performance characteristics of pgx's CollectRows and AppendRows convenience APIs for Go. The author benchmarks these APIs against a...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/3db349efe6928f23a0ce3458a463d7cd?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/3db349efe6928f23a0ce3458a463d7cd?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Awesome Go","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Awesome Go","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/awego","url":"https://daily.dev/sources/awego"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":3},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"performance,golang,postgresql","timeRequired":"PT14M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Awesome Go","item":"https://daily.dev/sources/awego"},{"@type":"ListItem","position":3,"name":"pgx.CollectRows: Nice APIs Don't Have To Be Slow"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/pgx-collectrows-nice-apis-don-t-have-to-be-slow-wfmgvgsg0#faq","mainEntity":[{"@type":"Question","name":"Why is pgx.CollectRows slower than manually scanning rows in Go?","acceptedAnswer":{"@type":"Answer","text":"Because RowToStructByName uses reflection to look up struct field mappings on every row and the RowToFunc pattern forces a heap allocation for each returned value that then gets copied into the slice. Profiling showed 19% of CPU time in field lookup and roughly 27% in allocation plus garbage collection, making AppendRows run about 1.8x slower and allocate 10x more memory than a hand-written loop. Anyone tuning database access layers in Go can track these performance write-ups on daily.dev before shipping to production."}},{"@type":"Question","name":"What performance improvement did pgx 5.6 add for RowToStructByName?","acceptedAnswer":{"@type":"Answer","text":"Version 5.6 introduced caching of the (type, columns) field mapping so reflection-based struct field lookup only happens once per unique query shape instead of on every row. This single change roughly halved the overhead of AppendRows compared to earlier pgx versions, though repeated allocations and cache lookup costs still remained. Go developers weighing a pgx upgrade can follow driver-level performance changes like this on daily.dev."}},{"@type":"Question","name":"How does pgx-collect reduce overhead compared to standard pgx AppendRows?","acceptedAnswer":{"@type":"Answer","text":"It replaces the per-row RowToFunc callback with a query-scoped Scanner interface that separates one-time setup (Initialize) from per-row work (ScanRowInto), scans directly into a pointer to the slice element instead of returning a value that escapes to the heap, and reuses an allocated scanTargets slice across rows. Together these cut overhead versus baseline from 180% to 60%. Developers comparing Go database libraries for hot-path query performance can dig into trade-offs like this on daily.dev."}}]}
```

