Building InfluxDB 3.0
(and other systems) without starting from “scratch”
with Apache DataFusion
| © Copyright 2024, InfluxData
1
Andrew Lamb
Staff Engineer InfluxData
> 20 21 😱years in enterprise software development
Oracle: Database (2 years)
DataPower: XSLT compiler (2 years)
Vertica: DB / Query Optimizer (6 years)
Nutonian/DataRobot: ML Startups (7 years)
InfluxData: InfluxDB 3.0, Arrow, DataFusion (4 years)
MIT VI-2 2002, MEng 2003
i n f l u x d a t a . c o m
| © Copyright 2024, InfluxData
2
Outline
| © Copyright 2024, InfluxData
3
“One Size Fits All”
An Idea Whose Time Has Come and Gone
Stonebraker & Çetintemel (2005)
| © Copyright 2024, InfluxData
4
“One Size Fits All”: An Idea Whose Time Has Come and Gone*
Time
Adoption
Ease of Building
3.0
* Stonebraker & Çetintemel (2005)
| © Copyright 2024, InfluxData
5
It is hard (expensive) to build a new databases
Database company money raised
Source: https://www.crunchbase.com
I did this at Vertica too
| © Copyright 2024, InfluxData
6
“We can do it with the Apache Arrow Ecosystem”
Evan (CEO)
Paul (CTO)
| © Copyright 2024, InfluxData
7
Implementation timeline for a new Database
8
Client
API
In memory
storage
In-Memory
filter + aggregation
Durability / persistence
Metadata Catalog +
Management
Query
Language
Parser
Optimized /
Compressed storage
Execution on
Compressed
Data
Joins!
Additional Client
Languages
Outer Joins
Subquery support
More advanced analytics
Cost based optimizer
Out of core algorithms
Storage Rearrangement
Heuristic Query Planner
Arithmetic expressions
Date / time Expressions
Concurrency
Control
Data Model /
Type System
Distributed query execution
Resource
Management
“Lets Build
a Database”
🤔
“Ok now this is pretty good”
😐
“Look mom!
I have a database!”
😃
Online recovery
Window functions
| © Copyright 2024, InfluxData
8
Bonus: Ecosystem Integration
In addition to lowering engineering costs…💰
Building on DataFusion (via Arrow / Flight / Parquet) gives instant ecosystem integration
(maybe this is really another example of lower costs 🤔)
| © Copyright 2024, InfluxData
9
Ecosystem Integration
Where to connect?
Pandas
Drill
Impala
HBase
Kudu
Cassandra
Parquet
Spark
MyNewDB
| © Copyright 2024, InfluxData
10
Ecosystem Integration
MyNewDB
Flight
Use a common, efficient protocol.
Spark
Pandas
Drill
Impala
HBase
Kudu
Cassandra
Parquet
| © Copyright 2024, InfluxData
11
Ecosystem Integration
Object Store
“Lakehouse”
Data Lives in open formats on (very) cheap object storage
Specialized System 3
Specialized System 2
Specialized System 1
⇒ Native integration with Arrow/Flight/Parquet required for next gen systems
| © Copyright 2024, InfluxData
12
Intro to DataFusion
| © Copyright 2024, InfluxData
13
Apache 2.0 Licensed
Top Level Project of Apache Software Foundation (as of April 2024)
| © Copyright 2024, InfluxData
14
What: DataFusion is LLVM for Databases
Analytic Application
Domain Specific Language
Specialized Database
Application Logic
Catalog
Analysis Engine
Multiple SQL Dialects
Data Flow Analysis
Custom Operators
File System Interface
…
Clang
Rust
Julia
Swift
C/C++ frontend
LLVM
…
Rustlang frontend
LLVM
Julia frontend
LLVM
Swift frontend
LLVM
| © Copyright 2024, InfluxData
15
Architecture
Design Goals:
Results for Users
| © Copyright 2024, InfluxData
16
DataFusion / Query Engine: Input / Output
17
Data Batches
SQL Query
SELECT status, COUNT(1)
FROM http_api_requests_total
WHERE path = '/api/v2/write'
GROUP BY status;
Data Batches
DataFrame
ctx.read_table("http")?
.filter(...)?
.aggregate(..)?;
Catalog information:
tables, schemas, etc
| © Copyright 2024, InfluxData
17
Architecture
SQL
Front Ends
DataFrame
LogicalPlan
ExecutionPlan
Plan Representations and Rewrites
Expression Eval
Optimizations / Transformations
Optimizations / Transformations
HashAggregate
Sort
…
Execution Engine
Join
Catalog and
Data Sources
Parquet
CSV
…
Extension
Catalog / Table
Extension
Frontend
Extension
LogicalPlan Rewrite
Extension ExecutionPlan Rewrite
Extension
Stream
Extension Node
Extension Node
Streams
| © Copyright 2024, InfluxData
18
Case Study: InfluxDB 3.0
| © Copyright 2024, InfluxData
19
Time Series Databases: Crash Course
Specialized for storing data with times (obviously)
Key Properties:
Examples: InfluxDB (open), Azure Time Series Insights (closed), Facebook Gorilla (closed), Google Monarch (closed), Timescale (open), AWS Timestream (closed), Graphite Whisper / Grafana (open), …
| © Copyright 2024, InfluxData
20
InfluxDB 3.0 Requirements
| © Copyright 2024, InfluxData
21
How InfluxDB 3.0 uses DataFusion
Rationale
All queries and write path (parquet creation + compaction) use DataFusion + Arrow
| © Copyright 2024, InfluxData
22
InfluxDB 3.0 Architecture
| © Copyright 2024, InfluxData
23
Query Processing in InfluxDB 3.0
Storage gRPC Frontend
SQL Frontend
Optimization
(storage pruning, pushdown, etc)
Physical Planning
Execution
gRPC output
Arrow Flight
Query Input
Client / Language Specific Frontends
Shared Planning, Execution
Phases, based on DataFusion
Output RecordBatches
Client Specific
Output formats
read_group(..)
SELECT …
FROM …
DataFusion
LogicalPlan
Arrow
Record Batches
Reorg Frontend
compact_plan(..)
ParquetWriter
SeriesFrame
...
FlightData
Write to Parquet files
DataFusion
LogicalPlan
DataFusion
ExecutionPlan
InfluxQL Frontend
SELECT … FROM … GROUP BY *
| © Copyright 2024, InfluxData
24
DataFusion extensions used in InfluxDB 3.0
SQL
Front Ends
DataFrame
LogicalPlan
ExecutionPlan
Plan Representations and Rewrites
Expression Eval
Optimizations / Transformations
Optimizations / Transformations
HashAggregate
Sort
…
Execution Engine
Join
Catalog and
Data Sources
Parquet
CSV
…
Extension
Catalog / Table
Extension
Frontend
Extension
LogicalPlan Rewrite
Extension ExecutionPlan Rewrite
Extension
Stream
Extension Node
Extension Node
Streams
✅
✅
✅
✅
✅
✅
✅
| © Copyright 2024, InfluxData
25
Ingester
Persists data to object store as Parquet (sorted + deduplicated)
Object Store
Ingester
Ingester
…
3. Periodically writes data buffer as sorted parquet files to object store
…
…
write path
1. Incoming line protocol
weather,location=us-east temperature=82,humidity=67 1465839830100400200�weather,location=us-midwest temperature=82,humidity=65 1465839830100400200�weather,location=us-west temperature=70,humidity=54 1465839830100400200�weather,location=us-east temperature=83,humidity=69 1465839830200400200�weather,location=us-midwest temperature=87,humidity=78 1465839830200400200�weather,location=us-west temperature=72,humidity=56 1465839830200400200�weather,location=us-east temperature=84,humidity=67 1465839830300400200�weather,location=us-midwest temperature=90,humidity=82 1465839830400400200�weather,location=us-west temperature=71,humidity=57 1465839830400400200
weather,location=us-east temperature=82,humidity=67 1465839830100400200�weather,location=us-midwest temperature=82,humidity=65 1465839830100400200�weather,location=us-west temperature=70,humidity=54 1465839830100400200�weather,location=us-east temperature=83,humidity=69 1465839830200400200�weather,location=us-midwest temperature=87,humidity=78 1465839830200400200�weather,location=us-west temperature=72,humidity=56 1465839830200400200�weather,location=us-east temperature=84,humidity=67 1465839830300400200�weather,location=us-midwest temperature=90,humidity=82 1465839830400400200�weather,location=us-west temperature=71,humidity=57 1465839830400400200
…
…
2. Parsed + Buffered in Arrow Compatible* format
* appendable until queried and then snapshotted
| © Copyright 2024, InfluxData
26
Ingester: Persist
DataFusion use: Sort and Deduplicate and write Parquet
Object Store
…
…
Ingester
Buffer
Scan
Sort
Dedupe
DataFusion Plan
1. Buffered data fed to plan
…
parquet-rs
writer
2. Plan output fed to parquet writer*
3. Parquet written to object store
* In the progress of migrating to use DataFusion parquet writer (to get parallelized encoding)
| © Copyright 2024, InfluxData
27
Ingester: Partition Pruning
DataFusion use: PruningPredicate for partition pruning
Buffer is partitioned (e.g. by user tag + time)
Stores min/max metadata
…
…
time: min/max
partn: min/max
…
time: min/max
partn: min/max
…
time: min/max
partn: min/max
Querier
SELECT … WHERE
time > now() - ‘1 minute’ AND
region = ‘us-east-1’
User SQL query
1. Querier parses / analyzes user SQL
time > 123456789
AND region = ‘us-east-1’
2. Predicate sent to ingester
4. Only matching partitions returned
Ingester
3. min/max evaluated using PruningPredicate
| © Copyright 2024, InfluxData
28
Compactor
DataFusion use: Merge / Deduplicate and write Parquet
Object Store
…
…
Compactor
* In the progress of migrating to use DataFusion parquet writer (to get parallelized encoding)
1. Multiple parquet files read from object store
…
parquet-rs
writer
3. Plan output fed to parquet writer*
DataFusion Plan
Read Parquet
Merge
Dedupe
2. DataFusion plan merges and deduplicates
| © Copyright 2024, InfluxData
29
Querier
DataFusion used throughout
Query Planner
Querier
SELECT … WHERE
time > now() - ‘1 minute’ AND
region = ‘us-east-1’
User SQL query
FlightSQL handler
(gRPC)
Metadata Cache
DataFusion
Catalog API
Remote Catalog Service
Ingester
…
Object Store
Data Cache
FlightSQL
1. FlightSQL query received
3. Execution Plan runs
2. Query planned using IOx catalog + ingester
DataFusion Plan
…
4. Results returned to user
| © Copyright 2024, InfluxData
30
Querier: Partition + File Pruning
DataFusion use: PruningPredicate for partition + file pruning
Query Planner
Querier
SELECT … WHERE
time > now() - ‘1 minute’ AND
region = ‘us-east-1’
User SQL query
Remote Catalog Service
Object Store
DataFusion
TableProvider API
Metadata Cache
Partition: 2024-06-01
002312.parquet
a773ef.parquet
599e92.parquet
Partition: 2024-06-02
0356af.parquet
54229a.parquet
B432f3.parquet
…
Partition: 2024-06-30
432790.parquet
4321aa.parquet
time > 123456789
AND region = ‘us-east-1’
2. Evaluate Pruning- Predicate with min/max
1. SQL planned, predicates pushed down
3. Only relevant partitions / files used for plan
| © Copyright 2024, InfluxData
31
Querier: Custom SQL Operator
cpu.user | time |
89.3 | 2000-05-05T12:00:00Z |
88.0 | 2000-05-05T12:05:01Z |
85.0 | 2000-05-05T12:15:00Z |
45.3 | 2000-05-05T12:34:00Z |
... | ... |
78.9 | 2000-05-05T12:58:00Z |
78.7 | 2000-05-05T12:59:00Z |
Note: No data points for 12:20:00-12:29:00
cpu.user | time |
88.6 | 2000-05-05T12:00:00Z |
85.0 | 2000-05-05T12:10:01Z |
??? | 2000-05-05T12:20:00Z |
45.3 | 2000-05-05T12:30:00Z |
55.0 | 2000-05-05T12:40:00Z |
78.8 | 2000-05-05T12:50:00Z |
SQL?
GROUP BY date_bin would have no row with this timestamp
| © Copyright 2024, InfluxData
32
Querier: Custom SQL Operator
SELECT
date_bin_gapfill(
interval '10 minute', time
) as minute,
avg(cpu.user)
from cpu
where
time between
timestamp '2000-05-05T12:00:00Z' and
timestamp '2000-05-05T12:59:00Z'
group by minute;
| © Copyright 2024, InfluxData
33
Querier: Custom SQL Operator
SELECT
date_bin_gapfill(
interval '10 minute', time
) as minute,
avg(cpu.user)
from cpu
where
time between
timestamp '2000-05-05T12:00:00Z' and
timestamp '2000-05-05T12:59:00Z'
group by minute;
Filter
(time < '2000-05-05T12:00:00Z' and
time < '2000-05-05T12:59:00Z'
GroupBy
group: date_bin_gapfill(...)
agg: avg(cpu.user)
Scan
(projection + filter not shown)
Standard GroupBy can’t do gapfilling
| © Copyright 2024, InfluxData
34
Querier: Custom SQL Operator
Filter
(time < '2000-05-05T12:00:00Z' and
time < '2000-05-05T12:59:00Z'
GroupBy
group: minute
agg: date_bin_gapfill(...)
Scan
(projection + filter not shown)
Filter
(time < '2000-05-05T12:00:00Z' and
time < '2000-05-05T12:59:00Z'
GroupBy
group: date_bin as minute
agg: avg(...)
Scan
(projection + filter not shown)
GapFill
group: minute
agg: locf(avg(..))
1. OptimizerRule rewrites to use GapFill operator (extension)
Finalged expressions
| © Copyright 2024, InfluxData
35
Querier: Custom SQL Operator
ProjectionExec: expr=[minute, COUNT(cpu.user)] |
GapFillExec: group_expr=[date_bin_gapfill(..)],
aggr_expr=[COUNT(cpu.user)],
stride=10 minutes,
time_range=[957528000000000000).."957531540000000000"]
AggregateExec: mode=FinalPartitioned,
gby=[date_bin_gapfill(...)),cpu.time)],
aggr=[COUNT(cpu.user)]
CoalesceBatchesExec: target_batch_size=8192
RepartitionExec: partitioning=Hash([date_bin_gapfill(..., 4)
AggregateExec: mode=Partial,
gby=[date_bin_gapfill(...)),cpu.time)],
aggr=[COUNT(cpu.user)]
ParquetExec: file_groups=000000000000.parquet,predicates..)
Custom Stream (“operator)
| © Copyright 2024, InfluxData
36
Querier: Custom InfluxQL frontend
SELECT
MEAN(usage_idle),
MEAN(bytes_free)
FROM cpu, disk
GROUP BY TIME(10s)
FILL(linear)
Not a cross join!
Gap filling + interpolation
| © Copyright 2024, InfluxData
37
Querier: Custom InfluxQL frontend
SELECT
MEAN(usage_idle),
MEAN(bytes_free)
FROM cpu, disk
GROUP BY TIME(10s)
FILL(linear)
InfluxQL Parser
(InfluxDB)
InfluxQL Planner
(InfluxDB)
1. InfluxQL Text
4. Optimized, Executed and Run like all other plans
3. Planned into a DataFusion LogicalPlan
Filter
(time < '2000-05-05T12:00:00Z' and
time < '2000-05-05T12:59:00Z'
GroupBy
group: date_bin as time
agg: avg(..), avg(..)
Scan
(projection + filter not shown)
GapFill
group: minute
agg: avg(..), avg(..)
LogicalPlan
2. Parsed into a custom AST
SELECT
AGG
AGG
Column
Column
FROM
Table
…
usage_free
usage_idle
cpu
Table
disk
| © Copyright 2024, InfluxData
38
Querier: time series specific optimization
InfluxDB uses sortedness heavily, so often ends up with multi-column merges
Very wasteful for queries l
SELECT … FROM …
ORDER BY time DESC
LIMIT 10
SortPreservingMerge
expr: [tag1, tag2, time]
Input Stream
Input Stream
Input Stream
…
…
Each input stream ~ a partition, parquet files + data from ingester
More details in Blog: Making Most Recent Value Queries Hundreds of Times Faster
Not a cross join!
Have to read first batch from each stream before producing any output, even if limit
| © Copyright 2024, InfluxData
39
Querier: time series specific optimization
SortPreservingMerge
expr: [tag1, tag2, time]
ProgressiveEval
expr: [tag1, tag2, time]
Input Stream
Input Stream
Input Stream
…
…
* Really starts two in parallel to minimize IO stalls
2. Reads one stream at a time*
1. PhysicalOptimizerRule replaces SortPreservingMerge with ProgressiveEval if non overlapping key ranges
| © Copyright 2024, InfluxData
40
Case Study: Lance + LanceDB
Thanks to Weston Pace
| © Copyright 2024, InfluxData
41
Lance
LanceDB: Open-source vector database for AI that's designed to store, manage, query and retrieve embeddings on large-scale multi-modal data.
Lance: open-source columnar data format designed for performant ML workloads and fast random access.
Both the database and the underlying data format are designed from the ground up to be easy-to-use, scalable and cost-effective.
Lance: File Format
LanceDB: Database
| © Copyright 2024, InfluxData
42
Lance Table Format
Protocol
Manifest
Implementation
DataFusion: Table Scanner
Over 1000 potential datafusion physical plans generated by Lance scanner
DataFusion: Beyond Scans
let existing = session_ctx.read_lance(self.dataset.clone(), true)?;
let new_data = session_ctx.read_one_shot(source)?;
let join_cols = self
.params
.on // columns to join on
.iter()
.map(|c| c.as_str())
.collect::<Vec<_>>();
let joined = new_data.join(existing, JoinType::Full, &join_cols, &join_cols, None)?; // full join
Ok(joined.execute_stream().await?)
DataFusion: Even in the File Format!
Case Study: Apache DataFusion Comet
| © Copyright 2024, InfluxData
47
Apache DataFusion Comet
Runs Spark SQL queries using the native runtime (using Apache DataFusion execution)
⇒ faster and more resource efficient than JVM based runtimes.
Similar to
I just made up this icon!
| © Copyright 2024, InfluxData
48
Comet Goals
| © Copyright 2024, InfluxData
49
Architecture
Integration Layer with Spark
DataFusion’s ExecutionPlan Streams
Use Spark Planner / Executor machinery
| © Copyright 2024, InfluxData
50
Architecture
| © Copyright 2024, InfluxData
51
| © Copyright 2024, InfluxData
52
SDF
The Semantic Data Fabric
SDF is a multi-dialect SQL compiler, transformation framework,
and analytical database engine.
| © Copyright 2024, InfluxData
53
SDF
Assembly
Intelisense
Tests
Reports
Business Value
Catalog
The Key: Multi Dialect SQL -> Unified Logical Plan
BigQuery
Redshift
Metadata
Unified Logical Plan
SDF
Static Analysis
SDF Development Framework
Ingest
Deploy
Analyze
Guarantee
Snowflake
Trino/Presto
SDF uses complete ANTLR Grammars to define many SQL dialects - notably, proprietary ones like Snowflake.
All SQLs compile to a unified Intermediate Representation: the Datafusion Logical Plan.
This gives SDF Executable Semantics.
SDF’s transformation layer statically analyzes many logical plans at once for correctness and generates rich metadata.
| © Copyright 2024, InfluxData
54
SDF: Datafusion Enabled Transformation Layer
DBT Preprocesses macros and configurations.
Then issues a request to your cloud database.
The cloud database compiles the SQL and ultimately executes the query. Errors can ONLY be caught by the cloud database’s compiler.
SDF first reads your cloud database’s state.
Then statically analyzes all configuration (macros, metadata), queries (sql), and dataflows (classifiers) to provide compile time guarantees.
Only queries that have been validated both syntactically and semantically are run against the warehouse.
SDF Transformation
DBT Transformation
| © Copyright 2024, InfluxData
55
SDF: Integrated Database
Executable Semantics power SDF DB.
With a complete DF logical plan for any query in any environment , SDF can:
(a) Execute that query locally using Datafusion as the execution runtime
(b) Route that query to a remote cloud database
Enables execution of the same query against proprietary compute providers
| © Copyright 2024, InfluxData
56
The Result: Composable Execution
SDF provides intelligent federated execution between transformation layer and data cloud providers.
Composable Execution enables the same query to run against proprietary compute providers (like Snowflake, BigQuery, and Redshift) and SDF DB; with both query engines yielding the same result.
| © Copyright 2024, InfluxData
57
Thank you
Come and join us (it is lots of fun!)
100s of like-minded people hacking on Databases!
| © Copyright 2024, InfluxData
58