Contents
Performance and Tuning
Overhead vs. Plain Heap
pg_vault_tde adds AES-256-GCM encryption/decryption and IV generation on every tuple read and write. Expected overhead depends heavily on workload shape and row size:
| Workload | Typical Overhead | Notes |
|---|---|---|
| OLTP (mixed read/write, 100–500 B rows) | < 15% | |
| Bulk INSERT (1M rows) | 25–40% | AES-GCM + random IV generation per tuple |
| Sequential scan (1M rows, read-only) | 20–35% | Decrypt + palloc copy per tuple |
| Index scan (point lookups) | < 5% | Single tuple decrypt per fetch |
These figures were measured on pg_vault_tde’s one and only build, which already gets OpenSSL’s automatic hardware-accelerated AES dispatch on supported CPUs (see below).
Hardware Acceleration
pg_vault_tde ships a single package for each (format, PostgreSQL major)
combination — there is no CPU-specific build variant. Hardware-accelerated
AES (AES-NI, VAES, ARM Crypto Extensions, SVE2) is provided automatically at
runtime by OpenSSL 3.x’s EVP/provider layer, based on the CPU the server is
actually running on. This is a property of the OpenSSL library installed on
the host, not of how pg_vault_tde.so was compiled: pg_vault_tde never
implements AES itself, it only calls OpenSSL’s EVP API
(src/crypto/pg_vault_tde_hw_accel.c), so there is nothing a CPU-specific
compile flag could speed up.
make check-cpu # detect this machine's available CPU crypto extensions
make bench-cpu # OpenSSL AES throughput microbenchmark
Confirm which provider is actually active at runtime:
SELECT * FROM pg_vault_tde_hw_accel_info();
-- (openssl_version, configured_provider, provider_loaded, gcm_cipher, siv_cipher, aes_ni_available)
pg_vault_tde.crypto_provider selects a specific OpenSSL 3.x provider (e.g.
qatprovider for Intel QAT offload, or fips); it is PGC_POSTMASTER and
requires a restart. Leave it empty for OpenSSL’s automatic built-in
dispatch, which is correct for the vast majority of deployments.
Benchmarking Your Own Workload
bash bench_tde.sh 100000
Runs INSERT, SELECT, UPDATE, index scan, and TABLESAMPLE workloads on a
plain-heap table vs. an encrypted_heap table, and prints a side-by-side
overhead comparison.
To isolate pure table-access-method overhead from actual cryptography cost,
you can set pg_vault_tde.enabled = off (requires a restart — this GUC is
PGC_POSTMASTER) and re-run the benchmark. Read the warning in
Encrypted Tables and Indexes first — never
do this against a database that has real encrypted_heap data in it.
The local CI pipeline exposes the same benchmark with configurable row counts and profiles:
make ci-bench BENCH_ROWS=100000
Why It’s Not Slower Than This
Two design choices keep the per-tuple cost down without any tuning required on your part:
- Cached cipher contexts. Each backend keeps one AES-GCM and one AES-SIV
cipher context alive for its lifetime, keyed by
(relid, generation). The expensive AES key-schedule installation only happens on the first tuple of a relation (or right after a rotation) — every subsequent tuple of the same relation reuses the installed schedule and only rearms the IV. - Batched random IV generation. Instead of one
pg_strong_random()syscall per tuple, IVs are generated 256 at a time and drawn from an in-memory batch, amortizing the syscall cost across many tuples.
Buffer Pin Behavior
Decrypting a tuple copies it out of the shared buffer into a freshly
allocated plaintext tuple; the shared buffer pin is held until that copy is
handed off internally, preserving the normal page-at-a-time access pattern
of a sequential scan. In practice this means buffer hit counts for
encrypted_heap tables should be comparable to plain heap tables —
proportional to the number of pages touched, not the number of rows
returned. If you observe buffer statistics wildly out of line with a
plain-heap baseline, that’s a signal to investigate, not expected TDE
overhead.