Stop Using COUNT(*) on Large Tables – Algorithm-Driven Approximations That Are Good Enough
Figure 1: The COUNT(*) Time Bomb – Billions of rows, minutes of waiting, and a ticking clock that threatens dashboard performance.
Your data is growing faster than your database can count.
Executive Summary
Problem: COUNT(*) queries on large tables take minutes to hours, causing dashboard timeouts, DBA alerts, and user frustration1. Understanding the deep mechanics of these query operations is increasingly crucial for modern DBA roles.
Cause: MVCC requires scanning every index leaf node to check row visibility — a full scan of gigabytes of data2.
Solution: In representative benchmark scenarios, Approximate Query Processing (AQP) can reduce execution time by several orders of magnitude while maintaining high statistical accuracy. Actual results depend on workload, hardware, data distribution, and implementation3.
Introduction: The COUNT(*) Crisis in Modern Databases
In illustrative scenarios, a simple SELECT COUNT(*) FROM orders query that used to return in milliseconds might take over 2 seconds on a 20-million-row table. At 450 million rows, the same query can take several minutes. And at billion-row scale, it can take hours1.
The problem isn't your hardware. It's that you're asking for exact precision when you only need "good enough." By leveraging intelligent SQL query processing, modern databases can bypass these traditional physical scanning limitations.
Why COUNT(*) Is Killing Your Performance
Figure 2: How InnoDB's MVCC Scans for COUNT(*) – The B‑tree index scan path, checking row visibility for every leaf node.
It's Not the Database's Fault – It's MVCC
InnoDB (MySQL) and PostgreSQL use Multi-Version Concurrency Control (MVCC) to handle transactions2. When you run COUNT(*), the database must scan every leaf node of an index and check visibility for each row against the current transaction7.
Phase 1: Ubuntu Environment Setup & Comprehensive Database Schema
Before we can implement algorithm-driven approximations, we need a robust foundation. Below is a comprehensive, production-ready setup for an Ubuntu 22.04 LTS environment, including the exhaustive database schema with all parameters for our core e-commerce tables. Just as running SELECT * on customer tables kills performance, running exact counts on orders creates severe bottlenecks. To keep these query paths optimized as the database expands, applying automated AI-driven index tuning can prevent full-scan degradation. This schema is designed to demonstrate the exact scenarios where COUNT(*) fails and AQP succeeds.
Snippet 1: Ubuntu Setup & Complete Database Schema
View full source code (Click to expand)
#!/bin/bash
# ==============================================================================
# SCRIPT: setup_ubuntu_aqp_env.sh
# DESCRIPTION: Automates the setup of Ubuntu 22.04 LTS for AQP Proxy
# ==============================================================================
set -e
echo "[INFO] Updating package lists and upgrading existing packages..."
sudo apt-get update -y
sudo apt-get upgrade -y
echo "[INFO] Installing base dependencies..."
sudo apt-get install -y \
curl wget gnupg2 lsb-release software-properties-common \
build-essential git vim htop net-tools ufw fail2ban
echo "[INFO] Adding PostgreSQL official APT repository..."
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update -y
echo "[INFO] Installing PostgreSQL 15 and required extensions..."
sudo apt-get install -y postgresql-15 postgresql-contrib-15 libpq-dev
echo "[INFO] Tuning PostgreSQL configuration for AQP..."
PG_CONF="/etc/postgresql/15/main/postgresql.conf"
sudo sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/g" $PG_CONF
sudo sed -i "s/max_connections = 100/max_connections = 500/g" $PG_CONF
sudo sed -i "s/shared_buffers = 128MB/shared_buffers = 4GB/g" $PG_CONF
sudo sed -i "s/#effective_cache_size = 4GB/effective_cache_size = 12GB/g" $PG_CONF
sudo sed -i "s/work_mem = 4MB/work_mem = 64MB/g" $PG_CONF
sudo systemctl restart postgresql
sudo systemctl enable postgresql
echo "[SUCCESS] Ubuntu environment and PostgreSQL setup complete."
-- ==============================================================================
-- COMPREHENSIVE DATABASE SCHEMA FOR E-COMMERCE AQP DEMONSTRATION
-- ==============================================================================
-- Create the dedicated database and user for the AQP application
-- NOTE: Always use a strong, securely generated password in production!
CREATE USER aqp_user WITH PASSWORD '<strong-password>';
CREATE DATABASE aqp_ecommerce OWNER aqp_user;
\c aqp_ecommerce
-- ------------------------------------------------------------------------------
-- TABLE: customers
-- ------------------------------------------------------------------------------
CREATE TABLE customers (
customer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
address_line1 VARCHAR(255),
address_line2 VARCHAR(255),
city VARCHAR(100),
state_province VARCHAR(100),
postal_code VARCHAR(20),
country_code CHAR(2) DEFAULT 'US',
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'closed')),
tier VARCHAR(20) DEFAULT 'standard' CHECK (tier IN ('standard', 'premium', 'vip')),
region VARCHAR(50),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP WITH TIME ZONE,
email_verified_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_customers_region_status ON customers(region, status);
CREATE INDEX idx_customers_created_at ON customers(created_at);
-- ------------------------------------------------------------------------------
-- TABLE: products
-- ------------------------------------------------------------------------------
CREATE TABLE products (
product_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sku VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT,
base_price NUMERIC(10, 2) NOT NULL CHECK (base_price >= 0),
cost_price NUMERIC(10, 2) CHECK (cost_price >= 0),
currency CHAR(3) DEFAULT 'USD',
stock_quantity INT DEFAULT 0 CHECK (stock_quantity >= 0),
weight_kg NUMERIC(6, 3),
dimensions_cm VARCHAR(50),
category_id INT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_is_active ON products(is_active);
-- ------------------------------------------------------------------------------
-- TABLE: orders (Primary target for COUNT(*) bottlenecks)
-- ------------------------------------------------------------------------------
CREATE TABLE orders (
order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL REFERENCES customers(customer_id) ON DELETE CASCADE,
status VARCHAR(30) DEFAULT 'pending' CHECK (status IN (
'pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded'
)),
subtotal_amount NUMERIC(12, 2) NOT NULL,
tax_amount NUMERIC(10, 2) DEFAULT 0.00,
shipping_amount NUMERIC(10, 2) DEFAULT 0.00,
discount_amount NUMERIC(10, 2) DEFAULT 0.00,
total_amount NUMERIC(12, 2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
payment_method VARCHAR(50),
payment_status VARCHAR(20) DEFAULT 'unpaid',
shipping_address JSONB,
notes TEXT,
channel VARCHAR(50) DEFAULT 'web',
order_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
fulfilled_at TIMESTAMP WITH TIME ZONE,
cancelled_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_order_date ON orders(order_date);
CREATE INDEX idx_orders_channel_status ON orders(channel, status);
-- ------------------------------------------------------------------------------
-- TABLE: order_items
-- ------------------------------------------------------------------------------
CREATE TABLE order_items (
item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(order_id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES products(product_id),
quantity INT NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(10, 2) NOT NULL,
discount_percent NUMERIC(5, 2) DEFAULT 0.00,
total_price NUMERIC(12, 2) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);
GRANT ALL PRIVILEGES ON DATABASE aqp_ecommerce TO aqp_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO aqp_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO aqp_user;
Deep Dive: Understanding Adaptive Sampling (Figure 3 Explained)
Figure 3: Adaptive Sampling Flowchart – Starting with 0.01% of rows, the algorithm progressively increases sample size until the variance falls below a threshold.
The Logic Behind Adaptive Sampling
Look at that first blue box in Figure 3: "Sample 0.01%." This isn't an arbitrary number—it's the result of years of statistical research5. When you're dealing with a billion-row table, 0.01% still gives you 100,000 rows to work with. That's enough data to get an initial sense of what's happening, but small enough to process in milliseconds.
The orange box "Measure variance" calculates the coefficient of variation (CV). If CV is below a threshold—typically around 0.05 or 5%—the algorithm says, "Great, I'm confident enough." If CV is above that threshold, it means the sample is too noisy, and we need more data10.
The system remembers that WHERE region = 'Antarctica' always has high variance and starts with a larger sample next time. The system adapts based on the variance of past queries, elevating adaptive sampling from a simple heuristic to a highly adaptive statistical engine. This level of adaptability works hand-in-hand with modern database workload forecasting to prepare the engine for traffic surges.
Phase 2: Implementing the AQP Proxy Application
Now that our database is ready, we need the "brain" of the operation. Below is the complete Python implementation of the Proxy shown in Figure 4. This FastAPI application intercepts queries, applies HyperLogLog or Adaptive Sampling, and injects confidence intervals.
Snippet 2: Python AQP Proxy Implementation
View full source code (Click to expand)
# ==============================================================================
# ALGORITHM-DRIVEN APPROXIMATE QUERY PROCESSING (AQP) PROXY
# ==============================================================================
import time, math, hashlib, asyncpg, logging, re
from typing import Dict, Any, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger("AQP_Proxy")
# Use environment variables for passwords in production!
DB_CONFIG = {
"host": "localhost", "port": 5432,
"user": "aqp_user", "password": "<strong-password>",
"database": "aqp_ecommerce", "min_size": 5, "max_size": 50
}
pool: Optional[asyncpg.Pool] = None
class HyperLogLog:
"""Pure Python implementation of HyperLogLog for cardinality estimation."""
def __init__(self, precision: int = 14):
self.precision = precision
self.num_registers = 1 << precision
self.registers = [0] * self.num_registers
self.alpha = 0.7213 / (1 + 1.079 / self.num_registers)
def _hash_value(self, value: str) -> int:
return int(hashlib.md5(value.encode('utf-8')).hexdigest(), 16)
def add(self, value: str) -> None:
hash_value = self._hash_value(value)
register_index = hash_value & (self.num_registers - 1)
shifted = hash_value >> self.precision
leading_zeros = (64 - self.precision) - shifted.bit_length() + 1 if shifted > 0 else 64 - self.precision
self.registers[register_index] = max(self.registers[register_index], leading_zeros)
def estimate(self) -> int:
m = self.num_registers
indicator = sum(2.0 ** (-reg) for reg in self.registers)
raw_estimate = self.alpha * (m ** 2) / indicator
if raw_estimate <= 2.5 * m:
zeros = self.registers.count(0)
if zeros > 0: return round(m * math.log(m / zeros))
return round(raw_estimate)
class AdaptiveSampler:
"""Implements adaptive sampling for filtered COUNT(*) queries."""
def __init__(self, target_cv: float = 0.05, max_sample_pct: float = 5.0):
self.target_cv = target_cv
self.max_sample_pct = max_sample_pct
self.initial_sample_pct = 0.01
async def estimate_count(self, conn: asyncpg.Connection, table: str, where_clause: str) -> Dict[str, Any]:
current_sample_pct = self.initial_sample_pct
for iteration in range(1, 11):
start_time = time.time()
query = f"SELECT COUNT(*) as sample_count FROM {table} TABLESAMPLE BERNOULLI({current_sample_pct}) {where_clause}"
row = await conn.fetchrow(query)
sample_count = row['sample_count']
estimated_count = int(sample_count * (100.0 / current_sample_pct))
sample_size_approx = int(estimated_count * (current_sample_pct / 100.0))
if sample_size_approx > 0 and sample_count > 0:
p = sample_count / sample_size_approx
std_dev = math.sqrt(sample_size_approx * p * (1 - p))
cv = std_dev / sample_count
else:
cv = 1.0
if cv <= self.target_cv:
margin_of_error = 1.96 * std_dev * (100.0 / current_sample_pct)
return {
"estimated_count": estimated_count,
"confidence_interval": [max(0, int(estimated_count - margin_of_error)), int(estimated_count + margin_of_error)],
"confidence_level": 0.95,
"error_margin_percent": (margin_of_error / estimated_count) * 100,
"final_sample_percent": current_sample_pct,
"execution_time_ms": (time.time() - start_time) * 1000
}
multiplier = (cv / self.target_cv) ** 2 if cv > 0 else 2
current_sample_pct = min(current_sample_pct * multiplier, self.max_sample_pct)
return {"estimated_count": estimated_count, "warning": "Max iterations reached."}
app = FastAPI(title="AQP Proxy", version="1.0.0")
sampler = AdaptiveSampler()
@app.on_event("startup")
async def startup_event():
global pool
pool = await asyncpg.create_pool(**DB_CONFIG)
@app.post("/execute")
async def execute_query(request: BaseModel):
sql = request.sql.strip()
is_count_star = bool(re.search(r'COUNT\s*\(\s*\*\s*\)', sql, re.IGNORECASE))
async with pool.acquire() as conn:
if is_count_star:
table_match = re.search(r'FROM\s+(\w+)', sql, re.IGNORECASE)
where_match = re.search(r'WHERE\s+(.*)', sql, re.IGNORECASE | re.DOTALL)
table = table_match.group(1) if table_match else "orders"
where_clause = f"WHERE {where_match.group(1)}" if where_match else ""
return await sampler.estimate_count(conn, table, where_clause)
else:
rows = await conn.fetch(sql)
return {"mode": "exact", "data": [dict(row) for row in rows]}
Deep Dive: AQP Architecture (Figure 4 Explained)
Figure 4: Algorithm-Driven AQP Architecture – A proxy rewrites queries, leverages HyperLogLog, adaptive sampling, and learned models, and returns results with confidence intervals.
The Complete Architecture: A Comprehensive System Design
The proxy sits between your application and database, acting as an intelligent gatekeeper. The Query Rewriter parses the SQL AST to understand the query structure15. It checks its historical database to retrieve learned metadata about which technique works best for each query pattern11.
The Confidence Injector enriches the result with metadata, providing transparency and building trust12. The feedback loop from query logs back to the AQP Engine is what makes this system highly adaptive, allowing it to adjust to workload changes without human intervention.
Phase 3: Production Deployment, Monitoring, and Observability on Ubuntu
A brilliant algorithm is useless if it crashes at 3 AM. This section covers the complete production deployment on Ubuntu, including systemd service management, Prometheus metrics export, and automated health checks. To prevent bottlenecks in production, configuring memory is crucial, and you can stop guessing your buffer pool size by aligning your cache settings dynamically.
Snippet 3: Ubuntu Production Deployment & Observability
View full source code (Click to expand)
#!/bin/bash
# ==============================================================================
# SCRIPT: deploy_ubuntu_production.sh
# ==============================================================================
echo "[INFO] Creating dedicated 'aqp' system user..."
sudo useradd -r -s /bin/false -m -d /opt/aqp aqp_user
sudo chown -R aqp_user:aqp_user /opt/aqp
echo "[INFO] Creating systemd service file for AQP Proxy..."
cat <<EOF | sudo tee /etc/systemd/system/aqp-proxy.service
[Unit]
Description=Algorithm-Driven Approximate Query Processing (AQP) Proxy
After=network.target postgresql.service
[Service]
Type=simple
User=aqp_user
Group=aqp_user
WorkingDirectory=/opt/aqp
ExecStart=/opt/aqp/venv/bin/uvicorn aqp_proxy:app --host 0.0.0.0 --port 8080 --workers 4
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=strict
[Install]
WantedBy=multi-user.target
EOF
sudo mkdir -p /var/log/aqp
sudo chown aqp_user:aqp_user /var/log/aqp
sudo systemctl daemon-reload
sudo systemctl enable aqp-proxy.service
sudo systemctl start aqp-proxy.service
echo "[INFO] Configuring UFW firewall..."
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow from 10.0.0.0/8 to any port 8080 proto tcp
sudo ufw --force enable
echo "[SUCCESS] Production deployment complete."
# ==============================================================================
# PROMETHEUS METRICS EXPORTER FOR AQP PROXY
# ==============================================================================
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from fastapi import Response
QUERY_COUNT = Counter('aqp_queries_total', 'Total queries processed', ['mode', 'status'])
QUERY_LATENCY = Histogram('aqp_query_duration_seconds', 'Query execution time', ['mode'])
APPROXIMATION_ERROR = Gauge('aqp_approximation_error_percent', 'Error margin %', ['technique'])
@app.get("/metrics")
async def metrics():
return Response(content=generate_latest(), media_type="text/plain")
def record_query(mode: str, status: str, latency: float):
QUERY_COUNT.labels(mode=mode, status=status).inc()
QUERY_LATENCY.labels(mode=mode).observe(latency)
Comprehensive Comparison: All Approaches Side-by-Side
| Parameter | COUNT(*) Exact | EXPLAIN Estimate | Counter Table | Redis Cache | HyperLogLog | Adaptive Sampling | AQP (Combined) |
|---|---|---|---|---|---|---|---|
| Response Time | Seconds to Hours | ~0.001 seconds | ~0.001 seconds | <1ms | 10-50ms | 50-500ms | 50-200ms |
| Accuracy | 100% exact | ±10-50% | 100% exact | 100% (if consistent) | 98-99.5% | 95-99.9% | 98-99.9% |
| WHERE Clause Support | Full scan | Estimate only | No | No | Limited | Full support | Full support |
| Infrastructure Change | None | None | Triggers | Redis + app changes | Extension install | None (native) | Proxy deployment |
| Confidence Intervals | N/A (exact) | No | No | No | Yes (theoretical) | Yes (empirical) | Yes (combined) |
| Best For | Compliance, financial | Quick estimates | Simple counts | High-throughput | DISTINCT counts | Filtered counts | All use cases |
The Bottom Line
"Good enough" is often better than "exact but too late."
Your users don't care if the dashboard says "1.2M" instead of "1,234,567." They care that the page loads in milliseconds instead of minutes.
Stop counting every row. Start approximating intelligently.
Frequently Asked Questions
Why is COUNT(*) so slow on large tables?
InnoDB and PostgreSQL use Multi-Version Concurrency Control (MVCC), which means they cannot maintain a single global row count. When you run COUNT(*), the database must scan every leaf node of an index and check row visibility for each row2.
How accurate are algorithm-driven approximations?
In controlled benchmarks, HyperLogLog can achieve <2% error for distinct counts using just 16KB of memory4. Adaptive sampling can achieve <1% error for filtered counts, though actual accuracy depends on data distribution5.
Do I need to change my application code?
No. The recommended deployment model uses a proxy that intercepts SQL queries and rewrites them transparently. This requires zero code changes and acts as a zero-code fix for ORM queries that are notoriously hard to optimize14.
What is the difference between COUNT(*) and COUNT(DISTINCT)?
COUNT(*) counts every single row in a table, including duplicates. COUNT(DISTINCT column) counts only the unique values in a specific column. Approximations like HyperLogLog are especially useful and powerful for the latter.
Can I use approximations for financial or legal reporting?
No. Approximations are designed for dashboards, analytics, and user interfaces where speed is critical and a tiny margin of error is acceptable. For financial audits, legal compliance, or exact inventory counts, you must always use exact queries.
How does the system know when to use an exact count vs. an approximate one?
The proxy can be configured with specific rules. For example, you can tag certain critical queries or specific users as requiring "exact" precision, while allowing all other standard dashboard queries to use fast approximations.
Will this affect the accuracy of my historical data trends?
Because the error margin is typically less than 1-2%, historical trends remain highly accurate. The system also provides a confidence interval, so you know exactly how reliable the estimate is at any given moment.
What happens if the database schema changes?
The proxy reads the SQL structure dynamically. However, major schema changes might require updating the proxy's configuration or rewriting the specific SQL queries your application sends.
Is HyperLogLog a new technology?
No, it was invented in 2007 by Philippe Flajolet and his colleagues. It has been widely adopted in modern databases like Redis, PostgreSQL, and Google BigQuery for its incredible efficiency and low memory usage.
Glossary of Terms for Non-Technical Readers
Database terminology can be intimidating. Here is a simple, jargon-free guide to the key concepts discussed in this article:
- 1. Approximate Query Processing (AQP)
- A technique databases use to give you a highly accurate estimate much faster, rather than taking the time to count every single item perfectly.
- 2. Multi-Version Concurrency Control (MVCC)
- A behind-the-scenes system that lets multiple people use the database at the exact same time without their work interfering with each other.
- 3. HyperLogLog (HLL)
- A clever mathematical shortcut that allows computers to count millions of unique items using very little memory, with a tiny, predictable margin of error.
- 4. Adaptive Sampling
- A smart guessing method. The computer looks at a tiny piece of the data first; if it isn't confident enough in the guess, it automatically looks at a slightly larger piece until it is sure.
- 5. Coefficient of Variation (CV)
- A statistical measurement that tells us how "noisy" or spread out our data sample is. A lower number means our estimate is much more reliable.
- 6. Confidence Interval
- A range that tells you how accurate an estimate is. For example, instead of just saying "1 million," it says "1 million, plus or minus 10,000."
- 7. Database Index
- Just like the index at the back of a textbook, this helps the database find specific information instantly without having to read every single page.
- 8. Leaf Node
- The very bottom level of a database index where the actual pointers to the data records are stored.
- 9. Buffer Pool
- A reserved, ultra-fast area in the computer's memory (RAM) where the database keeps frequently used information to speed up access. For a deeper understanding of memory allocations, consult our database caching guide.
- 10. Buffer Pool Pollution
- When a massive, slow query forces the database to kick out useful, frequently accessed data from the fast memory to make room, which slows down everything else.
- 11. Query Rewriter
- A background tool that intercepts a slow database request and automatically translates it into a faster, approximate version before sending it to the database.
- 12. Sidecar Proxy
- A helper application that sits right next to your main software to handle tasks like rewriting queries, meaning you don't have to rewrite your main application's code.
- 13. Cardinality
- A fancy word for "uniqueness." It measures how many different items are in a set, like counting how many unique customers bought something, rather than the total number of purchases.
- 14. Selectivity
- A measure of how well a filter narrows down the data. A highly selective filter (like "find the one user named John Smith") returns very few rows, while a broad filter (like "find all active users") returns many.
- 15. AQP Engine
- The core mathematical component of the system that actually performs the calculations to generate those fast, approximate answers.
References
- Percona 2025 DBA Survey — State of Database Performance. Percona, 2025.
- PostgreSQL Documentation — "Multi-Version Concurrency Control (MVCC)." PostgreSQL Global Development Group, Current.
- Kraska, Tim, et al. — "AI Meets Database: Approximate Query Processing with Machine Learning." Proceedings of the VLDB Endowment, 2020.
- Flajolet, Philippe, et al. — "HyperLogLog: The Analysis of a Near-Optimal Cardinality Estimation Algorithm." 2007.
- Peng, Xuanang, et al. — "Adaptive Sampling for Approximate Query Processing." SIGMOD, 2024.
- MIT CSAIL — "Learned Query Optimization." MIT, 2023.
- MySQL Documentation — "InnoDB Storage Engine." Oracle Corporation, Current.
- Cochran, William G. — Sampling Techniques. 3rd ed., John Wiley & Sons, 1977.
- Marcus, Ryan, et al. — "Learning to Optimize Join Queries With Deep Reinforcement Learning." 2023.
- Hellerstein, Joseph M., et al. — "The Case for Approximate Query Processing." 2021.
- Google Cloud Documentation — "Deploying Database Proxies for Query Optimization." Current.
- PostgreSQL Documentation — "SQL Command Syntax and AST Parsing." Current.
: