PostgreSQL

PostgreSQL Data Alignment - Use Case

2026.02.24 · Platform Technology Research Team

Read the original in Korean
PostgreSQL Data Alignment - Use Case

PostgreSQL Data Alignment - Use Case

Author
Platform Technology Research Team
Date
Feb 24, 2026
Description
Reordering columns by pg_type alignment rules cut padding and shrank a 10-million-row PostgreSQL table by about 25%.
KO slug
postgresql-dataalignment-2
Series
PostgreSQL
Status
Published
Translation
AI draft
Following PostgreSQL Data Alignment - Introduction, this article walks through optimizing column positions for Data Alignment in practice and checks how storage usage changes as a result.
 

1. Optimizing Column Positions for Data Alignment

Optimizing column positions for data alignment aims to reduce unnecessary padding, minimizing wasted space and improving search performance. You can determine the optimal column positions using typalign and typlen in the pg_type catalog.
 

Column Ordering Rules

Columns are ordered by placing fixed-size data types first, followed by variable-size data types.
  • Ordering rules for fixed-size data types
A fixed-size data type is one where pg_type.typlen > 0.
  1. Place columns in order of larger pg_type.typlen, with pg_type.typalign in the order d → i → s → c. A negative pg_type.typlen means variable size, so those types are not part of this step.
  1. For fixed-size character columns such as the char data type (pg_type.typalign = c), place columns in order of size, largest first.
 
  • Ordering rules for variable-size data types
A variable-size data type is one where pg_type.typlen = -1.
  1. Place variable-size numeric columns such as numeric and decimal first.
  1. Then place variable-size character columns such as varchar.
  1. Then place long variable-size string columns such as text, bytea, jsonb and xml. (Their length varies the most, and they are the most likely to be stored in TOAST)
 
Summary of column ordering rules
Order
pg_type.typlen condition
pg_type.typalign condition
Detailed condition
Example data types
1
Positive
d
Place in order of larger pg.type.typlen
timetz, bigint, timestamp
2
Positive
i
Place in order of larger pg.type.typlen
macaddr8, regclass, int
3
Positive
s
Place in order of larger pg.type.typlen
tid, smallint
4
Positive
c
Place in order of larger pg.type.typlen
name, char(50), char(2), boolean
5
-1
i
Variable-size numeric data types
numeric, decimal
6
-1
i
Variable-size character data types
varchar
7
-1
i
Long variable-size string data types
text, bytea, jsonb, xml
 

Variable-Length Data Types and TOAST Tables

Let's look at why variable-size data types are placed after fixed-size data types.
Variable-size string data types have a data header separate from the tuple header. The data header adds 1Byte when the stored data is up to 126 long, and 4Byte from 127 onward.
Also, when a tuple is large (usually 2KB), column data of that data type is compressed depending on typlen=-1 and typstorage=x, and if the tuple is still large after compression, it is stored in a TOAST table. When data is stored in a TOAST table, the actual table stores information pointing to TOAST.
As a result, variable-size data types are likely to be stored in TOAST once they exceed a certain size, so placing them after fixed-size data types lets the layout absorb the variation in length.
 

2. A Real Example

So far we have seen that tuple size changes depending on column order by data type. In this section, we compare table sizes with a large amount of data, with and without considering column order.
First, create a table structured as follows.
From the CREATE TABLE statement, we can estimate that each tuple needs 36Byte, excluding the 24Byte tuple header and including padding for data alignment. Let's load data and check whether it actually needs 36Byte.
notion image
Insert 10 million rows.
To check the size each tuple needs, we use the pageinspect extension to see how the data is actually stored.
The default_col table is stored at 36Byte per tuple, excluding the 24Byte tuple header. The t_data result breaks down as shown below. This matches the tuple size we estimated from the CREATE TABLE statement.
notion image
To shrink the table by minimizing unnecessary padding from data alignment, check the alignment information of the default_col table.
Recreate the default_col table as the ordering_col table following the column ordering rules in 1. Optimizing Column Positions for Data Alignment.
From the CREATE TABLE statement, we can estimate that each tuple needs 22Byte, excluding the 24Byte tuple header. The ordering_col table, recreated following the column ordering rules, needs no padding. Let's load data and check whether it actually needs 22Byte.
notion image
Insert the same 10 million rows that were loaded into the default_col table.
To check the size each tuple needs, we use the pageinspect extension to see how the data is actually stored.
The ordering_col table is stored at 22Byte per tuple, excluding the 24Byte tuple header. The t_data result breaks down as shown below. This matches the tuple size we estimated from the CREATE TABLE statement.
notion image
 
To compare table sizes as tuple size changes, check the sizes of the table without column ordering (default_col) and the table with column ordering (ordering_col).
Comparing table sizes after loading 10 million rows, we can see that the ordering_col table, with its columns reordered, uses 153MB less (about 25% smaller) than the default_col table.
 

3. Benefits of Changing Column Order

  • Lower storage usage : Reordering columns by data alignment rules minimizes the unnecessary padding used for alignment, which reduces storage usage. In the example above, a 615MB table dropped to 498MB, about 25% less, after its columns were reordered by data alignment rules.
  • Better use of TOAST : Placing long variable-size data such as text, jsonb and xml last lets it be stored in TOAST quickly.
  • Lower I/O cost : For the same reason storage usage drops, each page holds more row data, which lowers I/O cost and improves query performance. In other words, more row data can be cached in fewer pages. In the EXPLAIN output below, the default_col table incurs I/O on 83,334 pages, while the ordering_col table incurs I/O on 63,695 pages.
 
 

 
 
Related articles