惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
小众软件
小众软件
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
博客园 - 三生石上(FineUI控件)
博客园 - 【当耐特】
N
News | PayPal Newsroom
K
Kaspersky official blog
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
N
Netflix TechBlog - Medium
B
Blog
Recorded Future
Recorded Future
U
Unit 42
J
Java Code Geeks
Security Latest
Security Latest
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
Vulnerabilities – Threatpost
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Scott Helme
Scott Helme
Apple Machine Learning Research
Apple Machine Learning Research
aimingoo的专栏
aimingoo的专栏
T
Threatpost
Last Week in AI
Last Week in AI
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
NISL@THU
NISL@THU
有赞技术团队
有赞技术团队
博客园 - 叶小钗
N
News and Events Feed by Topic
V
V2EX
T
Troy Hunt's Blog
月光博客
月光博客
博客园 - Franky
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
C
Cisco Blogs
The Cloudflare Blog
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Database Index Selection: Why Basic Approaches Fall Short?
Mustafa ERBA · 2026-05-29 · via DEV Community

Introduction: The Unseen Costs of Indexes

When we talk about database performance, indexes are usually the first thing that comes to mind. When a query runs slowly, the first place we look is often for missing or incorrect indexes. We generally know what B-tree, GIN, and BRIN index types do and when to use them. We even have those famous graphs from PostgreSQL documentation in our minds. But in the real world, especially in large and complex systems, how much do we question why these basic index choices often fall short, or even sometimes degrade performance?

In this post, I'll explain with concrete examples from my own experiences why index selections cannot be made by just looking at table and query structures, and how organizational workflows, data distribution, and even hardware can influence these decisions. From the "late shipment report" problem I encountered in a manufacturing ERP to index optimizations in my own financial calculators, we will focus on moments where we pushed the limits of basic approaches.

B-Tree Index: The Savior for Every Situation?

The default and most frequently used index type in PostgreSQL is undoubtedly B-tree. It is generally very successful in speeding up queries using operators like equality (=), greater than (>), less than (<), and BETWEEN. It even works for prefix searches like LIKE 'prefix%'. I remember adding a B-tree index to almost every table while working on a manufacturing ERP for over 5 years.

However, B-trees also have their limits. Especially for searches like LIKE '%suffix' or LIKE '%substring%', due to the structure of B-tree, it can't do much beyond a full table scan. When we encounter such queries, the first solution that comes to mind is either using FTS (Full-Text Search) for more complex search algorithms or moving towards more advanced index structures.

For example, in a client project, we were trying to filter product movements in operator screens in real-time. We were querying by date range and product code, and these queries were quite fast with B-tree indexes. However, operators sometimes wanted to search by entering part of the product description. A search like LIKE '%screen%' caused serious performance issues in tables with millions of rows. Initially, we tried GIN indexes using the pg_trgm extension, but this slowed down table writes. Finally, we made the search need more structural by moving to a different data model.

ℹ️ Limitations of B-Tree Index

B-tree indexes, with their ordered data structure, speed up many common queries. However, their performance can degrade as search patterns become more complex or when data distribution is very uneven. They are particularly insufficient for full-text or complex string matching.

GIN Index: The Powerhouse for Text Searches?

When working with Full-Text Search (FTS) or text-heavy data, GIN (Generalized Inverted Index) indexes come into play. They are used to search for specific words or patterns in data of JSONB, array, or text types. GIN indexes can be a lifesaver in analyzing product descriptions or reviews on an e-commerce site.

In a client project, we were storing product features as JSONB. We needed to query the existence of a specific feature ("color": "blue") or multiple features ("color": "blue" AND "size": "XL") within this JSONB field. GIN indexes were a perfect fit for such queries. We created this index with the command CREATE INDEX idx_products_features ON products USING GIN (features);, and our queries went from seconds to milliseconds.

However, GIN indexes also come with their own costs. GIN indexes occupy much more disk space than B-trees and, importantly, slow down table data insertion (INSERT) or update (UPDATE) operations. This is because the index needs to be updated with every data change. In a project with my own financial calculators, using GIN indexes while processing constantly updated financial data had slowed down write performance so much that we considered moving the data to a separate time-series database.

# Example GIN index creation command
CREATE INDEX idx_articles_content ON articles USING GIN (to_tsvector('turkish', content));

# JSONB query with GIN index
SELECT * FROM products WHERE features @> '{"renk": "mavi", "boyut": "XL"}';

Enter fullscreen mode Exit fullscreen mode

⚠️ Considerations for GIN Indexes

While GIN indexes excel in complex data structures and text searches, they come with significant costs in terms of disk space and write performance. They should be used with caution in systems with heavy write operations.

BRIN Index: An Alternative for Large, Ordered Data

BRIN (Block Range Index) indexes are designed as an alternative to B-trees for large tables and ordered datasets. BRIN indexes use the physical order of the table on disk to determine if data falls within a certain range. Since they only store one entry per data block, they are much smaller than B-trees.

In a data warehouse project, we had a time-series table with millions of records. Data was typically added to this table in chronological order. When querying using the event_timestamp column, using a B-tree index both greatly increased the index size and didn't provide the expected performance for some queries. This is precisely where BRIN indexes came into play.

CREATE INDEX idx_timeseries_event_time ON timeseries_data USING BRIN (event_timestamp);

With this index, when the query specified a time range, PostgreSQL only had to scan the data blocks corresponding to that range, rather than reading all millions of records. The biggest advantage of BRIN indexes is that when data is added in order or has a specific natural order, they can offer similar or better performance with a much smaller footprint than B-trees.

However, BRIN indexes also have a critical prerequisite: the data must be physically ordered on disk. If your data is frequently updated, deleted, or randomly inserted, the benefits of BRIN indexes quickly disappear. I once tried a BRIN index on a stock movement table in a manufacturing company's ERP system. The data was ordered when added, but later stock corrections and returns caused the order to be disrupted, rendering the BRIN index useless.

💡 Advantages and Conditions of BRIN Indexes

BRIN indexes are an excellent option for large and ordered datasets. They save disk space and are effective for range queries. However, as they rely on the physical order of data on disk, maintaining data order is critical.

Overlooked Factors in Index Selection

Typically, when selecting indexes, we focus on query patterns, data types, and the basic characteristics of the index type. However, in a production environment, things are much more complex.

Data Distribution and Cardinality

The cardinality of a column (the number of unique values) plays a critical role in index selection. A B-tree index on a column with low cardinality (e.g., columns with only a few distinct values like gender or status codes) often doesn't perform better than a full table scan. This is because the index will point to rows representing a large portion of the table. In such cases, it's crucial to carefully examine the EXPLAIN ANALYZE output.

At one point, a client's order status table had a status column with only 3 distinct values: 'pending', 'processing', 'completed'. We had created a B-tree index on this column. However, the query WHERE status = 'completed' was slow because it scanned 70% of the table. In this situation, optimizing the query or managing the status in a different data structure might have been a more appropriate approach than using an index.

# B-tree index on a low-cardinality column being insufficient
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'completed';
-- We would expect to see a large 'Seq Scan' or 'Bitmap Heap Scan' in the analysis output.

Enter fullscreen mode Exit fullscreen mode

Write vs. Read Balance

Indexes improve read performance but degrade write performance. Every index must be updated during a data change. If your table experiences very frequent data writes (e.g., logging or real-time transaction records), updating multiple indexes for each added piece of data can create a significant performance bottleneck.

In the backend of my own mobile application, I was anonymously logging user activities. Initially, I had created B-tree indexes on columns like date, user ID, and activity type. When millions of log rows were added daily, write performance dropped so much that the application started to slow down. Eventually, I realized that most log queries were just searching by time and switched to a BRIN index solely on event_timestamp, removing the other indexes. This change increased write performance by over 300%.

Index Maintenance and Cost

Indexes don't just take up space; they also require maintenance. In PostgreSQL, the VACUUM operation is important for reclaiming free space left by deleted or updated rows and optimizing indexes. Operations like VACUUM FULL are more aggressive but can cause significant access issues by locking the table.

In a manufacturing ERP system, we weren't regularly checking the pg_stat_user_indexes table. Over time, the indexes had become so bloated that we started experiencing disk space issues. By looking at the idx_scan and last_vacuum/last_autovacuum columns in pg_stat_user_indexes, we identified which indexes were unused or hadn't been VACUUMed for a long time. Deleting unused indexes and optimizing VACUUM settings helped us reduce disk usage by 20%.

Advanced Indexing Approaches

There are also more advanced methods we can resort to when basic index types are insufficient.

Partial Indexes

Partial indexes allow you to create an index on only a specific subset of the table. This reduces the index size and improves write performance. For example, if you frequently query only records with a specific status, you can create a partial index for that status.

In a client project, we rarely queried cancelled orders. The order table had millions of rows, and queries with the condition status = 'cancelled' were slow. However, cancelled orders constituted only 1% of the table. In this case, instead of adding an index to the entire table, we created a partial index just for cancelled orders:

CREATE INDEX idx_orders_cancelled ON orders (order_id) WHERE status = 'cancelled';

Enter fullscreen mode Exit fullscreen mode

This index was much smaller, containing only the order_ids of cancelled orders, and it sped up relevant queries.

Expression Indexes

Expression indexes allow you to create an index on the results of functions or expressions performed on columns, rather than on the columns themselves. The to_tsvector expression I mentioned earlier is an example. Or you can use the lower() function for case-insensitive comparisons.

For instance, if you have a username column in a user table and frequently perform queries like WHERE lower(username) = 'admin', creating an expression index on lower(username) will speed up these queries.

CREATE INDEX idx_users_lower_username ON users (lower(username));
SELECT * FROM users WHERE lower(username) = 'admin';

Enter fullscreen mode Exit fullscreen mode

Covering Indexes (with INCLUDE in PostgreSQL)

With the INCLUDE keyword in PostgreSQL 11 and later versions, it's possible to create covering indexes. This allows the query to be completed using only the index, without needing to access the main table. This can significantly improve query performance.

In a financial reporting tool, I needed to retrieve transaction details for a specific account and date range. We had a B-tree index on both the account ID and the date. However, the query also retrieved the transaction description. In this case, I created a covering index by adding the transaction description to the INCLUDE part of the index, which included the order by and where conditions:

CREATE INDEX idx_transactions_account_date ON transactions (account_id, transaction_date) INCLUDE (description);

Enter fullscreen mode Exit fullscreen mode

This way, queries that needed the account_id, transaction_date, and description columns could run solely from the index, without touching the main table at all.

🔥 Considerations for Covering Indexes

Covering indexes can significantly improve query performance but also increase index size. Since each included column in INCLUDE increases the index size, it's important to only add columns that are truly needed. Otherwise, the index itself can become a performance bottleneck.

Conclusion: Indexes Are a Tool, But Not a Solution on Their Own

Database indexes are one of the cornerstones of performance optimization. However, making the right decision by just looking at basic types like B-tree, GIN, or BRIN, or even just analyzing query plans, is often not possible. Factors like data distribution, write/read balance, index costs, and advanced indexing strategies must also be considered.

We must remember that indexes are only one part of the solution in complex systems. Sometimes, the best index is no index at all. Or a better data model, better query writing, or even choosing a different database technology can yield much more effective results than index optimization. One of the biggest mistakes I've seen in my career is over-reliance on indexes while neglecting the underlying data model or query logic.

As I mentioned in my previous [related: database performance analysis] posts, learning to read EXPLAIN ANALYZE output is the first step, but being able to see the system as a whole and manage trade-offs correctly is essential.