diff --git a/Cargo.lock b/Cargo.lock
index 660aeea1c6..c583ce406f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -31,6 +31,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+[[package]]
+name = "ahash"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877"
+dependencies = [
+ "getrandom 0.2.2",
+ "once_cell",
+ "version_check",
+]
+
[[package]]
name = "ahash"
version = "0.7.2"
@@ -96,6 +107,15 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+[[package]]
+name = "arrayvec"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
+dependencies = [
+ "nodrop",
+]
+
[[package]]
name = "arrayvec"
version = "0.5.2"
@@ -159,7 +179,7 @@ dependencies = [
name = "arrow_util"
version = "0.1.0"
dependencies = [
- "ahash",
+ "ahash 0.7.2",
"arrow 0.1.0",
"futures",
"hashbrown 0.11.2",
@@ -193,6 +213,15 @@ dependencies = [
"wait-timeout",
]
+[[package]]
+name = "async-mutex"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
+dependencies = [
+ "event-listener",
+]
+
[[package]]
name = "async-stream"
version = "0.3.1"
@@ -372,7 +401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
- "arrayvec",
+ "arrayvec 0.5.2",
"constant_time_eq",
]
@@ -424,6 +453,12 @@ version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
+[[package]]
+name = "bytemuck"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -439,6 +474,40 @@ dependencies = [
"serde",
]
+[[package]]
+name = "cached"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e2afe73808fbaac302e39c9754bfc3c4b4d0f99c9c240b9f4e4efc841ad1b74"
+dependencies = [
+ "async-mutex",
+ "async-trait",
+ "cached_proc_macro",
+ "cached_proc_macro_types",
+ "futures",
+ "hashbrown 0.9.1",
+ "once_cell",
+]
+
+[[package]]
+name = "cached_proc_macro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf857ae42d910aede5c5186e62684b0d7a597ce2fe3bd14448ab8f7ef439848c"
+dependencies = [
+ "async-mutex",
+ "cached_proc_macro_types",
+ "darling",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "cached_proc_macro_types"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
+
[[package]]
name = "cast"
version = "0.2.5"
@@ -512,7 +581,7 @@ dependencies = [
"ansi_term 0.11.0",
"atty",
"bitflags",
- "strsim",
+ "strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
@@ -767,6 +836,41 @@ dependencies = [
"sct",
]
+[[package]]
+name = "darling"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.9.3",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "dashmap"
version = "4.0.2"
@@ -804,7 +908,7 @@ name = "datafusion"
version = "4.0.0-SNAPSHOT"
source = "git+https://github.com/apache/arrow-datafusion.git?rev=9cf32cf2cda8472b87130142c4eee1126d4d9cbe#9cf32cf2cda8472b87130142c4eee1126d4d9cbe"
dependencies = [
- "ahash",
+ "ahash 0.7.2",
"arrow 4.0.0-SNAPSHOT",
"async-trait",
"chrono",
@@ -830,6 +934,15 @@ dependencies = [
"futures",
]
+[[package]]
+name = "debugid"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91cf5a8c2f2097e2a32627123508635d47ce10563d999ec1a95addf08b502ba"
+dependencies = [
+ "uuid",
+]
+
[[package]]
name = "difference"
version = "2.0.0"
@@ -975,6 +1088,12 @@ dependencies = [
"termcolor",
]
+[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
[[package]]
name = "extend"
version = "0.1.2"
@@ -1319,7 +1438,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
- "ahash",
+ "ahash 0.7.2",
]
[[package]]
@@ -1456,6 +1575,12 @@ dependencies = [
"tokio-native-tls",
]
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
version = "0.2.3"
@@ -1477,6 +1602,24 @@ dependencies = [
"hashbrown 0.9.1",
]
+[[package]]
+name = "inferno"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37fb405dbcc505ed20838c4f8dad7b901094de90add237755df54bd5dcda2fdd"
+dependencies = [
+ "ahash 0.6.3",
+ "atty",
+ "indexmap",
+ "itoa",
+ "lazy_static",
+ "log",
+ "num-format",
+ "quick-xml",
+ "rgb",
+ "str_stack",
+]
+
[[package]]
name = "influxdb2_client"
version = "0.1.0"
@@ -1541,6 +1684,7 @@ dependencies = [
"panic_logging",
"parking_lot",
"parquet 0.1.0",
+ "pprof",
"predicates",
"prettytable-rs",
"prost",
@@ -1738,7 +1882,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
- "arrayvec",
+ "arrayvec 0.5.2",
"bitflags",
"cfg-if",
"ryu",
@@ -1859,6 +2003,16 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
+[[package]]
+name = "memmap"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
[[package]]
name = "memoffset"
version = "0.6.3"
@@ -2024,6 +2178,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
[[package]]
name = "nom"
version = "5.1.2"
@@ -2095,6 +2255,16 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "num-format"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465"
+dependencies = [
+ "arrayvec 0.4.12",
+ "itoa",
+]
+
[[package]]
name = "num-integer"
version = "0.1.44"
@@ -2625,6 +2795,27 @@ dependencies = [
"plotters-backend",
]
+[[package]]
+name = "pprof"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234cb1ca0d59aa771d9bc7e268739d7aef6ca7e9e8d3b78d92f266cd663fd0c1"
+dependencies = [
+ "backtrace",
+ "inferno",
+ "lazy_static",
+ "libc",
+ "log",
+ "nix",
+ "parking_lot",
+ "prost",
+ "prost-build",
+ "prost-derive",
+ "symbolic-demangle",
+ "tempfile",
+ "thiserror",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.10"
@@ -2824,6 +3015,15 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+[[package]]
+name = "quick-xml"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26aab6b48e2590e4a64d1ed808749ba06257882b461d01ca71baeb747074a6dd"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quote"
version = "1.0.9"
@@ -3095,6 +3295,15 @@ dependencies = [
"winreg",
]
+[[package]]
+name = "rgb"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fddb3b23626145d1776addfc307e1a1851f60ef6ca64f376bcb889697144cf0"
+dependencies = [
+ "bytemuck",
+]
+
[[package]]
name = "ring"
version = "0.16.20"
@@ -3461,6 +3670,7 @@ dependencies = [
"arrow_util",
"async-trait",
"bytes",
+ "cached",
"chrono",
"crc32fast",
"criterion",
@@ -3702,12 +3912,24 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
+[[package]]
+name = "str_stack"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
+
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+[[package]]
+name = "strsim"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
+
[[package]]
name = "structopt"
version = "0.3.21"
@@ -3738,6 +3960,28 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+[[package]]
+name = "symbolic-common"
+version = "8.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be7dfa630954f18297ceae1ff2890cb7f5008a0b2d2106b0468dafc45b0b6b12"
+dependencies = [
+ "debugid",
+ "memmap",
+ "stable_deref_trait",
+ "uuid",
+]
+
+[[package]]
+name = "symbolic-demangle"
+version = "8.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b4ba42bd1221803e965054767b1899f2db9a12c89969965c6cb3a02af7014eb"
+dependencies = [
+ "rustc-demangle",
+ "symbolic-common",
+]
+
[[package]]
name = "syn"
version = "1.0.67"
diff --git a/Cargo.toml b/Cargo.toml
index 904749f4e9..a987028199 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -92,6 +92,7 @@ parking_lot = "0.11.1"
itertools = "0.9.0"
# used by arrow/datafusion anyway
prettytable-rs = "0.8"
+pprof = { version = "^0.4", default-features = false, features = ["flamegraph", "protobuf"] }
prost = "0.7"
# Forked to upgrade hyper and tokio
routerify = { git = "https://github.com/influxdata/routerify", rev = "274e250" }
diff --git a/data_types/src/error.rs b/data_types/src/error.rs
index 2b51153cbe..ef7131298b 100644
--- a/data_types/src/error.rs
+++ b/data_types/src/error.rs
@@ -4,8 +4,8 @@ use std::fmt::Debug;
use observability_deps::tracing::error;
/// Add ability for Results to log error messages via `error!` logs.
-/// This is useful when using async tasks that may not have a natural
-/// return error
+/// This is useful when using async tasks that may not have any code
+/// checking their return values.
pub trait ErrorLogger {
/// Log the contents of self with a string of context. The context
/// should appear in a message such as
diff --git a/docs/README.md b/docs/README.md
index 2cb4e2d1fc..142adaaf01 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -17,6 +17,7 @@ We hold monthly Tech Talks that explain the project's technical underpinnings. Y
* Rust style and Idiom guide: [style_guide.md](style_guide.md)
* Tracing and logging Guide: [tracing.md](tracing.md)
+* Profiling Guide: [profiling.md](profiling.md)
* How InfluxDB IOx manages the lifecycle of time series data: [data_management.md](data_management.md)
* Thoughts on parquet encoding and compression for timeseries data: [encoding_thoughts.md](encoding_thoughts.md)
* Thoughts on using multiple cores: [multi_core_tasks.md](multi_core_tasks.md)
diff --git a/docs/images/flame_graph.png b/docs/images/flame_graph.png
new file mode 100644
index 0000000000..a56def9c99
Binary files /dev/null and b/docs/images/flame_graph.png differ
diff --git a/docs/profiling.md b/docs/profiling.md
new file mode 100644
index 0000000000..db34eb70d2
--- /dev/null
+++ b/docs/profiling.md
@@ -0,0 +1,46 @@
+# IOx — Profiling
+
+IOx includes an embedded `pprof` exporter compatible with the [go pprof](https://golang.org/pkg/net/http/pprof/) tool.
+
+To use it, aim your favorite tool at your IOx host at the HTTP `/debug/pprof/profile` endpoint.
+
+# Use the Go `pprof` tool:
+
+Example
+
+```shell
+go tool pprof 'http://localhost:8080/debug/pprof/profile?seconds=5'
+```
+
+And you get output like:
+
+```
+Fetching profile over HTTP from http://localhost:8080/debug/pprof/profile?seconds=5
+Saved profile in /Users/mkm/pprof/pprof.cpu.006.pb.gz
+Type: cpu
+Entering interactive mode (type "help" for commands, "o" for options)
+(pprof) top
+Showing nodes accounting for 93, 100% of 93 total
+Showing top 10 nodes out of 185
+ flat flat% sum% cum cum%
+ 93 100% 100% 93 100% backtrace::backtrace::libunwind::trace
+ 0 0% 100% 1 1.08% <&str as nom::traits::InputTakeAtPosition>::split_at_position1_complete
+ 0 0% 100% 1 1.08% <(FnA,FnB) as nom::sequence::Tuple>::parse
+ 0 0% 100% 1 1.08% <(FnA,FnB,FnC) as nom::sequence::Tuple>::parse
+ 0 0% 100% 5 5.38% ::try_poll
+ 0 0% 100% 1 1.08% ::to_vec
+ 0 0% 100% 1 1.08% ::allocate
+ 0 0% 100% 1 1.08% as core::clone::Clone>::clone
+ 0 0% 100% 3 3.23% as alloc::vec::spec_extend::SpecExtend>::spec_extend
+ 0 0% 100% 1 1.08% as core::iter::traits::collect::Extend>::extend
+```
+
+# Use the built in flame graph renderer
+
+IOx also knows how to render a flamegraph SVG directly if opened directly in the browser:
+
+For example, if you aim your browser at an IOx server with a URL such as http://localhost:8080/debug/pprof/profile?seconds=5
+
+You will see a beautiful flame graph such as
+
+![Flame Graph](images/flame_graph.png)
diff --git a/mutable_buffer/src/chunk/snapshot.rs b/mutable_buffer/src/chunk/snapshot.rs
index 04718fc689..efda563c0e 100644
--- a/mutable_buffer/src/chunk/snapshot.rs
+++ b/mutable_buffer/src/chunk/snapshot.rs
@@ -8,7 +8,7 @@ use internal_types::selection::Selection;
use snafu::{OptionExt, ResultExt, Snafu};
use super::Chunk;
-use data_types::partition_metadata::Statistics;
+use data_types::{error::ErrorLogger, partition_metadata::Statistics};
#[derive(Debug, Snafu)]
pub enum Error {
@@ -57,8 +57,15 @@ impl ChunkSnapshot {
let mut records: HashMap = Default::default();
let table = &chunk.table;
- let schema = table.schema(&chunk.dictionary, Selection::All).unwrap();
- let batch = table.to_arrow(&chunk.dictionary, Selection::All).unwrap();
+ let schema = table
+ .schema(&chunk.dictionary, Selection::All)
+ .log_if_error("ChunkSnapshot getting table schema")
+ .unwrap();
+ let batch = table
+ .to_arrow(&chunk.dictionary, Selection::All)
+ .log_if_error("ChunkSnapshot converting table to arrow")
+ .unwrap();
+
let name = chunk.table_name.as_ref();
let timestamp_range =
@@ -87,10 +94,13 @@ impl ChunkSnapshot {
},
);
- Self {
- chunk_id: chunk.id.expect("cannot snapshot chunk without an ID"),
- records,
- }
+ let chunk_id = chunk
+ .id
+ .ok_or("cannot snapshot chunk without an ID")
+ .log_if_error("ChunkSnapshot determining chunk id")
+ .unwrap();
+
+ Self { chunk_id, records }
}
/// return the ID of the chunk this is a snapshot of
diff --git a/parquet_file/src/catalog.rs b/parquet_file/src/catalog.rs
index 5c2b731c26..d3b7583d05 100644
--- a/parquet_file/src/catalog.rs
+++ b/parquet_file/src/catalog.rs
@@ -251,24 +251,33 @@ where
S: CatalogState,
{
/// Create new catalog w/o any data.
- pub fn new_empty(
+ ///
+ /// An empty transaction will be used to mark the catalog start so that concurrent open but still-empty catalogs can
+ /// easily be detected.
+ pub async fn new_empty(
object_store: Arc,
server_id: ServerId,
db_name: impl Into,
state_data: S::EmptyInput,
- ) -> Self {
+ ) -> Result {
let inner = PreservedCatalogInner {
previous_tkey: None,
state: Arc::new(S::new_empty(state_data)),
};
- Self {
+ let catalog = Self {
inner: RwLock::new(inner),
transaction_semaphore: Semaphore::new(1),
object_store,
server_id,
db_name: db_name.into(),
- }
+ };
+
+ // add empty transaction
+ let transaction = catalog.open_transaction().await;
+ transaction.commit().await?;
+
+ Ok(catalog)
}
/// Load existing catalog from store, if it exists.
@@ -383,14 +392,13 @@ where
}
/// Get latest revision counter.
- ///
- /// This can be `None` for a newly created catalog.
- pub fn revision_counter(&self) -> Option {
+ pub fn revision_counter(&self) -> u64 {
self.inner
.read()
.previous_tkey
.clone()
.map(|tkey| tkey.revision_counter)
+ .expect("catalog should have at least an empty transaction")
}
}
@@ -960,6 +968,42 @@ pub mod tests {
use super::test_helpers::TestCatalogState;
use super::*;
+ #[tokio::test]
+ async fn test_create_empty() {
+ let object_store = make_object_store();
+ let server_id = make_server_id();
+ let db_name = "db1";
+
+ assert!(PreservedCatalog::::load(
+ Arc::clone(&object_store),
+ server_id,
+ db_name.to_string(),
+ ()
+ )
+ .await
+ .unwrap()
+ .is_none());
+
+ PreservedCatalog::::new_empty(
+ Arc::clone(&object_store),
+ server_id,
+ db_name.to_string(),
+ (),
+ )
+ .await
+ .unwrap();
+
+ assert!(PreservedCatalog::::load(
+ Arc::clone(&object_store),
+ server_id,
+ db_name.to_string(),
+ ()
+ )
+ .await
+ .unwrap()
+ .is_some());
+ }
+
#[tokio::test]
async fn test_inmem_commit_semantics() {
let object_store = make_object_store();
@@ -1064,12 +1108,7 @@ pub mod tests {
(),
)
.await;
- assert!(matches!(
- res,
- Err(Error::MissingTransaction {
- revision_counter: 0
- })
- ));
+ assert_eq!(res.unwrap_err().to_string(), "Missing transaction: 0",);
}
#[tokio::test]
@@ -1327,14 +1366,16 @@ pub mod tests {
make_server_id(),
"db1".to_string(),
(),
- );
+ )
+ .await
+ .unwrap();
let mut t = catalog.open_transaction().await;
// open transaction
t.transaction.as_mut().unwrap().proto.uuid = Uuid::nil().to_string();
assert_eq!(
format!("{:?}", t),
- "TransactionHandle(open, 0.00000000-0000-0000-0000-000000000000)"
+ "TransactionHandle(open, 1.00000000-0000-0000-0000-000000000000)"
);
// "closed" transaction
@@ -1521,7 +1562,9 @@ pub mod tests {
server_id,
db_name.to_string(),
(),
- );
+ )
+ .await
+ .unwrap();
// get some test metadata
let metadata1 = make_metadata(object_store, "foo").await;
@@ -1531,8 +1574,9 @@ pub mod tests {
let mut trace = TestTrace::new();
// empty catalog has no data
- assert!(catalog.revision_counter().is_none());
+ assert_eq!(catalog.revision_counter(), 0);
assert_catalog_parquet_files(&catalog, &[]);
+ trace.record(&catalog);
// fill catalog with examples
{
@@ -1548,7 +1592,7 @@ pub mod tests {
t.commit().await.unwrap();
}
- assert_eq!(catalog.revision_counter().unwrap(), 0);
+ assert_eq!(catalog.revision_counter(), 1);
assert_catalog_parquet_files(
&catalog,
&[
@@ -1578,7 +1622,7 @@ pub mod tests {
t.commit().await.unwrap();
}
- assert_eq!(catalog.revision_counter().unwrap(), 1);
+ assert_eq!(catalog.revision_counter(), 2);
assert_catalog_parquet_files(
&catalog,
&[
@@ -1599,7 +1643,7 @@ pub mod tests {
// NO commit here!
}
- assert_eq!(catalog.revision_counter().unwrap(), 1);
+ assert_eq!(catalog.revision_counter(), 2);
assert_catalog_parquet_files(
&catalog,
&[
@@ -1629,7 +1673,7 @@ pub mod tests {
.unwrap()
.unwrap();
assert_eq!(
- catalog.revision_counter().unwrap(),
+ catalog.revision_counter(),
trace.tkeys.last().unwrap().revision_counter
);
assert_catalog_parquet_files(
diff --git a/query/src/exec/context.rs b/query/src/exec/context.rs
index c8a07ac0db..984fffca9e 100644
--- a/query/src/exec/context.rs
+++ b/query/src/exec/context.rs
@@ -8,7 +8,7 @@ use datafusion::{
execution::context::{ExecutionContextState, QueryPlanner},
logical_plan::{LogicalPlan, UserDefinedLogicalNode},
physical_plan::{
- collect,
+ collect, displayable,
merge::MergeExec,
planner::{DefaultPhysicalPlanner, ExtensionPlanner},
ExecutionPlan, PhysicalPlanner, SendableRecordBatchStream,
@@ -151,21 +151,15 @@ impl IOxExecutionContext {
/// Prepare (optimize + plan) a pre-created logical plan for execution
pub fn prepare_plan(&self, plan: &LogicalPlan) -> Result> {
- debug!(
- "Creating plan: Initial plan\n----\n{}\n{}\n----",
- plan.display_indent_schema(),
- plan.display_graphviz(),
- );
+ debug!(text=%plan.display_indent_schema(), "initial plan");
let plan = self.inner.optimize(&plan)?;
+ debug!(text=%plan.display_indent_schema(), graphviz=%plan.display_graphviz(), "optimized plan");
- debug!(
- "Creating plan: Optimized plan\n----\n{}\n{}\n----",
- plan.display_indent_schema(),
- plan.display_graphviz(),
- );
+ let physical_plan = self.inner.create_physical_plan(&plan)?;
- self.inner.create_physical_plan(&plan)
+ debug!(text=%displayable(physical_plan.as_ref()).indent(), "optimized physical plan");
+ Ok(physical_plan)
}
/// Executes the logical plan using DataFusion on a separate
diff --git a/query/src/predicate.rs b/query/src/predicate.rs
index f02fdc6958..5b6c69fb90 100644
--- a/query/src/predicate.rs
+++ b/query/src/predicate.rs
@@ -3,7 +3,7 @@
//! mode as well as for arbitrary other predicates that are expressed
//! by DataFusion's `Expr` type.
-use std::collections::{BTreeSet, HashSet};
+use std::{collections::{BTreeSet, HashSet}, fmt};
use data_types::timestamp::TimestampRange;
use datafusion::{
@@ -27,9 +27,9 @@ pub const EMPTY_PREDICATE: Predicate = Predicate {
/// Represents a parsed predicate for evaluation by the
/// TSDatabase InfluxDB IOx query engine.
///
-/// Note that the data model of TSDatabase (e.g. ParsedLine's)
+/// Note that the InfluxDB data model (e.g. ParsedLine's)
/// distinguishes between some types of columns (tags and fields), and
-/// likewise the semantics of this structure has some types of
+/// likewise the semantics of this structure can express some types of
/// restrictions that only apply to certain types of columns.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Predicate {
@@ -37,23 +37,23 @@ pub struct Predicate {
/// to only tables whose names are in `table_names`
pub table_names: Option>,
- // Optional field restriction. If present, restricts the results to only
- // tables which have *at least one* of the fields in field_columns.
+ /// Optional field restriction. If present, restricts the results to only
+ /// tables which have *at least one* of the fields in field_columns.
pub field_columns: Option>,
+ /// Optional partition key filter
+ pub partition_key: Option,
+
+ /// Optional timestamp range: only rows within this range are included in
+ /// results. Other rows are excluded
+ pub range: Option,
+
/// Optional arbitrary predicates, represented as list of
/// DataFusion expressions applied a logical conjunction (aka they
/// are 'AND'ed together). Only rows that evaluate to TRUE for all
/// these expressions should be returned. Other rows are excluded
/// from the results.
pub exprs: Vec,
-
- /// Optional timestamp range: only rows within this range are included in
- /// results. Other rows are excluded
- pub range: Option,
-
- /// Optional partition key filter
- pub partition_key: Option,
}
impl Predicate {
@@ -108,8 +108,66 @@ impl Predicate {
}
}
+impl fmt::Display for Predicate {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fn iter_to_str(s: impl IntoIterator- ) -> String
+ where
+ S: ToString,
+ {
+ s.into_iter()
+ .map(|v| v.to_string())
+ .collect::>()
+ .join(", ")
+ }
+
+ write!(f, "Predicate")?;
+
+ if let Some(table_names) = &self.table_names {
+ write!(f, " table_names: {{{}}}", iter_to_str(table_names))?;
+ }
+
+ if let Some(field_columns) = &self.field_columns {
+ write!(f, " field_columns: {{{}}}", iter_to_str(field_columns))?;
+ }
+
+ if let Some(partition_key) = &self.partition_key {
+ write!(f, " partition_key: '{}'", partition_key)?;
+ }
+
+ if let Some(range) = &self.range {
+ // TODO: could be nice to show this as actual timestamps (not just numbers)?
+ write!(f, " range: [{} - {}]", range.start, range.end)?;
+ }
+
+ if !self.exprs.is_empty() {
+ // Expr doesn't implement `Display` yet, so just the debug version
+ // See https://github.com/apache/arrow-datafusion/issues/347
+ let display_exprs = self.exprs.iter().map(|e| format!("{:?}", e));
+ write!(f, " exprs: [{}]", iter_to_str(display_exprs))?;
+ }
+
+ Ok(())
+ }
+}
+
#[derive(Debug, Default)]
-/// Structure for building `Predicate`s
+/// Structure for building [`Predicate`]s
+///
+/// Example:
+/// ```
+/// use query::predicate::PredicateBuilder;
+/// use datafusion::logical_plan::{col, lit};
+///
+/// let p = PredicateBuilder::new()
+/// .timestamp_range(1, 100)
+/// .add_expr(col("foo").eq(lit(42)))
+/// .build();
+///
+/// assert_eq!(
+/// p.to_string(),
+/// "Predicate range: [1 - 100] exprs: [#foo Eq Int32(42)]"
+/// );
+/// ```
pub struct PredicateBuilder {
inner: Predicate,
}
@@ -315,9 +373,8 @@ impl PredicateBuilder {
#[cfg(test)]
mod tests {
- use datafusion::logical_plan::{col, lit};
-
use super::*;
+ use datafusion::logical_plan::{col, lit};
#[test]
fn test_default_predicate_is_empty() {
@@ -392,4 +449,38 @@ mod tests {
assert_eq!(predicate.exprs[5], col("city").eq(lit("Boston")));
assert_eq!(predicate.exprs[6], col("city").not_eq(lit("Braintree")));
}
+
+ #[test]
+ fn predicate_display_ts() {
+ // TODO make this a doc example?
+ let p = PredicateBuilder::new().timestamp_range(1, 100).build();
+
+ assert_eq!(p.to_string(), "Predicate range: [1 - 100]");
+ }
+
+ #[test]
+ fn predicate_display_ts_and_expr() {
+ let p = PredicateBuilder::new()
+ .timestamp_range(1, 100)
+ .add_expr(col("foo").eq(lit(42)).and(col("bar").lt(lit(11))))
+ .build();
+
+ assert_eq!(
+ p.to_string(),
+ "Predicate range: [1 - 100] exprs: [#foo Eq Int32(42) And #bar Lt Int32(11)]"
+ );
+ }
+
+ #[test]
+ fn predicate_display_full() {
+ let p = PredicateBuilder::new()
+ .timestamp_range(1, 100)
+ .add_expr(col("foo").eq(lit(42)))
+ .table("my_table")
+ .field_columns(vec!["f1", "f2"])
+ .partition_key("the_key")
+ .build();
+
+ assert_eq!(p.to_string(), "Predicate table_names: {my_table} field_columns: {f1, f2} partition_key: 'the_key' range: [1 - 100] exprs: [#foo Eq Int32(42)]");
+ }
}
diff --git a/query/src/provider/physical.rs b/query/src/provider/physical.rs
index 7ea4023258..b2186ecde0 100644
--- a/query/src/provider/physical.rs
+++ b/query/src/provider/physical.rs
@@ -1,11 +1,11 @@
//! Implementation of a DataFusion PhysicalPlan node across partition chunks
-use std::sync::Arc;
+use std::{fmt, sync::Arc};
use arrow::datatypes::SchemaRef;
use datafusion::{
error::DataFusionError,
- physical_plan::{ExecutionPlan, Partitioning, SendableRecordBatchStream},
+ physical_plan::{DisplayFormatType, ExecutionPlan, Partitioning, SendableRecordBatchStream},
};
use internal_types::{schema::Schema, selection::Selection};
@@ -116,6 +116,21 @@ impl ExecutionPlan for IOxReadFilterNode {
Ok(Box::pin(adapter))
}
+
+ fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match t {
+ DisplayFormatType::Default => {
+ // Note Predicate doesn't implement Display so punt on showing that now
+ write!(
+ f,
+ "IOxReadFilterNode: table_name={}, chunks={} predicate={}",
+ self.table_name,
+ self.chunk_and_infos.len(),
+ self.predicate,
+ )
+ }
+ }
+ }
}
/// Removes any columns that are not present in schema, returning a possibly
diff --git a/read_buffer/benches/plain.rs b/read_buffer/benches/plain.rs
index da796c5620..f6c3a03f5a 100644
--- a/read_buffer/benches/plain.rs
+++ b/read_buffer/benches/plain.rs
@@ -116,7 +116,7 @@ fn benchmark_plain_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum(&input);
+ let _ = encoding.sum::(&input);
});
},
);
@@ -142,7 +142,7 @@ fn benchmark_plain_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum::(&input);
+ let _ = encoding.sum::(&input);
});
},
);
@@ -161,7 +161,7 @@ fn benchmark_plain_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum(&input);
+ let _ = encoding.sum::(&input);
});
},
);
@@ -206,7 +206,7 @@ fn benchmark_plain_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum(&input);
+ let _ = encoding.sum::(&input);
});
},
);
diff --git a/read_buffer/benches/sum_fixed.rs b/read_buffer/benches/sum_fixed.rs
index b34426766e..7c38172b5f 100644
--- a/read_buffer/benches/sum_fixed.rs
+++ b/read_buffer/benches/sum_fixed.rs
@@ -116,7 +116,7 @@ fn benchmark_none_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum(&input);
+ let _ = encoding.sum::(&input);
});
},
);
@@ -142,7 +142,7 @@ fn benchmark_none_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum::(&input);
+ let _ = encoding.sum::(&input);
});
},
);
@@ -161,7 +161,7 @@ fn benchmark_none_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum(&input);
+ let _ = encoding.sum::(&input);
});
},
);
@@ -187,7 +187,7 @@ fn benchmark_none_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum::(&input);
+ let _ = encoding.sum::(&input);
});
},
);
@@ -206,7 +206,7 @@ fn benchmark_none_sum(
|b, input| {
b.iter(|| {
// do work
- let _ = encoding.sum(&input);
+ let _ = encoding.sum::(&input);
});
},
);
diff --git a/read_buffer/src/column/encoding/scalar/fixed.rs b/read_buffer/src/column/encoding/scalar/fixed.rs
index 6186f456a9..64b65169e9 100644
--- a/read_buffer/src/column/encoding/scalar/fixed.rs
+++ b/read_buffer/src/column/encoding/scalar/fixed.rs
@@ -21,7 +21,7 @@ use arrow::array::Array;
use crate::column::{cmp, RowIDs};
-#[derive(Debug, Default)]
+#[derive(Debug, Default, PartialEq, PartialOrd)]
/// A Fixed encoding is one in which every value has a fixed width, and is
/// stored contiguous in a backing vector. Fixed encodings do not support NULL
/// values, so are suitable for columns known to not have NULL values that we
@@ -566,13 +566,6 @@ macro_rules! fixed_from_arrow_impls {
}
fixed_from_arrow_impls! {
- (&arrow::array::Int64Array, i64),
- (&arrow::array::Int64Array, i32),
- (&arrow::array::Int64Array, i16),
- (&arrow::array::Int64Array, i8),
- (&arrow::array::Int64Array, u32),
- (&arrow::array::Int64Array, u16),
- (&arrow::array::Int64Array, u8),
(arrow::array::Int64Array, i64),
(arrow::array::Int64Array, i32),
(arrow::array::Int64Array, i16),
@@ -581,10 +574,6 @@ fixed_from_arrow_impls! {
(arrow::array::Int64Array, u16),
(arrow::array::Int64Array, u8),
- (&arrow::array::UInt64Array, u64),
- (&arrow::array::UInt64Array, u32),
- (&arrow::array::UInt64Array, u16),
- (&arrow::array::UInt64Array, u8),
(arrow::array::UInt64Array, u64),
(arrow::array::UInt64Array, u32),
(arrow::array::UInt64Array, u16),
diff --git a/read_buffer/src/column/encoding/scalar/fixed_null.rs b/read_buffer/src/column/encoding/scalar/fixed_null.rs
index c0c64c610b..4e3865622d 100644
--- a/read_buffer/src/column/encoding/scalar/fixed_null.rs
+++ b/read_buffer/src/column/encoding/scalar/fixed_null.rs
@@ -14,6 +14,7 @@
//! consumer of these encodings.
use std::cmp::Ordering;
use std::fmt::Debug;
+use std::iter::FromIterator;
use std::mem::size_of;
use arrow::{
@@ -23,7 +24,7 @@ use arrow::{
use crate::column::{cmp, RowIDs};
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
pub struct FixedNull
where
T: ArrowNumericType,
@@ -116,26 +117,30 @@ where
//
//
- /// Return the logical (decoded) value at the provided row ID. A NULL value
+ /// Return the logical (decoded) value at the provided row ID according to
+ /// the logical type of the column, which is specified by `U`. A NULL value
/// is represented by None.
- pub fn value(&self, row_id: u32) -> Option {
+ pub fn value(&self, row_id: u32) -> Option
+ where
+ U: From,
+ {
if self.arr.is_null(row_id as usize) {
return None;
}
- Some(self.arr.value(row_id as usize))
+ Some(U::from(self.arr.value(row_id as usize)))
}
- /// Returns the logical (decoded) values for the provided row IDs.
+ /// Returns the logical (decoded) values for the provided row IDs according
+ /// to the logical type of the column, which is specified by `U`.
///
/// NULL values are represented by None.
///
/// TODO(edd): Perf - we should return a vector of values and a vector of
/// integers representing the null validity bitmap.
- pub fn values(
- &self,
- row_ids: &[u32],
- mut dst: Vec