Centralized PostgreSQL Connection Pool for QGIS Plugins
QGIS 3.28+ | Python 3.9+Without QPooler, each QGIS plugin creates its own PostgreSQL connections independently. This leads to resource exhaustion, poor performance, and server overload.
QPooler acts as a centralized connection broker. All plugins share a single optimized pool, dramatically reducing resource usage and improving performance.
QPooler follows a modular architecture with clear separation of concerns. Each component has a single responsibility.
Public facade - single entry point for all database operations
Connection pooling with singleton pattern and watchdog thread
TTL-based query caching with LRU eviction
Access control with role-based permissions
Performance tracking per plugin
Transaction management with savepoints
Every database request follows a consistent path through QPooler's components.
db.fetch_all() with SQL and parameters
1 connection per plugin, automatic reuse. Reduces 15+ connections to 3-4.
TTL-based caching with 84% hit rate. LISTEN/NOTIFY for automatic invalidation.
Role-based access control (read/write/admin). Table whitelist/blacklist. Rate limiting.
Per-plugin statistics: query count, avg time, percentiles (p50, p90, p99).
Context manager with auto-commit/rollback. Savepoint support.
Safe query builders. PostGIS helpers with explicit SRID.
Independent health checks, idle cleanup, transaction timeouts.
Uses native QGIS connections. Proper lifecycle management.
| Role | SELECT | INSERT | UPDATE | DELETE | DDL |
|---|---|---|---|---|---|
| read | Yes | No | No | No | No |
| write | Yes | Yes | Yes | Yes | No |
| admin | Yes | Yes | Yes | Yes | Yes |
from qpooler import db
# Configure from existing QGIS connection
db.configure_from_qgis("my_connection")
db.set_plugin_id("my_plugin")
# Execute queries
rows = db.fetch_all("SELECT * FROM parcels WHERE active = %s", (True,))
row = db.fetch_one("SELECT COUNT(*) FROM parcels")
# With caching (60s TTL)
rows = db.fetch_all("SELECT * FROM ref_table", use_cache=True, cache_ttl=60)
with db.transaction(role="write") as tx:
tx.execute("INSERT INTO logs (msg) VALUES (%s)", ("hello",))
tx.execute("UPDATE counters SET val = val + 1")
# Auto-commit on success, auto-rollback on exception
from qpooler.sql import SQLHelper, SpatialHelper, QueryBuilder
# Simple SELECT
sql, params = SQLHelper.select("users", ["id", "name"], "active = %s", (True,))
# PostGIS with explicit SRID
sql, params = SpatialHelper.st_intersects(
"parcels", "geom", "POINT(0 0)", srid=2154
)
# Fluent query builder
sql, params = (QueryBuilder()
.select("id", "name")
.from_table("users")
.where("active = %s", (True,))
.order_by("name")
.limit(10)
.build())
# Full status
status = db.status()
# {"pool": {...}, "cache": {...}, "metrics": {...}}
# Test connection
ok = db.test_connection()
# Get metrics
metrics = db.get_metrics()
# Check policy violations
violations = db.get_violations()
| Method | Description |
|---|---|
db.configure(dsn, ...) |
Configure pool with DSN string |
db.configure_from_qgis(name) |
Configure from QGIS connection |
db.set_plugin_id(id) |
Set default plugin identifier |
db.list_qgis_connections() |
List available QGIS connections |
| Method | Description |
|---|---|
db.fetch_all(sql, params, ...) |
Execute SELECT, return all rows |
db.fetch_one(sql, params, ...) |
Execute SELECT, return single row |
db.execute(sql, params, ...) |
Execute INSERT/UPDATE/DELETE |
db.transaction(role) |
Context manager for transactions |
| Method | Description |
|---|---|
db.status() |
Full diagnostic status |
db.test_connection() |
Test database connectivity |
db.get_metrics() |
Get performance metrics |
db.get_violations() |
Get policy violations |