Data Analytics at Scale
Query engines and analytical processing for petabyte-scale data
From OLTP to OLAP at Scale
Traditional databases excel at transactional workloads (OLTP), thousands of small, fast queries updating individual records. But when you need to analyze billions of rows to answer "how many customers purchased in Q4?" or "what's our revenue trend by region?", you need OLAP (Online Analytical Processing). Modern query engines like Presto, Apache Spark, and Druid can scan petabytes of data in seconds by distributing work across hundreds of machines. Understanding these tools is critical for building data-driven applications at scale.
OLTP vs OLAP: Understanding the Difference
OLTP (Transactional)
Online Transaction Processing, designed for day-to-day operations
- Query Pattern: Small, fast queries (milliseconds)
- Operations: INSERT, UPDATE, DELETE single records
- Volume: Thousands of concurrent users
- Data Touched: Few rows at a time
- Examples: E-commerce checkout, bank transfers, user login
- Databases: PostgreSQL, MySQL, Oracle
OLAP (Analytical)
Online Analytical Processing, designed for business intelligence
- Query Pattern: Complex, long-running (seconds to minutes)
- Operations: SELECT with aggregations (SUM, AVG, GROUP BY)
- Volume: Dozens of analysts running queries
- Data Touched: Millions to billions of rows
- Examples: Sales reports, trend analysis, dashboards
- Tools: Presto, Spark, Snowflake, BigQuery
Column-Oriented Storage: The Secret to Speed
The single most important optimization for analytical queries is storing data by column instead of by row.
Row-Oriented Storage (Traditional)
Stored on disk: Row 1: [id=1, name="Alice", age=30, salary=80000, dept="Eng"] Row 2: [id=2, name="Bob", age=25, salary=60000, dept="Sales"] Row 3: [id=3, name="Charlie", age=35, salary=90000, dept="Eng"] ... Query: SELECT AVG(salary) FROM employees WHERE dept = 'Eng'; Problem: Must read ALL columns for ALL rows (name, age, etc.) even though we only need salary and dept!
For 1 billion rows Γ 50 bytes = 50GB read from disk
Column-Oriented Storage (Analytical)
Stored on disk: dept column: ["Eng", "Sales", "Eng", "Eng", "Sales", ...] salary column: [80000, 60000, 90000, 75000, 55000, ...] name column: ["Alice", "Bob", "Charlie", ...] (not needed) age column: [30, 25, 35, ...] (not needed) Query: SELECT AVG(salary) FROM employees WHERE dept = 'Eng'; Optimization: Only read dept and salary columns! Compression: "Eng" repeated 1M times compresses to few bytes
For 1 billion rows Γ 8 bytes (2 columns) = 8GB read (6x faster!)
With compression: ~1GB (50x faster!)
Compression Benefits
ποΈ Run-Length Encoding
["Eng", "Eng", "Eng"] β [("Eng", 3)]
Perfect for sorted columns
π Dictionary Encoding
"Engineering" β 1, "Sales" β 2
Store IDs instead of strings
π’ Bit Packing
Store age (0-100) in 7 bits instead of 32
4.5x compression
Presto (Trino): Distributed SQL Query Engine
Query petabytes across data lakes with standard SQL
Presto (now called Trino) is a distributed SQL query engine developed at Facebook. It runs queries across multiple data sources (S3, HDFS, Cassandra, MySQL) without moving data. Perfect for ad-hoc analytics and data lake exploration.
Architecture: Coordinator + Workers
βββββββββββββββββββββββββββββββββββββββββββββββββββ β PRESTO CLUSTER ARCHITECTURE β βββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β ββββββββββββββββββββ β β β Coordinator β (Receives query, plans) β β β - Parse SQL β β β β - Optimize β β β β - Schedule β β β ββββββββββ¬ββββββββββ β β β β β β Distributes tasks β β βΌ β β ββββββββββ΄ββββββββββ¬βββββββββββββββββββ¬ββββ β β β β β β β β βΌ βΌ βΌ β β β βββββββββββ βββββββββββ βββββββββββ β β β βWorker 1 β βWorker 2 β βWorker N β β β β βScan S3 β βScan S3 β βScan S3 β β β β β1M rows β β1M rows β β1M rows β β β β ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ β β β β β β β β β ββββββββββββββββΌβββββββββββββββ β β β β β β β βΌ β β β ββββββββββββββββ β β β β Aggregate β β β β β & Return β β β β ββββββββββββββββ β β βββββββββββββββββββββββββββββββββββββββββββββββββββ
Results streamed back through coordinator
Key Features
π Federated Queries
Query across S3, Postgres, Cassandra in single SQL statement
β‘ In-Memory Processing
Pipelined execution, no intermediate disk writes
π ANSI SQL
Standard SQL, no new language to learn
π Low Latency
Interactive queries, seconds instead of hours
Example: Querying Data Lake (Parquet on S3)
-- Define table pointing to S3 Parquet files
CREATE TABLE sales (
order_id BIGINT,
customer_id BIGINT,
product_id BIGINT,
amount DECIMAL(10,2),
order_date DATE
)
WITH (
format = 'PARQUET',
external_location = 's3://my-data-lake/sales/'
);
-- Query 1 billion rows across 100 workers
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(amount) AS revenue,
COUNT(DISTINCT customer_id) AS customers
FROM sales
WHERE order_date >= DATE '2024-01-01'
GROUP BY 1
ORDER BY 1;β’ 100 workers scan S3 in parallel (10M rows each)
β’ Column pruning: Only reads order_date, amount, customer_id
β’ Predicate pushdown: Skips Parquet files before 2024-01-01
β’ Query time: ~15 seconds for 1B rows
Result:
month | revenue | customers 2024-01-01 | 15,234,567 | 234,567 2024-02-01 | 16,789,012 | 245,891 2024-03-01 | 18,234,456 | 256,789
Example: Federated Query (Join Across Sources)
-- Join S3 data lake with live PostgreSQL SELECT c.name, c.email, SUM(s.amount) AS total_spent FROM hive.default.sales s -- S3 / Hive catalog JOIN postgresql.public.customers c -- Live Postgres ON s.customer_id = c.id WHERE s.order_date >= DATE '2024-01-01' GROUP BY c.name, c.email HAVING SUM(s.amount) > 10000 ORDER BY total_spent DESC LIMIT 100;
β’ Scans 1B rows from S3 in parallel
β’ Fetches matching customers from Postgres
β’ Performs distributed join in-memory
β’ No data movement required!
Result: Top 100 customers by spend (seconds)
When to Use Presto
- Ad-hoc exploratory queries on data lakes (S3, HDFS)
- Joining data across multiple systems without ETL
- Interactive analytics (BI tools like Tableau, Looker)
- When you need sub-minute query latency
- Data sits in Parquet/ORC/Avro format
Pros & Cons
β Pros
- Fast interactive queries (seconds)
- Standard SQL, easy adoption
- Query multiple data sources
- No data movement needed
- Scales to petabytes
- Active community (Trino)
β Cons
- Memory intensive (in-memory)
- Not great for batch ETL jobs
- Complex queries can OOM
- No built-in fault tolerance
- Requires cluster management
- Slower than Spark for iterative ML
Apache Spark: Unified Analytics Engine
Batch, streaming, ML, and graph processing in one framework
Apache Spark is a distributed computing framework that processes massive datasets using in-memory computation. Unlike Hadoop MapReduce (which writes to disk between stages), Spark keeps data in memory, making it 100x faster for iterative algorithms. It's the Swiss Army knife of big data.
Architecture: Driver + Executors
ββββββββββββββββββββββββββββββββββββββββββββββββββββ β SPARK CLUSTER ARCHITECTURE β ββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β βββββββββββββββββββ β β β Spark Driver β (Your code, DAG creation) β β β - SparkContext β β β β - DAG Schedulerβ β β β - Task β β β β Scheduler β β β ββββββββββ¬βββββββββ β β β β β β Distributes tasks β β βΌ β β ββββββββββ΄ββββββ¬βββββββββββββββ¬βββββ β β β β β β β β βΌ βΌ βΌ β β β ββββββββββββ ββββββββββββ ββββββββββββ β β βExecutor 1β βExecutor 2β βExecutor Nβ β β β4 cores β β4 cores β β4 cores β β β β16GB RAM β β16GB RAM β β16GB RAM β β β β β β β β β β β β Task 1 β β Task 3 β β Task 5 β β β β Task 2 β β Task 4 β β Task 6 β β β ββββββββββββ ββββββββββββ ββββββββββββ β β β² β² β² β β βββββββββββββββΌββββββββββββββ β β β β β Read from HDFS/S3 β ββββββββββββββββββββββββββββββββββββββββββββββββββββ
Lazy evaluation: Builds execution plan before running
Key Features
πΎ In-Memory Compute
Cache datasets in RAM, 100x faster than disk-based MapReduce
π Unified API
Batch (SQL), streaming, ML, graph, all in one framework
π Multiple Languages
Python, Scala, Java, R, SQL, choose your preference
π§ Fault Tolerant
Lineage tracking, automatically recomputes lost partitions
Example: Spark SQL (DataFrame API)
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, sum, count, date_trunc
# Initialize Spark
spark = SparkSession.builder \
.appName("Sales Analysis") \
.config("spark.executor.memory", "16g") \
.config("spark.executor.cores", "4") \
.getOrCreate()
# Read Parquet from S3 (billions of rows)
sales_df = spark.read.parquet("s3://data-lake/sales/")
# Perform aggregation (distributed across cluster)
monthly_revenue = sales_df \
.filter(col("order_date") >= "2024-01-01") \
.groupBy(date_trunc("month", "order_date").alias("month")) \
.agg(
sum("amount").alias("revenue"),
count("customer_id").alias("orders")
) \
.orderBy("month")
# Execute and show results
monthly_revenue.show()β’ Lazy evaluation: Builds execution plan first
β’ Catalyst optimizer: Pushes filters, prunes columns
β’ Tungsten execution: Generates optimized bytecode
β’ Executes in parallel across all executors
Result:
+-------------------+-----------+--------+ |month |revenue |orders | +-------------------+-----------+--------+ |2024-01-01 00:00:00|15234567.89|123456 | |2024-02-01 00:00:00|16789012.34|134567 | |2024-03-01 00:00:00|18234456.78|145678 | +-------------------+-----------+--------+
Example: Spark Streaming (Real-Time)
# Read streaming data from Kafka
stream_df = spark.readStream \
.format("kafka") \
.option("kafka.bootstrap.servers", "localhost:9092") \
.option("subscribe", "clickstream") \
.load()
# Parse JSON and aggregate in 5-minute windows
from pyspark.sql.functions import window, from_json
from pyspark.sql.types import StructType, StructField, StringType, LongType, TimestampType
schema = StructType([
StructField("timestamp", TimestampType()),
StructField("page_url", StringType()),
StructField("user_id", LongType())
])
clicks_per_page = stream_df \
.selectExpr("CAST(value AS STRING) as json") \
.select(from_json("json", schema).alias("data")) \
.select("data.*") \
.groupBy(
window("timestamp", "5 minutes"),
"page_url"
) \
.count() \
.orderBy("window", "count", ascending=False)
# Write results to dashboard (continuously)
query = clicks_per_page.writeStream \
.format("memory") \
.queryName("top_pages") \
.outputMode("complete") \
.start()β’ Micro-batch processing: 5-minute tumbling windows
β’ Stateful aggregation: Maintains window state
β’ Fault-tolerant: Checkpointing to HDFS/S3
β’ Updates continuously as new data arrives
Result: Real-time dashboard of popular pages
Example: Machine Learning (MLlib)
from pyspark.ml.feature import VectorAssembler
from pyspark.ml.regression import LinearRegression
# Load training data (millions of rows)
data = spark.read.parquet("s3://ml-data/housing/")
# Prepare features
assembler = VectorAssembler(
inputCols=["sqft", "bedrooms", "bathrooms", "age"],
outputCol="features"
)
training_data = assembler.transform(data)
# Split into training and test sets
train_data, test_data = training_data.randomSplit([0.8, 0.2], seed=42)
# Train model (distributed)
lr = LinearRegression(
featuresCol="features",
labelCol="price",
maxIter=10
)
model = lr.fit(train_data)
# Make predictions
predictions = model.transform(test_data)
predictions.select("features", "price", "prediction").show()β’ Distributed training across cluster
β’ Gradient descent parallelized
β’ Model broadcasted to executors
β’ Predictions computed in parallel
Result: Trained model + predictions on test set
When to Use Spark
- Batch ETL jobs processing terabytes daily
- Real-time streaming analytics (Kafka, Kinesis)
- Machine learning at scale (MLlib, TensorFlow)
- Iterative algorithms (PageRank, K-means)
- Complex multi-stage data pipelines
- When you need fault tolerance for long jobs
Pros & Cons
β Pros
- 100x faster than MapReduce
- Unified API (batch + streaming + ML)
- Fault tolerant (lineage tracking)
- Rich ecosystem (Spark SQL, MLlib)
- Works with existing Hadoop data
- Multiple language support
β Cons
- Memory intensive (requires tuning)
- Complex configuration (100+ params)
- Steeper learning curve than SQL
- Not great for sub-second latency
- GC pauses with large heaps
- Slower than Presto for ad-hoc queries
Apache Druid: Real-Time Analytics Database
Sub-second queries on streaming and historical data
Apache Druid is designed for real-time analytics on event streams. It ingests data continuously, pre-aggregates it, and serves queries in milliseconds. Perfect for dashboards, anomaly detection, and user-facing analytics where speed matters.
Architecture: Real-Time + Historical
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β DRUID ARCHITECTURE β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β Ingestion Layer: β β ββββββββββββ ββββββββββββ β β β Kafka βββββββΆβ Real-timeβ (Indexing Service) β β β Stream β β Nodes β Ingest + Index β β ββββββββββββ βββββββ¬βββββ β β β β β βΌ (persist) β β Storage Layer: ββββββββββββ β β ββββββββββββ βDeep β β β βHistoricalββββββββStorage β (S3/HDFS) β β βNodes β β(Segments)β Column-oriented β β ββββββ¬ββββββ ββββββββββββ β β β β β Query Layer: β β βΌ β β ββββββββββββ ββββββββββββ β β β Broker βββββββΆβ Query β β β β Nodes β β Results β (Merge + Return) β β ββββββ¬ββββββ ββββββββββββ β β β² β β β β β ββββββ΄ββββββ β β β Client β SELECT ... WHERE timestamp > now()-1h β β ββββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Cold data: Historical nodes (deep storage)
Query: Broker merges results from both
Key Features
β‘ Sub-Second Queries
Bitmap indexes + columnar storage = millisecond responses
π Real-Time Ingestion
Stream from Kafka, data queryable within seconds
π Roll-Up Aggregation
Pre-aggregate at ingestion, store summaries not raw events
π― Approximate Algorithms
HyperLogLog for COUNT(DISTINCT), 99% accurate, 1% memory
Example: Ingestion Spec (Roll-Up)
{
"type": "kafka",
"dataSchema": {
"dataSource": "clickstream",
"timestampSpec": { "column": "timestamp", "format": "iso" },
"dimensionsSpec": {
"dimensions": ["user_id", "page_url", "country", "device"]
},
"metricsSpec": [
{ "type": "count", "name": "count" },
{ "type": "longSum", "name": "session_duration", "fieldName": "duration" },
{ "type": "hyperUnique", "name": "unique_users", "fieldName": "user_id" }
],
"granularitySpec": {
"segmentGranularity": "hour", // 1 segment per hour
"queryGranularity": "minute" // Roll up to minute
}
}
}Input: 1M raw events per minute
After roll-up: ~10K rows per minute (100x compression)
Stores: aggregated metrics by (timestamp, user_id, page, country, device)
Example: Real-Time Dashboard Query
SELECT TIME_FLOOR(__time, 'PT5M') AS time_bucket, country, SUM(count) AS page_views, APPROX_COUNT_DISTINCT_DS_HLL(unique_users) AS users FROM clickstream WHERE __time >= CURRENT_TIMESTAMP - INTERVAL '1' HOUR GROUP BY 1, 2 ORDER BY 1 DESC, 3 DESC LIMIT 100;
β’ Scans pre-aggregated segments (not raw events)
β’ Bitmap indexes filter by time instantly
β’ HyperLogLog gives approximate unique users
β’ Query time: ~50ms for 1 hour of data
Result:
time_bucket |country|page_views|users 2024-03-15 14:55:00 |US |1,234,567 |45,678 2024-03-15 14:50:00 |US |1,198,234 |44,123 2024-03-15 14:55:00 |UK |567,890 |23,456
Example: Approximate COUNT DISTINCT
-- Exact count (slow - scans all events) SELECT COUNT(DISTINCT user_id) FROM clickstream; -- Scans: 1 billion rows, Takes: 30 seconds -- Approximate count (fast - uses HyperLogLog sketch) SELECT APPROX_COUNT_DISTINCT_DS_HLL(unique_users) FROM clickstream; -- Scans: pre-aggregated segments, Takes: 100ms -- Accuracy: Β±1-2% error
Exact: 45,678,912 users (30s)
Approximate: 45,234,567 users (100ms)
Error: 0.97%, acceptable for dashboards!
When to Use Druid
- User-facing analytics dashboards (need sub-second)
- Real-time monitoring and alerting
- Clickstream and event analytics
- Network telemetry and APM
- Time-series data with high cardinality
- When approximate algorithms are acceptable
Pros & Cons
β Pros
- Extremely fast (sub-second queries)
- Real-time ingestion (Kafka)
- Automatic roll-up (compression)
- High concurrency (1000+ QPS)
- Scales to petabytes
- Built-in approximate algorithms
β Cons
- Complex architecture (many components)
- No joins (denormalize at ingestion)
- Updates/deletes are expensive
- Requires careful dimension design
- High operational overhead
- Steep learning curve
Query Engine Comparison
| Feature | Presto/Trino | Apache Spark | Apache Druid |
|---|---|---|---|
| Primary Use Case | Ad-hoc SQL queries | Batch ETL + ML | Real-time dashboards |
| Query Latency | Seconds | Seconds to minutes | Milliseconds |
| Data Sources | S3, HDFS, DBs (federated) | S3, HDFS, Kafka | Kafka, S3 (pre-indexed) |
| Real-Time Streaming | β No | β Yes (micro-batch) | β Yes (true streaming) |
| Fault Tolerance | Limited (in-memory) | β Strong (lineage) | β Strong (segments) |
| Machine Learning | β No | β MLlib built-in | β No |
| Concurrency | 100s of queries | 10s of jobs | 1000s QPS |
| Setup Complexity | Medium | Medium-High | High |
Best Practices for Analytics at Scale
Never store analytics data in CSV or JSON. Always use columnar formats like Parquet or ORC.
β Parquet Benefits
- 10-100x compression
- Column pruning (read only needed columns)
- Predicate pushdown (skip entire files)
- Schema evolution support
- Universal tool support
β CSV Problems
- No compression (10x larger)
- Must read entire file
- No schema (parsing errors)
- Slow to parse (CPU intensive)
- No predicate pushdown
Partition by columns that are frequently used in WHERE clauses to enable partition pruning.
-- Bad: No partitioning s3://data-lake/sales/all_data.parquet (1TB file) Query: WHERE date = '2024-01-15' Scans: Entire 1TB file -- Good: Partition by date s3://data-lake/sales/year=2024/month=01/day=15/*.parquet Query: WHERE date = '2024-01-15' Scans: Only files in /day=15/ (~3GB)
Different engines excel at different workloads. Choose based on your access patterns.
| Access Pattern | Best Tool | Why |
|---|---|---|
| Ad-hoc exploration (analysts) | Presto | Fast, standard SQL, no setup |
| Daily ETL pipelines | Spark | Fault tolerant, handles failures |
| Real-time dashboards | Druid | Sub-second latency |
| Machine learning | Spark | MLlib integration |
| Joining multiple sources | Presto | Federated queries |
For data accessed repeatedly, caching can provide 10-100x speedup.
# Spark: Cache hot datasets in memory
df = spark.read.parquet("s3://data/orders/")
df_filtered = df.filter(col("date") >= "2024-01-01")
df_filtered.cache() # Keep in executor memory
# First query: reads from S3 (slow)
df_filtered.groupBy("region").sum("revenue").show()
# Subsequent queries: reads from memory (fast!)
df_filtered.groupBy("product").sum("revenue").show()
df_filtered.groupBy("customer_segment").sum("revenue").show()For dashboards and monitoring, approximate results are often good enough and 100x faster.
HyperLogLog (COUNT DISTINCT)
Memory: ~12KB vs full set in RAM
Accuracy: Β±2% error
Speed: 100x faster
APPROX_COUNT_DISTINCT(user_id)T-Digest (PERCENTILE)
Memory: Fixed size sketch
Accuracy: Very high
Speed: 50x faster
APPROX_PERCENTILE(latency, 0.95)Poor resource allocation is the #1 cause of slow queries. Monitor and adjust.
Key Spark Configurations:
spark.executor.memory, Too low = OOM, too high = GC pausesspark.executor.cores, More cores = more parallelismspark.sql.shuffle.partitions, Default 200, increase for big dataspark.memory.fraction, 60% memory for execution vs storage
Common Pitfalls to Avoid
β Data Skew (Hot Partitions)
Problem: One partition has 90% of data, others idle
Solution: Use salting, add random prefix to skewed keys. Example: Instead of GROUP BY user_id, use GROUP BY CONCAT(user_id, '_', RAND(1-10))
β Reading Too Much Data
Problem: SELECT * from billions of rows
Solution: Always use WHERE clauses with partition keys. Use LIMIT for exploration. Project only needed columns (SELECT specific_cols, not *).
β Too Many Small Files
Problem: 1 million files of 1KB each (metadata overhead)
Solution: Compact files regularly. Use coalesce() or repartition() to merge small files into 128MB-1GB files.
β Not Using Broadcast Joins
Problem: Shuffling 1TB table to join with 1MB lookup table
Solution: Broadcast small tables (<100MB) to all executors. Spark: broadcast(small_df). Avoids expensive shuffle.
β Ignoring Query Plans
Problem: Running slow queries without understanding why
Solution: Always EXPLAIN your queries. Look for full table scans, missing partition pruning, or expensive shuffles.
β Memory Configuration Issues
Problem: OutOfMemory errors or excessive GC pauses
Solution: Tune executor memory and cores. Increase parallelism (more, smaller tasks). Use persist() with MEMORY_AND_DISK for large datasets.
Real-World Architecture Example
E-Commerce Analytics Platform
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β DATA FLOW ARCHITECTURE β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β Ingestion Layer: β β ββββββββββββ ββββββββββββ ββββββββββββ β β βApp Logs βββββββΆβ Kafka βββββββΆβ Spark β β β βEvents β β Topics β βStreaming β β β ββββββββββββ ββββββββββββ ββββββ¬ββββββ β β β β β βΌ β β Storage Layer (Data Lake): β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β S3 / HDFS (Partitioned Parquet) β β β β /events/year=2024/month=03/day=15/*.parquet β β β β - Raw events (keep 90 days) β β β β - Aggregated (keep 2 years) β β β ββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ β β β β β Processing Layer: β β ββββββββββββββββββ¬ββββββββββββββββββ β β βΌ βΌ βΌ β β ββββββββββ βββββββββββ ββββββββββββ β β β Spark β β Presto β β Druid β β β βBatch β βAd-hoc β βReal-time β β β βETL β βQueries β βDashboard β β β ββββββ¬ββββ ββββββ¬βββββ ββββββ¬ββββββ β β β β β β β Serving Layer: β β β β βΌ βΌ βΌ β β βββββββββββ βββββββββββ ββββββββββββ β β β Data β β Jupyter β β Grafana β β β βWarehouseβ βNotebooksβ βDashboard β β β βββββββββββ βββββββββββ ββββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Layer Responsibilities:
Kafka: Collects 1M events/sec from web/mobile apps
Spark Streaming: Writes Parquet to S3 every 5 minutes
Spark Batch: Nightly ETL aggregations (revenue by product)
Presto: Data scientists run ad-hoc queries on S3
Druid: Powers real-time executive dashboard (sub-second)
Managed Services vs Self-Hosted
Running these systems yourself requires significant DevOps expertise. Consider managed services.
| Technology | AWS | Google Cloud | Azure |
|---|---|---|---|
| Presto/Trino | Amazon Athena | - | - |
| Spark | EMR, Glue | Dataproc | HDInsight, Databricks |
| Druid | - | - | - |
| General OLAP | Redshift | BigQuery | Synapse Analytics |
β Managed Services
- No cluster management
- Auto-scaling built-in
- Pay per query (serverless)
- Automatic updates/patches
- Built-in monitoring
β Self-Hosted
- Full control over config
- Lower cost at huge scale
- No vendor lock-in
- But requires expertise
- 24/7 on-call needed
Key Takeaways
- Column-oriented storage is fundamental, use Parquet/ORC, not CSV
- Choose the right tool, Presto for ad-hoc, Spark for ETL/ML, Druid for dashboards
- Partition intelligently, align with query patterns, avoid too many small files
- Monitor and tune, query plans, resource allocation, and data skew
- Use approximations, HyperLogLog, T-Digest when exact isn't needed
- Consider managed services, unless you have dedicated ops team