Turning Row Bytes Into a Table Size You Can Plan Around
Databases answer in bytes. pg_total_relation_size('orders') returns a bare integer; information_schema.tables reports data_length and index_length in bytes; a storage-engine stats page gives you a nine- or twelve-digit number with no unit at all. Capacity planning, on the other hand, happens in gigabytes — the unit of the disk you have to provision, the volume you have to grow and the budget line someone has to approve.
GB = bytes ÷ 1 000 000 000. So a table reported as 42 500 000 000 bytes is 42 500 000 000 ÷ 1 000 000 000 = 42.5 GB.The useful skill is not the division itself but knowing which bytes the number contains. A relation size and a table size are different figures, and the gap between them is usually where a capacity forecast goes wrong.
Row width is never just the columns
Indexes are often the bigger half
Growth is a rate, not a snapshot
From a Byte Figure to a Provisioning Number
Pull the byte figure from the database
Ask for the raw integer rather than a pretty-printed string — the unformatted value is what you want to convert, and it avoids the server having already rounded to one decimal place for you.
Paste it into the bytes field
The gigabyte value appears as you type. Query output is often already grouped with spaces or uses a comma as the decimal mark; both are handled, and spaces are simply ignored.
Copy it into the capacity plan
The per-field copy button hands back the bare number, so the figure drops straight into a spreadsheet cell, a Terraform volume size or a ticket without stripping units first.
Reverse it to write a threshold
Swap the fields to go from gigabytes back to bytes when a monitoring rule or a quota needs the value in the unit the database exposes. Either dropdown is searchable, so terabytes are one keystroke away when a table outgrows GB.
Row Width × Row Count: What a Table Actually Costs
Multiply the average on-disk row width by the number of rows and you get heap bytes; divide by a billion for gigabytes. The figures below assume the width already includes the per-row header, and exclude indexes — add those separately.
| Typical table | Row width | Rows | Heap bytes | Size in GB |
|---|---|---|---|---|
| Event stream, narrow | 96 B | 250 000 000 | 24 000 000 000 | 24 GB |
| Session records | 128 B | 10 000 000 | 1 280 000 000 | 1.28 GB |
| Order lines | 256 B | 50 000 000 | 12 800 000 000 | 12.8 GB |
| User profiles | 512 B | 100 000 000 | 51 200 000 000 | 51.2 GB |
| Audit log with JSON | 1 024 B | 5 000 000 | 5 120 000 000 | 5.12 GB |
| Documents, wide rows | 2 048 B | 1 000 000 | 2 048 000 000 | 2.048 GB |
Two adjustments almost always apply. Dead tuples awaiting vacuum inflate the heap above the arithmetic figure, and any column whose value exceeds roughly two kilobytes is compressed and pushed out to a TOAST relation, which the plain table size does not include but the total relation size does.
Both fields live while you compare tables
Type in either box and the other follows immediately, so you can run down a list of relation sizes from one query without clearing and re-entering each time.
Twelve-digit values stay readable
Thousands are separated with a space and very large results switch to scientific notation, which makes miscounted digits in a raw byte figure obvious at a glance.
Follows a table past a terabyte
Both unit menus are searchable and list every storage unit, so the same page covers a small lookup table in kilobytes and a partitioned fact table in terabytes.
Production figures stay local
Everything is computed in the browser after the page loads, so sizes copied out of a production database are never sent anywhere.
Database Sizing Questions
Why is my table bigger than the column widths suggest?
Every row carries fixed overhead before your data starts. In PostgreSQL that is a 23-byte tuple header plus a 4-byte item pointer in the page, and column values are padded to their alignment boundary — a bigint after a bool may waste seven bytes. On a narrow table those additions can be a third of the row. Multiply the real width, not the nominal one, before dividing into gigabytes.
Should indexes be counted in the size I provision for?
Yes, and they are frequently underestimated. Each index stores its key columns plus a row pointer and its own page overhead, so a table with six indexes can spend more space on them than on the heap. Size the heap and the indexes separately, convert each to gigabytes, and provision the sum — then leave headroom for the temporary copy a reindex needs.
What exactly does the total relation size include?
It returns bytes for the heap, every index, the free-space and visibility maps, and any TOAST relation attached to the table. The plain table-size function covers the heap and its maps only. When a monitoring dashboard and a manual query disagree about a table, this is almost always why — one of them counted the indexes and the other did not.
How do oversized column values change the arithmetic?
A row must fit inside a page, so once a value grows past roughly two kilobytes the engine compresses it and, if that is not enough, moves it out of line into a side relation, leaving a small pointer in the row. The visible row width collapses while the real storage sits elsewhere. For a table of large text or JSON documents, a width-times-rows estimate can be wildly low unless you measure the side relation too.
How do I project next year's size from rows per day?
Multiply rows per day by the measured on-disk width to get bytes per day, convert that to gigabytes, then multiply by the retention window. At 2 000 000 rows a day and a 256-byte width you are adding 512 000 000 bytes daily — 0.512 GB, about 15.4 GB a month. Apply the index ratio you measured on the existing data on top, and check the result against the actual growth after a month rather than trusting the first estimate.
No comments yet. Be the first to comment!