Unconventional PostgreSQL optimization techniques
From index fundamentals to unconventional techniques.
This skill provides comprehensive PostgreSQL index and optimization knowledge:
| File | Contents | |------|----------| | INDEXES.md | Deep dive on index internals, types, costs, decision trees | | SKILL.md | Unconventional optimization techniques | | CARD.yml | Sniffable interface, methods, advertisements | | README.md | This overview |
Captures foundational knowledge from dlt's article (249 points on HN):
| Index Type | Best For | Key Insight | |------------|----------|-------------| | B-Tree | General purpose, sorting, ranges | Default, only type for PK/unique | | Hash | Equality on large values | 5x smaller than B-Tree for URLs | | BRIN | Huge append-only tables | Tiny (stores ranges, not values) | | GIN | Arrays, JSONB, full-text | Fast lookups, high write cost | | GiST | Spatial, ranges, full-text | Balanced read/write tradeoff |
Essential reading: Use The Index, Luke
From Haki Benita's article:
Three techniques are covered:
| Technique | Problem | Solution |
|-----------|---------|----------|
| Constraint Exclusion | Full scans on impossible queries | SET constraint_exclusion TO 'on' |
| Function-Based Indexes | Oversized indexes on high-cardinality columns | Index lower-cardinality expression |
| Hash Index Uniqueness | Giant B-Tree on large text values | Exclusion constraint with hash index |
PostgreSQL has check constraints that guarantee certain values can never exist. But by default, queries that ask for those impossible values still scan the table!
-- Table has CHECK (plan IN ('free', 'pro'))
SELECT * FROM users WHERE plan = 'Pro'; -- Scans 100K rows for 0 results
The fix is a session/connection setting:
SET constraint_exclusion TO 'on';
-- Now PostgreSQL recognizes the condition is always false
Use case: BI environments, ad-hoc query tools, reporting databases where users make typos.
If you store timestamps but query by day/month/year, you're paying for index precision you don't use.
-- Stores 10M distinct timestamps
-- But queries only need ~365 distinct dates per year
CREATE INDEX sale_sold_at_date_ix
ON sale((date_trunc('day', sold_at AT TIME ZONE 'UTC'))::date);
Result: 66 MB index instead of 214 MB — 3x smaller.
The catch: expressions must match exactly. PostgreSQL 18's virtual generated columns solve this.
B-Tree indexes store actual values in leaf blocks. For large text values (URLs, document paths), this creates huge indexes.
Hash indexes store only the hash — much smaller. PostgreSQL doesn't support CREATE UNIQUE INDEX ... USING HASH, but exclusion constraints achieve the same effect:
ALTER TABLE urls
ADD CONSTRAINT urls_url_unique_hash
EXCLUDE USING HASH (url WITH =);
Result: 32 MB index instead of 154 MB — 5x smaller, and faster queries.
From the Hacker News discussion:
"If you're dealing with <ridiculous number of users>, there is a good chance that you don't want to be putting BI/OLAP indices on your OLTP database."
The tradeoff: every index adds write overhead. Smaller indexes help, but sometimes the answer is a read replica with its own indexes.
"PG's lack of plan caching strikes again, this sort of thing is not a concern in other DB's that reuse query plans."
PostgreSQL does cache prepared statement plans (after 5 executions), but it's per-connection. The constraint_exclusion overhead matters for ad-hoc queries that aren't prepared.
For monotonic data (e.g., timestamps arriving in order), BRIN indexes are extremely small and fast. Worth considering alongside function-based indexes.
MERGE (added in PostgreSQL 15) is more powerful than INSERT ... ON CONFLICT but has MVCC edge cases. For concurrent OLTP workloads, stick with ON CONFLICT. MERGE is better for manual fixups and batch operations.
| File | Purpose |
|------|---------|
| CARD.yml | Sniffable interface for activation |
| SKILL.md | Protocol and detailed techniques |
| README.md | This file — deep context |
What operations?
├── Equality only?
│ ├── Large values (URLs, UUIDs)? → Hash
│ └── Normal values → B-Tree
│
├── Ranges, sorting, ORDER BY? → B-Tree
│
├── Huge table, append-only?
│ └── Value correlates with physical location? → BRIN
│
├── Searching WITHIN data?
│ ├── Arrays, JSONB? → GIN
│ └── Full-text? → GIN (read-heavy) or GiST (write-heavy)
│
├── Geometric/spatial? → GiST
│
└── Unsure? → B-Tree (default)
Index Skip Scan — Multi-column indexes can now be used even when queries only filter on lower-order columns. This changes longstanding wisdom about column ordering.
Virtual Generated Columns — Function-based indexes no longer require discipline. Create a virtual column that guarantees the correct expression.
npx skills add SimHacker/postgres-optimization下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer
Tags:moollm, database, postgresql, performance, optimization, indexing