Skip to content

Commit bfaf04d

Browse files
Merge pull request #1 from Agent-Hellboy/initial_commit
initial commit
2 parents 2dc368d + 44672a1 commit bfaf04d

File tree

11 files changed

+1204
-35
lines changed

11 files changed

+1204
-35
lines changed

.github/workflows/c-cpp.yml

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
1-
name: C/C++ CI
1+
name: build
22

3-
on:
4-
push:
5-
branches: [ "main" ]
6-
pull_request:
7-
branches: [ "main" ]
3+
on: [push, pull_request]
84

95
jobs:
10-
build:
11-
12-
runs-on: ubuntu-latest
6+
ubuntu:
7+
runs-on: ${{ matrix.os }}
8+
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
include:
13+
- postgres: 19
14+
os: ubuntu-24.04
15+
- postgres: 18
16+
os: ubuntu-24.04
17+
- postgres: 17
18+
os: ubuntu-24.04
19+
- postgres: 16
20+
os: ubuntu-24.04
1321

1422
steps:
15-
- uses: actions/checkout@v4
16-
- name: configure
17-
run: ./configure
18-
- name: make
19-
run: make
20-
- name: make check
21-
run: make check
22-
- name: make distcheck
23-
run: make distcheck
23+
- uses: actions/checkout@v5
24+
- uses: ankane/setup-postgres@v1
25+
with:
26+
postgres-version: ${{ matrix.postgres }}
27+
dev-files: true
28+
- run: make
29+
env:
30+
PG_CFLAGS: -std=c11 -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -Wno-declaration-after-statement ${{ matrix.postgres >= 18 && '-Wno-missing-field-initializers' || '' }}
31+
- run: |
32+
export PG_CONFIG=`which pg_config`
33+
sudo --preserve-env=PG_CONFIG make install
34+
- run: make installcheck
35+
- if: ${{ failure() }}
36+
run: cat regression.diffs

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
*.so
2828
*.so.*
2929
*.dylib
30+
results/
3031

3132
# Executables
3233
*.exe
3334
*.out
35+
!expected/*.out
3436
*.app
3537
*.i*86
3638
*.x86_64

LICENSE

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1-
MIT License
1+
pg_retry PostgreSQL Extension
22

3-
Copyright (c) 2025 Prince Roshan
3+
Copyright (c) 2025, Prince Roshan
44

5-
Permission is hereby granted, free of charge, to any person obtaining a copy
6-
of this software and associated documentation files (the "Software"), to deal
7-
in the Software without restriction, including without limitation the rights
8-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9-
copies of the Software, and to permit persons to whom the Software is
10-
furnished to do so, subject to the following conditions:
5+
Permission to use, copy, modify, and distribute this software and its
6+
documentation for any purpose, without fee, and without a written agreement
7+
is hereby granted, provided that the above copyright notice and this
8+
paragraph and the following two paragraphs appear in all copies.
119

12-
The above copyright notice and this permission notice shall be included in all
13-
copies or substantial portions of the Software.
10+
IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
11+
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
12+
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE
13+
AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1414

15-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
15+
THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED
16+
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17+
PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE
18+
AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
19+
ENHANCEMENTS, OR MODIFICATIONS.
20+
21+
---
22+
23+
This extension is released under the same license terms as PostgreSQL itself.
24+
For more information, see: https://www.postgresql.org/about/licence/

Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
EXTENSION = pg_retry
2+
MODULE_big = pg_retry
3+
OBJS = pg_retry.o
4+
DATA = sql/pg_retry--1.0.sql
5+
DOCS = README.md
6+
7+
# Regression tests
8+
REGRESS = pg_retry
9+
REGRESS_OPTS = --load-extension=pg_retry
10+
11+
12+
# Additional compiler flags (PGXS provides most flags)
13+
PG_CFLAGS = -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -std=c11 \
14+
-Wimplicit-fallthrough -g -O2 -fno-omit-frame-pointer \
15+
-fstack-protector-strong -D_FORTIFY_SOURCE=3
16+
17+
PG_CONFIG ?= pg_config
18+
PGXS := $(shell $(PG_CONFIG) --pgxs)
19+
include $(PGXS)

README.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# pg_retry
2+
3+
A PostgreSQL extension that provides retry functionality for SQL statements on transient errors with exponential backoff.
4+
5+
## Overview
6+
7+
`pg_retry` allows you to automatically retry SQL statements that fail due to transient errors such as serialization failures, deadlocks, lock timeouts, or query cancellations. It implements exponential backoff with jitter to avoid thundering herd problems.
8+
9+
## Features
10+
11+
- **Automatic retries** on configurable transient errors
12+
- **Exponential backoff** with configurable base and maximum delays
13+
- **Jitter** to prevent synchronized retries
14+
- **Subtransaction isolation** for each retry attempt
15+
- **Comprehensive validation** to prevent misuse
16+
- **Configurable GUC parameters** for defaults
17+
- **Detailed logging** of retry attempts
18+
19+
## Installation
20+
21+
### Prerequisites
22+
23+
- PostgreSQL 14+
24+
- C compiler and build tools
25+
26+
### Build and Install
27+
28+
```bash
29+
# Clone or download the extension
30+
cd pg_retry
31+
32+
# Build the extension
33+
make
34+
35+
# Install (may require sudo)
36+
make install
37+
38+
# Run tests (optional)
39+
make installcheck
40+
```
41+
42+
### Enable the Extension
43+
44+
```sql
45+
CREATE EXTENSION pg_retry;
46+
```
47+
48+
## Function Signature
49+
50+
```sql
51+
retry.retry(
52+
sql TEXT, -- the SQL statement to run (exactly one statement)
53+
max_tries INT DEFAULT 3, -- total attempts = 1 + retries; must be >= 1
54+
base_delay_ms INT DEFAULT 50, -- initial backoff delay in milliseconds
55+
max_delay_ms INT DEFAULT 1000, -- cap for exponential backoff
56+
retry_sqlstates TEXT[] DEFAULT ARRAY['40001','40P01','55P03','57014']
57+
) RETURNS INT -- number of rows processed/returned by the statement
58+
```
59+
60+
### Retryable SQLSTATEs
61+
62+
By default, the following SQLSTATEs are considered retryable:
63+
64+
- `40001`: serialization_failure
65+
- `40P01`: deadlock_detected
66+
- `55P03`: lock_not_available
67+
- `57014`: query_canceled (e.g., statement_timeout)
68+
69+
## Usage Examples
70+
71+
### Basic Usage
72+
73+
```sql
74+
-- Simple retry with defaults
75+
SELECT retry.retry('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
76+
```
77+
78+
### Custom Retry Parameters
79+
80+
```sql
81+
-- More aggressive retries for critical operations
82+
SELECT retry.retry(
83+
'INSERT INTO audit_log (event, timestamp) VALUES ($1, NOW())',
84+
5, -- max_tries
85+
100, -- base_delay_ms
86+
5000, -- max_delay_ms
87+
ARRAY['40001', '40P01', '55P03', '57014', '53300'] -- additional SQLSTATEs
88+
);
89+
```
90+
91+
### Handling Different Statement Types
92+
93+
```sql
94+
-- DML operations return affected rows
95+
SELECT retry.retry('UPDATE users SET last_login = NOW() WHERE id = 123');
96+
97+
-- SELECT returns number of rows returned
98+
SELECT retry.retry('SELECT * FROM large_table WHERE status = $1');
99+
100+
-- DDL/utility operations return 0
101+
SELECT retry.retry('CREATE INDEX CONCURRENTLY ON big_table (column)');
102+
```
103+
104+
## Configuration (GUC Parameters)
105+
106+
You can set default values using PostgreSQL GUC parameters:
107+
108+
```sql
109+
-- Set global defaults
110+
ALTER SYSTEM SET pg_retry.default_max_tries = 5;
111+
ALTER SYSTEM SET pg_retry.default_base_delay_ms = 100;
112+
ALTER SYSTEM SET pg_retry.default_max_delay_ms = 5000;
113+
ALTER SYSTEM SET pg_retry.default_sqlstates = '40001,40P01,55P03,57014,53300';
114+
115+
-- Reload configuration
116+
SELECT pg_reload_conf();
117+
```
118+
119+
## Safety and Validation
120+
121+
The extension includes several safety checks:
122+
123+
### Single Statement Only
124+
125+
Only exactly one SQL statement is allowed per call:
126+
127+
```sql
128+
-- This works
129+
SELECT retry.retry('SELECT 42');
130+
131+
-- This fails
132+
SELECT retry.retry('SELECT 1; SELECT 2');
133+
```
134+
135+
### No Transaction Control
136+
137+
Transaction control statements are prohibited:
138+
139+
```sql
140+
-- These all fail
141+
SELECT retry.retry('BEGIN; SELECT 1; COMMIT');
142+
SELECT retry.retry('SAVEPOINT sp1; SELECT 1; RELEASE sp1');
143+
SELECT retry.retry('ROLLBACK');
144+
```
145+
146+
### Parameter Validation
147+
148+
Input parameters are validated:
149+
150+
```sql
151+
-- These fail
152+
SELECT retry.retry('SELECT 1', 0); -- max_tries < 1
153+
SELECT retry.retry('SELECT 1', 3, -1); -- negative delay
154+
SELECT retry.retry('SELECT 1', 3, 1000, 500); -- base > max delay
155+
```
156+
157+
## Retry Behavior
158+
159+
### Exponential Backoff Algorithm
160+
161+
```
162+
delay = min(max_delay_ms, base_delay_ms * (2^(attempt-1)))
163+
jitter = random() * (delay * 0.2) -- ±20%
164+
final_delay = max(1ms, delay + jitter)
165+
```
166+
167+
### Example Delays (base_delay_ms=50, max_delay_ms=1000)
168+
169+
- Attempt 1: ~50ms ± 10ms
170+
- Attempt 2: ~100ms ± 20ms
171+
- Attempt 3: ~200ms ± 40ms
172+
- Attempt 4: ~400ms ± 80ms
173+
- Attempt 5: ~800ms ± 160ms
174+
- Attempt 6+: ~1000ms ± 200ms
175+
176+
### Logging
177+
178+
Each retry attempt is logged as a WARNING:
179+
180+
```
181+
WARNING: pg_retry: attempt 2/3 failed with SQLSTATE 40001: could not serialize access due to concurrent update
182+
```
183+
184+
## Error Handling
185+
186+
- **Retryable errors**: Automatically retried up to `max_tries`
187+
- **Non-retryable errors**: Immediately rethrown
188+
- **Exhausted retries**: Last error is rethrown with full context
189+
190+
## Performance Considerations
191+
192+
- Each retry runs in a subtransaction
193+
- SPI overhead for statement execution
194+
- Exponential backoff prevents resource exhaustion
195+
- Jitter prevents thundering herd problems
196+
197+
## Limitations
198+
199+
- Only supports single SQL statements
200+
- No support for transaction control
201+
- Cannot retry certain operations (COPY FROM STDIN, large objects, cursors)
202+
- Function is marked `VOLATILE` and `PARALLEL RESTRICTED`
203+
204+
## Testing
205+
206+
Run the regression tests:
207+
208+
```bash
209+
make installcheck
210+
```
211+
212+
## License
213+
214+
Copyright (c) 2025, Prince Roshan
215+
216+
This extension is released under PostgreSQL license terms. See the LICENSE file for the full license text.
217+
218+
## Contributing
219+
220+
1. Fork the repository
221+
2. Make your changes
222+
3. Add tests for new functionality
223+
4. Ensure `make installcheck` passes
224+
5. Submit a pull request

0 commit comments

Comments
 (0)