Published · 5d agoBuild2 min read
Negative Bytes Scanned: Why a 149 GB Query Reports -1.5 GB
Snowflake QUERY_HISTORY unloaded to Parquet can write large byte counts as INT32. Wraparound pins any corrupted value within about 2.1 GB of zero, so the sign is the only signal left.
Written for builders.See today for builders

What happened
- Unloading Snowflake QUERY_HISTORY data to Parquet can corrupt values when large numbers are written as INT32; casting the nine affected columns before export prevents overflow, bad joins, and incorrect analysis.
- By default Snowflake optimizes table columns in unloaded Parquet data files by setting the smallest precision that accepts all of the values; setting the ENABLE_UNLOAD_PHYSICAL_TYPE_OPTIMIZATION session parameter to FALSE yields a consistent output file schema determined by the logical column data types in the unload query or source table.
- A signed 32-bit two's complement integer can represent values from -2,147,483,648 to 2,147,483,647.
- A 149 GB (gibibyte) byte count is 159,987,531,776 bytes, about 74.5 times the maximum value a signed 32-bit integer can hold.
- When a byte count B is truncated into a signed 32-bit field, the stored value is ((B + 2^31) mod 2^32) - 2^31, so any value whose remainder modulo 4,294,967,296 exceeds 2,147,483,647 is reported as negative.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A telemetry row that reports -1.5 GB scanned for a query that read about 149 GB is not measuring anything; it is a 32-bit integer that ran out of room. According to the tldr.tech data roundup, unloading Snowflake QUERY_HISTORY to Parquet can corrupt values when large numbers are written as INT32, and casting the nine affected columns before export prevents overflow, bad joins, and incorrect analysis [1].
The arithmetic is unglamorous. A signed 32-bit integer stops at 2,147,483,647 [4]. A 149 GB scan is 159,987,531,776 bytes, roughly 74 and a half times that ceiling [5]. What survives truncation is the true value modulo 4,294,967,296, shifted back into signed range, so any remainder above 2^31-1 reads as a negative number [6].
That produces the constraint operators should internalise: an overflowed byte count can never sit further than about 2.1 GB from zero, no matter how much the query actually read [7]. The magnitude carries no information about scale. Sums over the column are noise, ordering by it is meaningless, and the same truncation breaks joins that use those values [1].
As for why INT32 gets chosen, Snowflake's own unload documentation says that by default it optimizes columns in unloaded Parquet files to the smallest precision that accepts all the values, and that setting ENABLE_UNLOAD_PHYSICAL_TYPE_OPTIMIZATION to FALSE produces an output schema determined by the logical column types instead [2]. So there are two levers: explicit casts on the nine columns [1], or pin the schema and stop the optimizer guessing [2].
None of this touches the invoice. Snowflake charges credits based on how many warehouses run, how long, and at what size, billed per second with a one-minute minimum [8]. Bytes scanned is a diagnostic, not a billing unit. What corrupts is everything built on top: chargeback models that attribute cost through object tags and query tags [9], and the ranking of which queries to fix first.
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Unloading Snowflake QUERY_HISTORY data to Parquet can corrupt values when large numbers are written as INT32; casting the nine affected columns before export prevents overflow, bad joins, and incorrect analysis.
- [2]
By default Snowflake optimizes table columns in unloaded Parquet data files by setting the smallest precision that accepts all of the values; setting the ENABLE_UNLOAD_PHYSICAL_TYPE_OPTIMIZATION session parameter to FALSE yields a consistent output file schema determined by the logical column data types in the unload query or source table.
ReportedSource: Snowflake documentation, loading and unloading Parquet data tutorialView cited source - [8]
Snowflake credits are charged based on the number of virtual warehouses used, how long they run, and their size; credits are billed per second with a 60-second minimum, and suspended warehouses use no credits.
- [9]
Snowflake's recommended cost attribution approach is to use object tags to associate resources and users with departments or projects, and query tags to associate individual queries with departments or projects when one application queries on behalf of users from multiple departments.
- [4]
A signed 32-bit two's complement integer can represent values from -2,147,483,648 to 2,147,483,647.
Derived - [5]
A 149 GB (gibibyte) byte count is 159,987,531,776 bytes, about 74.5 times the maximum value a signed 32-bit integer can hold.
Derived
Sources & coverage · 2 publishers
The reporting this story was synthesized from, earliest first. Every link goes to the original.
Cited in this coverage: tldr.tech data newsletter, 2026-08-17
- docs.snowflake.com5d agoData unloading considerations | Snowflake Documentation
- docs.snowflake.com5d agoTutorial: Loading and unloading Parquet data | Snowflake Documentation

