Preamble
Almost any common language, like Perl, Python, or C, can be used to write stored procedures in PostgreSQL. Overall, this provides excellent flexibility and passable performance. Customers may, however, occasionally comment that they “feel like the processes are slow.” The problem is that this observation might not even be due to PostgreSQL; in many cases, it is just “pilot error.” I’ll try to go into more detail about one of the major problems with poor performance.
How PostgreSQL treats procedures
As was already mentioned, stored procedures can be created in almost any language. PostgreSQL just sends the code for the stored procedure to the other language and gets the result back. A stored procedure is somewhat of a “black box” because PostgreSQL rarely understands what takes place there.
Here’s an illustration:
test=# CREATE OR REPLACE FUNCTION mymax(int, int) RETURNS int AS $$ BEGIN RETURN CASE WHEN $1 > $2 THEN $1 ELSE $2 END; END; $$ LANGUAGE 'plpgsql'; CREATE FUNCTION
The results are not very spectacular:
test=# SELECT mymax(20, 30); mymax ------- 30 (1 row)
Here, it’s crucial to remember that the PL/pgSQL function is a wholly closed system. The planner is unaware of the function of the “external” language in this scenario. This has some significant repercussions. Think about the following instance:
test=# CREATE TABLE demo AS SELECT * FROM generate_series(1, 1000000) AS id; SELECT 1000000 test=# CREATE INDEX idx_id ON demo(id); CREATE INDEX
The table is already big enough to take indexes into account:
test=# explain SELECT * FROM demo WHERE id = 20; QUERY PLAN --------------------------------------------------------------- Index Only Scan using idx_id on demo (cost=0.42..8.44 rows=1 width=4) Index Cond: (id = 20) (2 rows)
Functions in PostgreSQL are usually blackboxes
The problem is: If we start using the approach I just described, everything completely changes:
test=# explain SELECT * FROM demo WHERE id = mymax(20, 20); QUERY PLAN --------------------------------------------------------- Seq Scan on demo (cost=0.00..266925.00 rows=1 width=4) Filter: (id = mymax(20, 20)) (2 rows)
PostgreSQL is unaware that the function will return 20. The machine was not informed that this was a given outcome, despite the fact that it is obvious to humans. The outcome of a function is regarded as “volatile” by PostgreSQL, meaning that anything can occur. As a result, it cannot just ask the index to return the appropriate row. Even though the parameters for the first and second function calls are the same, the first function call may not return the same result. In order to be safe, the optimizer will perform a sequential scan, which will undoubtedly yield the desired outcome.
A function in PostgreSQL can be:
• VOLATILE
• STABLE
• IMMUTABLE
If you call a function multiple times with the exact same input parameters and mark it as VOLATILE, it will return whatever you pass it. In the case of STABLE, the function will produce the same outcome when given the same inputs during the same transaction.
The most well-known STABLE function, now(), consistently returns the same outcome within a single transaction:
test=# SELECT now(); now ------------------------------- 2018-01-09 11:48:46.385457+01 (1 row) test=# BEGIN; BEGIN test=# SELECT now(); now ------------------------------- 2018-01-09 11:48:51.073123+01 (1 row) test=# SELECT now(); now ------------------------------- 2018-01-09 11:48:51.073123+01 (1 row) test=# COMMIT; COMMIT test=# SELECT now(); now ------------------------------- 2018-01-09 11:48:59.640697+01 (1 row)
There are even some IMMUTABLE functions. In this instance, regardless of the transaction, the result will be constant given the same input parameters. In this situation, cosine would be an example:
test=# SELECT cos(10), cos(20); cos | cos --------------------+------------------- -0.839071529076452 | 0.408082061813392 (1 row) test=# SELECT cos(10), cos(20); cos | cos --------------------+------------------- -0.839071529076452 | 0.408082061813392 (1 row)
The cosine of a number will remain the same even in the second transaction.
Reducing the number of function calls
To resolve our issue, we must stop utilizing the current function.
test=# DROP FUNCTION mymax(int, int); DROP FUNCTION
… and duplicate it:
test=# CREATE OR REPLACE FUNCTION mymax(int, int) RETURNS int AS $$ BEGIN RETURN CASE WHEN $1 > $2 THEN $1 ELSE $2 END; END; $$ LANGUAGE 'plpgsql' IMMUTABLE; CREATE FUNCTION
If the same input values are given, the planner will do an index scan because it will know that the function is meant to return a fixed value:
test=# explain SELECT * FROM demo WHERE id = mymax(20, 20); QUERY PLAN ------------------------------------------------------ Index Only Scan using idx_id on demo (cost=0.42..8.44 rows=1 width=4) Index Cond: (id = 20) (2 rows)
Since the index scan is orders of magnitude quicker, it will return right away.
Detecting bottlenecks
Thankfully, PostgreSQL has a system view that could provide some insight into functions that might be problematic. Setting “track_functions = ‘all'” will instruct PostgreSQL to collect function statistics, which is the first thing you need to do:
test=# SELECT * FROM demo WHERE id = mymax(20, 20); id ---- 20 (1 row)
When this option is enabled, the following useful data will be available in pg_stat_user_functions:
test=# SELECT * FROM pg_stat_user_functions ; funcid | schemaname | funcname | calls | total_time | self_time --------+------------+----------+-------+------------+----------- 16429 | public | mymax | 1 | 0.025 | 0.025 (1 row)
It can make sense to inspect a function if you notice that it is being called irrationally frequently to see if it is VOLATILE unnecessarily. Speed can be greatly increased by altering the function definition.
Creating indexes on functions in PostgreSQL
You must confirm that a function is actually IMMUTABLE if you want to create indexes on it. PostgreSQL won’t build the index for you if you don’t do this. The explanation is straightforward: PostgreSQL must ensure that the index’s content is reliable and does not need to be updated over time if the underlying data remains constant.
About Enteros
Enteros offers a patented database performance management SaaS platform. It finds the root causes of complex database scalability and performance problems that affect business across a growing number of cloud, RDBMS, NoSQL, and machine learning database platforms.
The views expressed on this blog are those of the author and do not necessarily reflect the opinions of Enteros Inc. This blog may contain links to the content of third-party sites. By providing such links, Enteros Inc. does not adopt, guarantee, approve, or endorse the information, views, or products available on such sites.
Are you interested in writing for Enteros’ Blog? Please send us a pitch!
RELATED POSTS
How to Enable Data-Driven Media Growth with Enteros Cost Attribution and Software Management
- 22 June 2026
- Software Engineering
Introduction The media industry is experiencing one of the most significant transformations in its history. Streaming services, digital publishing platforms, online advertising ecosystems, video-on-demand applications, and content distribution networks have fundamentally changed how audiences consume content. Modern media organizations now operate highly complex digital ecosystems that support: Streaming platforms Digital publishing systems Video content delivery … Continue reading “How to Enable Data-Driven Media Growth with Enteros Cost Attribution and Software Management”
How to Enable Intelligent Wealth Management Operations with Enteros Database Software, AIOps Platform, and Gen AI
Introduction The wealth management industry is undergoing a major transformation. As investors demand personalized financial services, real-time portfolio visibility, and digital-first experiences, wealth management firms are increasingly relying on technology to drive operational efficiency, improve client engagement, and accelerate business growth. Modern wealth management organizations now support: Portfolio management platforms Wealth advisory applications Digital client … Continue reading “How to Enable Intelligent Wealth Management Operations with Enteros Database Software, AIOps Platform, and Gen AI”
The Future of Database Observability in Hybrid Cloud Environments
As enterprises accelerate digital transformation, hybrid cloud infrastructure has become the preferred operating model for many organizations. Instead of relying solely on on-premises data centers or fully public cloud deployments, businesses increasingly combine both environments to achieve greater flexibility, scalability, performance, and cost efficiency. Hybrid cloud enables organizations to distribute workloads strategically across private infrastructure … Continue reading “The Future of Database Observability in Hybrid Cloud Environments”
How AI-Powered Database Analytics Improves Digital Customer Experience
In today’s digital-first economy, customer experience has become one of the strongest differentiators for businesses. Whether customers are shopping online, using banking apps, booking travel, streaming media, or accessing SaaS platforms, they expect fast, seamless, and reliable digital interactions at every touchpoint. Modern users have little tolerance for delays. A slow-loading webpage, failed transaction, delayed … Continue reading “How AI-Powered Database Analytics Improves Digital Customer Experience”