Logo ← PostgreSQL Blog

SQL Query Journey in Postgres

In PostgreSQL, when a client sends an SQL query, it undergoes a complex journey from submission to execution and result retrieval. This…

SQL Query Journey in Postgres

In PostgreSQL, when a client sends an SQL query, it undergoes a complex journey from submission to execution and result retrieval. This journey involves multiple processes, from the Postmaster managing connections to the backend processes handling parsing, planning, and execution. Along the way, PostgreSQL ensures data consistency, durability through Write-Ahead Logging (WAL), and efficient data retrieval using shared buffers and disk storage. Understanding this flow helps optimize performance and ensures smooth interaction between the client and the database.

1. The Client’s Request

The story begins with the client (for example, psql, a web application, or any database-connected client) sending an SQL query to the PostgreSQL server.

  • The client typically connects to the PostgreSQL server using the TCP/IP protocol (or a Unix socket, depending on the configuration). The client sends an SQL query; SELECT * FROM users WHERE age > 30

2. Connection Establishment and the Postmaster Process

The Postmaster process is always running as the central management process in PostgreSQL. Here’s what happens:

  • When the query arrives at the server, the Postmaster process listens on the designated port (5432 by default).
  • The Postmaster accepts the connection request and forks a child process (a backend process) dedicated to the client. Each client gets its own backend process to ensure isolation.
  • The backend process manages the entire lifecycle of the query: from parsing the SQL statement, planning its execution, performing the actual query execution, to finally sending the result back to the client.

At this point, the client-server connection is established, and the backend process is ready to execute the query.

3. Query Parsing and Planning

Once the backend process has been forked, the query execution journey begins. The backend process performs several key tasks:

Parsing:

  • The query is first parsed to ensure it is syntactically correct. The backend process checks if the SQL statement adheres to PostgreSQL’s grammar and can be processed.
  • Errors in the query (e.g., missing semicolons, wrong column names, or incorrect SQL syntax) are detected here, and the backend returns an error to the client if needed.

Planning:

  • After parsing, PostgreSQL’s query planner takes over. The planner is responsible for determining the best execution plan for the query. This involves selecting indexes (if any), joining tables in an efficient way, and choosing the most optimal strategy for data retrieval.
  • PostgreSQL uses various strategies, such as sequential scan, index scan, or hash join, depending on the query and the available data.

4. Execution: The Data Retrieval Process

At this stage, the query planner has decided the most efficient way to retrieve data. The backend process now executes the plan. Here’s what happens during execution:

Accessing Memory (Shared Buffers):

  • Before going to disk, PostgreSQL will check the shared buffers to see if the requested data is already in memory. This is important for performance since reading from memory is much faster than reading from disk.
  • Shared Buffers hold the database pages (usually 8 KB each) that have been read from disk or modified by previous transactions.
  • If the data is already in memory (a cache hit), the backend can proceed to the next step without hitting the disk.

Disk Access (When Data is Not in Memory):

  • If the requested data is not in the shared buffers (cache miss), PostgreSQL retrieves it from disk.
  • The disk data is first read into the shared buffers, and then the backend process continues with executing the query.
  • In this process, PostgreSQL reads data from the data files that store the actual table content. It handles locking, consistency, and data retrieval in a manner that ensures no conflicting operations are occurring simultaneously (thanks to MVCC — Multi-Version Concurrency Control).

5. Write-Ahead Logging (WAL): Ensuring Durability

PostgreSQL’s durability and crash recovery rely heavily on Write-Ahead Logging (WAL). Here’s how it works during query execution:

  • Before any changes to data are made (in the case of an UPDATE or INSERT query, for example), PostgreSQL writes the change to the WAL. This ensures that the database’s state can be recovered even if the system crashes after the data is modified but before it’s written to the actual data files.
  • The WAL writer process runs in the background, continuously writing changes from the shared buffers to the WAL files, ensuring durability.
  • WAL logs are written before any changes occur to the actual data files (the data directory), making sure that PostgreSQL can recover any changes made during a transaction.

6. Data Modification and the Checkpoint Process

For data-modifying queries (like INSERT, UPDATE, DELETE), the backend process performs the following:

  • Once the WAL is written, the query changes the data in memory (shared buffers).
  • Checkpoints occur periodically in PostgreSQL, where the system flushes all modified pages from the shared buffers to the actual data files on disk. This ensures that all changes made since the last checkpoint are saved to disk.
  • If the server crashes, the WAL entries allow PostgreSQL to recover any changes that happened after the last checkpoint, bringing the system back to a consistent state.

7. Query Results: Returning Data to the Client

Once the backend process has completed executing the query, it prepares the results for the client. Here’s the final step:

Preparing the Response:

  • If the query was a SELECT, the backend process retrieves the relevant rows of data (either from memory or from disk) and packages them into a result set.
  • For data-modifying queries, the backend may return an acknowledgment, such as the number of rows affected (e.g., INSERT 0 1 for a single insertion).

Sending the Response:

  • The results are sent from the backend process to the client through the established connection.
  • If the query was executed successfully, the results are transmitted back to the client (e.g., in the form of rows and columns for a SELECT query, or a success message for a INSERT).
  • The client then receives the response and processes it according to its logic.

8. Cleanup and Process Termination

  • After the query is executed and the results are sent to the client, the backend process performs cleanup operations. This includes:
  • Closing open file descriptors.
  • Releasing locks on tables and rows.
  • Freeing up memory used by the query execution.
  • If the client disconnects, the backend process terminates. The Postmaster process continues to manage other incoming connections.

In Summary: The Complete Journey of an SQL Query in PostgreSQL

  1. Client sends the SQL query to the PostgreSQL server.
  2. Postmaster process listens for incoming connections and forks a backend process for the client.
  3. The backend process parses and plans the query, optimizing it for execution.
  4. Memory (shared buffers) is checked for the data. If not found, PostgreSQL fetches it from disk.
  5. Write-Ahead Logging (WAL) ensures the durability of data changes before modifications are made.
  6. The backend process executes the query, modifies data if necessary, and performs a checkpoint.
  7. Results are returned to the client, or the number of rows affected is acknowledged.
  8. After sending the result, the backend process cleans up and terminates.

This journey, from query submission to result return, illustrates the seamless interaction between the client, PostgreSQL processes, and the disk, ensuring efficiency, durability, and data integrity throughout the entire cycle.