Debugging PostgreSQL source code can be difficult when you are trying to understand the internal execution flow of queries. PostgreSQL 19 introduces a developer-focused configuration parameter named backtrace_functions that helps developers generate stack backtraces whenever specific functions trigger an error or log message.
This feature is especially useful for PostgreSQL contributors, extension developers, database kernel developers, and researchers exploring PostgreSQL internals.
We will explore how to use backtrace_functions in PostgreSQL 19 with a practical example using the heap_insert() function.
What are backtrace_functions in PostgreSQL?
backtrace_functions is a PostgreSQL developer configuration parameter that logs a complete execution backtrace whenever an error occurs inside selected functions.
It helps developers:
- Track internal PostgreSQL execution flow
- Debug source code execution paths
- Identify which functions are calling other functions
- Analyze executor workflows
- Understand backend processing during SQL execution
This parameter belongs to PostgreSQL developer options.
Checking the backtrace_functions Parameter
Inside PostgreSQL, you can check the parameter using:
SHOW backtrace_functions;
Example output:
postgres=# show backtrace_functions ;
backtrace_functions
---------------------
(1 row)
You can also inspect the parameter details from pg_settings.
SELECT * FROM pg_settings WHERE name = 'backtrace_functions';
Expanded output:
-[ RECORD 1 ]---+---------------------------------------------
name | backtrace_functions
setting |
unit |
category | Developer Options
short_desc | Log backtrace for errors in these functions.
extra_desc |
context | superuser
vartype | string
source | default
min_val |
max_val |
enumvals |
boot_val |
reset_val |
sourcefile |
sourceline |
pending_restart | f
You can identify the PostgreSQL configuration file location using:
SHOW config_file;
Example:
postgres=# show config_file ;
-[ RECORD 1 ]----------------------------------------
config_file | /etc/postgresql/18/main/postgresql.conf
Now open the postgres configuration file and check this parameter named backtrace_functions
cat /etc/postgresql/18/main/postgresql.conf | grep backtrace_functions
We can see that there is no parameter named backtrace_functions.But you can inspect this parameter inside postgres source code.
Internal Definition of backtrace_functions
If you inspect PostgreSQL source code version 19 file named guc_parameters.dat, you can find the internal definition of this parameter
{ name => 'backtrace_functions', type => 'string', context => 'PGC_SUSET', group => 'DEVELOPER_OPTIONS',
short_desc => 'Log backtrace for errors in these functions.',
flags => 'GUC_NOT_IN_SAMPLE',
variable => 'backtrace_functions',
boot_val => '""',
check_hook => 'check_backtrace_functions',
assign_hook => 'assign_backtrace_functions',
},This confirms that the parameter is designed specifically for developer-level debugging.
Compiling PostgreSQL 19 from Source
To experiment with backtrace_functions, compile PostgreSQL from source with debugging enabled.
Clone PostgreSQL:
git clone https://github.com/postgres/postgres.git
Move to the postgres directory
cd postgres
Configure the build:
./configure --prefix=$(pwd) --enable-cassert --enable-debug
Compile PostgreSQL:
make -j $(nproc)
Install PostgreSQL:
sudo make install
Initialize the database cluster:
bin/initdb -D pg_data
Start PostgreSQL on a custom port:
bin/pg_ctl -D pg_data -l logfile -o "-p 5440" start
Connect to PostgreSQL:
bin/psql postgres -p 5440
Adding a Custom Log Inside heap_insert
Now we will modify PostgreSQL source code to generate a custom log message inside the heap_insert() function.
Open the PostgreSQL source code and locate:
src/backend/access/heap/heapam.c
Inside the heap_insert() function, add the following code:
void
heap_insert(Relation relation, HeapTuple tup, CommandId cid,
int options, BulkInsertState bistate)
{
ereport(LOG,
(errmsg("my custom error")));
This log will intentionally trigger a message whenever heap_insert() executes.
Rebuilding PostgreSQL After Source Code Changes
After modifying the source code, rebuild PostgreSQL:
make
Install the updated binaries:
sudo make install
Restart PostgreSQL:
bin/pg_ctl -D pg_data -l logfile -o "-p 5440" restart
Reconnect to PostgreSQL:
bin/psql postgres -p 5440
Enabling backtrace_functions
Now enable backtrace logging for heap_insert.
SET backtrace_functions = 'heap_insert';
You can also enable multiple functions:
SET backtrace_functions = 'ExecInsert,heap_insert,ExecDelete';
Verify the configuration:
SHOW backtrace_functions;
Output:
backtrace_functions
-----------------------------------
ExecInsert,heap_insert,ExecDelete
(1 row)
Monitoring PostgreSQL Logs
Open another terminal and monitor the PostgreSQL log file:
tail -f logfile
Result :
cybrosys@cybrosys:~/postgres_19_source/postgres-master$ tail -f logfile
2026-06-03 12:13:49.436 IST [73675] LOG: background worker "logical replication launcher" (PID 73684) exited with exit code 1
2026-06-03 12:13:49.437 IST [73679] LOG: shutting down
2026-06-03 12:13:49.438 IST [73679] LOG: checkpoint starting: shutdown fast
2026-06-03 12:13:49.447 IST [73679] LOG: checkpoint complete: shutdown fast: wrote 0 buffers (0.0%), wrote 2 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.003 s, sync=0.001 s, total=0.010 s; sync files=3, longest=0.001 s, average=0.001 s; distance=0 kB, estimate=0 kB; lsn=0/27077108, redo lsn=0/27077108
2026-06-03 12:13:49.456 IST [73675] LOG: database system is shut down
2026-06-03 12:13:49.562 IST [74989] LOG: starting PostgreSQL 19devel on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0, 64-bit
2026-06-03 12:13:49.562 IST [74989] LOG: listening on IPv4 address "127.0.0.1", port 5450
2026-06-03 12:13:49.565 IST [74989] LOG: listening on Unix socket "/tmp/.s.PGSQL.5450"
2026-06-03 12:13:49.570 IST [74995] LOG: database system was shut down at 2026-06-03 12:13:49 IST
2026-06-03 12:13:49.577 IST [74989] LOG: database system is ready to accept connections
Testing the Backtrace Feature
Create a sample table:
CREATE TABLE test(
id int,
name text
);
Insert data:
INSERT INTO test(id, name)
VALUES (1, 'shiju');
PostgreSQL Backtrace Output
After executing the insert query, check the logfile
2026-06-03 12:21:21.613 IST [75011] LOG: my custom error
2026-06-03 12:21:21.613 IST [75011] BACKTRACE:
postgres: cybrosys postgres [local] INSERT(heap_insert+0x356) [0x63318b5912b6]
postgres: cybrosys postgres [local] INSERT(+0x1eab7a) [0x63318b599b7a]
postgres: cybrosys postgres [local] INSERT(+0x3c038c) [0x63318b76f38c]
postgres: cybrosys postgres [local] INSERT(+0x3c1d40) [0x63318b770d40]
postgres: cybrosys postgres [local] INSERT(standard_ExecutorRun+0x193) [0x63318b73b513]
postgres: cybrosys postgres [local] INSERT(+0x59b525) [0x63318b94a525]
postgres: cybrosys postgres [local] INSERT(+0x59c262) [0x63318b94b262]
postgres: cybrosys postgres [local] INSERT(PortalRun+0x26b) [0x63318b94b77b]
postgres: cybrosys postgres [local] INSERT(+0x59821b) [0x63318b94721b]
postgres: cybrosys postgres [local] INSERT(PostgresMain+0x1924) [0x63318b948fb4]
postgres: cybrosys postgres [local] INSERT(BackendMain+0x53) [0x63318b943003]
postgres: cybrosys postgres [local] INSERT(postmaster_child_launch+0x111) [0x63318b883d61]
postgres: cybrosys postgres [local] INSERT(+0x4d8cd5) [0x63318b887cd5]
postgres: cybrosys postgres [local] INSERT(PostmasterMain+0xd15) [0x63318b8895b5]
postgres: cybrosys postgres [local] INSERT(main+0x1d0) [0x63318b53b790]
/lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x79ff59e29d90]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x79ff59e29e40]
postgres: cybrosys postgres [local] INSERT(_start+0x25) [0x63318b53bb05]
Understanding the Backtrace Output
The backtrace output displays the internal PostgreSQL execution flow that eventually reaches the heap_insert() function during query execution. Each entry in the stack represents a function call involved in processing the SQL statement.
In this example, the heap_insert() function is responsible for inserting tuples into heap tables, while ExecInsert() handles the executor-level insert operation.
The standard_ExecutorRun() function executes the query plan generated by PostgreSQL, and PortalRun() manages the execution of SQL commands through the portal system.
Further down the stack, PostgresMain() acts as the primary backend processing loop for PostgreSQL sessions, while BackendMain() serves as the entry point for backend worker processes. Together, these functions show how PostgreSQL internally processes an INSERT statement from the client request level down to the physical tuple insertion stage.
Analyzing this backtrace information is highly valuable when studying PostgreSQL internals, debugging custom source code modifications, or understanding the relationship between executor functions and storage engine operations.
Advantages of Using backtrace_functions
Using backtrace_functions provides several benefits:
- Simplifies PostgreSQL source code debugging
- Helps understand execution flow
- Useful for backend development
- Helps trace function call hierarchy
- Useful for educational and research purposes
The backtrace_functions parameter in PostgreSQL 19 is a powerful debugging feature for developers working with PostgreSQL internals. By enabling backtraces for selected functions, developers can easily inspect execution paths and understand how PostgreSQL processes SQL operations internally.
In this example, we used heap_insert() to generate a custom log and capture the backend execution stack during an INSERT operation. The same approach can be applied to other PostgreSQL internal functions such as ExecInsert, ExecDelete, ExecUpdate, or custom extensions.
For PostgreSQL kernel developers and researchers, this feature provides a practical way to study backend execution behavior directly from runtime logs.