From 80a49c8c45ba003c0350ec0834d78fef941ccb8b Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Mon, 31 Jan 2022 13:48:04 -0500 Subject: [PATCH 01/22] fix(aws): add linear random backoff algorithm (#454) --- flock/src/aws/lambda.rs | 25 ++++++++++++++++++++----- flock/src/configs/flock.toml | 7 +++++-- flock/src/configs/mod.rs | 2 ++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/flock/src/aws/lambda.rs b/flock/src/aws/lambda.rs index 974ad4c4..17bb5ed9 100644 --- a/flock/src/aws/lambda.rs +++ b/flock/src/aws/lambda.rs @@ -17,7 +17,8 @@ use crate::configs::*; use crate::error::{FlockError, Result}; use crate::runtime::context::ExecutionContext; use bytes::Bytes; -use log::info; +use log::{debug, info}; +use rand::Rng; use rusoto_lambda::{ CreateFunctionRequest, GetFunctionRequest, InvocationRequest, InvocationResponse, Lambda, PutFunctionConcurrencyRequest, UpdateFunctionCodeRequest, @@ -74,8 +75,10 @@ pub async fn invoke_function( .map_err(|e| FlockError::AWS(e.to_string()))?; Ok(response) } else { - // Error retries and exponential backoff in AWS Lambda + // Error retry uses linear random backoff algorithm + // min(50 * increase_factor + random_milliseconds, max_backoff) let mut retries = 0; + let mut increase_factor = 0; loop { match FLOCK_LAMBDA_CLIENT .invoke(request.clone()) @@ -94,14 +97,26 @@ pub async fn invoke_function( } } Err(e) => { - info!("Function invocation error: {}", e); + debug!("Function invocation error: {}", e); } } - info!("Retrying {} function invocation...", function_name); - tokio::time::sleep(Duration::from_millis(2_u64.pow(retries) * 100)).await; + debug!( + "Retrying #{}: {} function invocation...", + retries, function_name + ); retries += 1; + increase_factor = std::cmp::min(increase_factor, 9) + 1; + + let random_milliseconds = rand::thread_rng().gen_range(0..100); + let backoff = Duration::from_millis(std::cmp::min( + 50 * increase_factor + random_milliseconds, + *FLOCK_LAMBDA_MAX_BACKOFF, + )); + + tokio::time::sleep(backoff).await; + if retries as usize > *FLOCK_LAMBDA_MAX_RETRIES { return Err(FlockError::AWS(format!( "Sync invocation failed after {} retries", diff --git a/flock/src/configs/flock.toml b/flock/src/configs/flock.toml index ae1b8332..3ef9ce76 100644 --- a/flock/src/configs/flock.toml +++ b/flock/src/configs/flock.toml @@ -60,8 +60,11 @@ regular_threshold = 20971520 async_granule = 3096 sync_granule = 74304 -# Error retries and exponential backoff in AWS Lambda -max_invoke_retries = 20 +# Error retries in AWS Lambda +max_invoke_retries = 200 + +# Maximum backoff in milliseconds +max_backoff = 500 # Timeout for the function timeout = "120" diff --git a/flock/src/configs/mod.rs b/flock/src/configs/mod.rs index 3bbc8e91..e12d3170 100644 --- a/flock/src/configs/mod.rs +++ b/flock/src/configs/mod.rs @@ -37,6 +37,8 @@ lazy_static! { pub static ref FLOCK_LAMBDA_SYNC_CALL: String = "RequestResponse".to_string(); /// AWS Lambda function maximum error retry. pub static ref FLOCK_LAMBDA_MAX_RETRIES: usize = FLOCK_CONF["lambda"]["max_invoke_retries"].parse::().unwrap(); + /// AWS Lambda function maximum error retry. + pub static ref FLOCK_LAMBDA_MAX_BACKOFF: u64 = FLOCK_CONF["lambda"]["max_backoff"].parse::().unwrap(); /// AWS Lambda function timeout. pub static ref FLOCK_LAMBDA_TIMEOUT: i64 = FLOCK_CONF["lambda"]["timeout"].parse::().unwrap(); /// AWS Lambda function concurrency. From 01afe3e1a259643cc3930af810e1b029e7845d19 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Tue, 1 Feb 2022 04:22:48 -0500 Subject: [PATCH 02/22] feat(arch): add microbench to evaluate ops on x86_64 and arm64 (#455) * feat(arch): add microbench to evaluate ops on x86_64 and arm64 * fix: decrease the data size for unit test * fix(cli): parse async type * fix: clear unused modules --- benchmarks/Cargo.toml | 4 + benchmarks/src/arch/main.rs | 101 ++++++++++++++ benchmarks/src/lib.rs | 4 + benchmarks/src/nexmark/main.rs | 2 +- flock-cli/src/arch.rs | 107 +++++++++++++++ flock-cli/src/main.rs | 1 + flock-cli/src/nexmark.rs | 6 +- flock-cli/src/repl.rs | 3 + flock-function/src/aws/actor.rs | 3 +- flock-function/src/aws/arch/mod.rs | 17 +++ flock-function/src/aws/arch/ops/filter.sql | 1 + flock-function/src/aws/arch/ops/group-by.sql | 1 + flock-function/src/aws/arch/ops/join.sql | 1 + flock-function/src/aws/arch/ops/sort.sql | 1 + flock-function/src/aws/arch/source.rs | 135 +++++++++++++++++++ flock-function/src/aws/main.rs | 8 +- flock-function/src/aws/nexmark/source.rs | 1 - flock/src/datasource/mod.rs | 3 + flock/src/runtime/context.rs | 5 + 19 files changed, 392 insertions(+), 12 deletions(-) create mode 100644 benchmarks/src/arch/main.rs create mode 100644 flock-cli/src/arch.rs create mode 100644 flock-function/src/aws/arch/mod.rs create mode 100644 flock-function/src/aws/arch/ops/filter.sql create mode 100644 flock-function/src/aws/arch/ops/group-by.sql create mode 100644 flock-function/src/aws/arch/ops/join.sql create mode 100644 flock-function/src/aws/arch/ops/sort.sql create mode 100644 flock-function/src/aws/arch/source.rs diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index c37a220a..4cce02c2 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -45,3 +45,7 @@ path = "src/ysb/main.rs" [[bin]] name = "s3_bench" path = "src/s3/main.rs" + +[[bin]] +name = "arch_bench" +path = "src/arch/main.rs" diff --git a/benchmarks/src/arch/main.rs b/benchmarks/src/arch/main.rs new file mode 100644 index 00000000..bc45948c --- /dev/null +++ b/benchmarks/src/arch/main.rs @@ -0,0 +1,101 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! This is a macrobenchmark to measure the performance of relational operators +//! on x86_64 and arm64 architectures. +//! +//! The benchmark is run on a single cloud function. The operators we benchmark +//! are: +//! +//! * hash join +//! * projection and filter +//! * sort +//! * aggregation (group by) + +#[path = "../rainbow.rs"] +mod rainbow; + +use flock::aws::{cloudwatch, lambda}; +use flock::prelude::*; +use humantime::parse_duration; +use lazy_static::lazy_static; +use log::info; +use rainbow::{rainbow_println, rainbow_string}; +use structopt::StructOpt; + +lazy_static! { + pub static ref ARCH_SOURCE_LOG_GROUP: String = "/aws/lambda/flock_datasource".to_string(); +} + +#[derive(Default, Clone, Debug, StructOpt)] +pub struct ArchBenchmarkOpt { + /// Number of events generated + #[structopt(short = "e", long = "events", default_value = "1000")] + pub events: usize, + + /// The worker function's memory size + #[structopt(short = "m", long = "memory_size", default_value = "128")] + pub memory_size: i64, + + /// The system architecture to use + #[structopt(short = "a", long = "arch", default_value = "x86_64")] + pub architecture: String, +} + +#[allow(dead_code)] +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + arch_benchmark(&mut ArchBenchmarkOpt::from_args()).await?; + Ok(()) +} + +pub async fn arch_benchmark(opt: &mut ArchBenchmarkOpt) -> Result<()> { + rainbow_println("================================================================"); + rainbow_println(" Running the benchmark "); + rainbow_println("================================================================"); + info!("Running the ARCH benchmark with the following options:\n"); + rainbow_println(format!("{:#?}\n", opt)); + + let arch_source_ctx = ExecutionContext { + plan: CloudExecutionPlan::new(vec![FLOCK_EMPTY_PLAN.clone()], None), + name: FLOCK_DATA_SOURCE_FUNC_NAME.clone(), + next: CloudFunction::Sink(DataSinkType::Blackhole), + ..Default::default() + }; + + // Create the function for the arch benchmark. + info!( + "Creating lambda function: {}", + rainbow_string(FLOCK_DATA_SOURCE_FUNC_NAME.clone()) + ); + lambda::create_function(&arch_source_ctx, opt.memory_size, &opt.architecture).await?; + + let p = serde_json::to_vec(&Payload { + datasource: DataSource::Arch(opt.events), + ..Default::default() + })? + .into(); + lambda::invoke_function( + &FLOCK_DATA_SOURCE_FUNC_NAME, + &FLOCK_LAMBDA_ASYNC_CALL, + Some(p), + ) + .await?; + + info!("Waiting for the current invocations to be logged."); + tokio::time::sleep(parse_duration("5s").unwrap()).await; + cloudwatch::fetch(&ARCH_SOURCE_LOG_GROUP, parse_duration("1min").unwrap()).await?; + + Ok(()) +} diff --git a/benchmarks/src/lib.rs b/benchmarks/src/lib.rs index 75c2dd7a..234f8ce6 100644 --- a/benchmarks/src/lib.rs +++ b/benchmarks/src/lib.rs @@ -19,5 +19,9 @@ pub use nexmark::{nexmark_benchmark, NexmarkBenchmarkOpt}; pub mod ysb; pub use ysb::{ysb_benchmark, YSBBenchmarkOpt}; +#[path = "./arch/main.rs"] +pub mod arch; +pub use arch::{arch_benchmark, ArchBenchmarkOpt}; + pub mod rainbow; pub use rainbow::{rainbow_println, rainbow_string}; diff --git a/benchmarks/src/nexmark/main.rs b/benchmarks/src/nexmark/main.rs index d445e3eb..7997728d 100644 --- a/benchmarks/src/nexmark/main.rs +++ b/benchmarks/src/nexmark/main.rs @@ -65,7 +65,7 @@ pub struct NexmarkBenchmarkOpt { #[structopt(short = "g", long = "generators", default_value = "1")] pub generators: usize, - /// Number of threads to use for parallel execution + /// Number of seconds to run each test #[structopt(short = "s", long = "seconds", default_value = "10")] pub seconds: usize, diff --git a/flock-cli/src/arch.rs b/flock-cli/src/arch.rs new file mode 100644 index 00000000..ac5e9e0f --- /dev/null +++ b/flock-cli/src/arch.rs @@ -0,0 +1,107 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Flock CLI measures the performance of relational operators on different +//! architectures such as x86, and ARM. + +use anyhow::{anyhow, Context as _, Ok, Result}; +use benchmarks::{arch_benchmark, rainbow_println, ArchBenchmarkOpt}; +use clap::{App, AppSettings, Arg, ArgMatches}; +use log::warn; + +pub fn command(matches: &ArgMatches) -> Result<()> { + let (command, matches) = match matches.subcommand() { + Some((command, matches)) => (command, matches), + None => unreachable!(), + }; + + match command { + "run" => run(matches), + _ => { + warn!("{} command is not implemented", command); + Ok(()) + } + } + .with_context(|| anyhow!("{} command failed", command))?; + + Ok(()) +} + +pub fn command_args() -> App<'static> { + App::new("arch") + .about("Benchmark the performance of relational operators on different architectures") + .setting(AppSettings::SubcommandRequired) + .subcommand(run_args()) +} + +fn run_args() -> App<'static> { + App::new("run") + .about("Runs the arch Benchmark") + .arg( + Arg::new("num events") + .short('e') + .long("events") + .help("Runs the arch benchmark with a number of events") + .takes_value(true) + .default_value("1000"), + ) + .arg( + Arg::new("memory size") + .short('m') + .long("memory-size") + .help("Sets the memory size (MB) for the worker function") + .takes_value(true) + .default_value("128"), + ) + .arg( + Arg::new("architecture") + .short('r') + .long("arch") + .help("Sets the architecture for the worker function") + .takes_value(true) + .possible_values(&["x86_64", "arm64"]) + .default_value("x86_64"), + ) +} + +pub fn run(matches: &ArgMatches) -> Result<()> { + let mut opt = ArchBenchmarkOpt::default(); + + if matches.is_present("num events") { + opt.events = matches + .value_of("num events") + .unwrap() + .parse::() + .with_context(|| anyhow!("Invalid num events"))?; + } + + if matches.is_present("memory size") { + opt.memory_size = matches + .value_of("memory size") + .unwrap() + .parse::() + .with_context(|| anyhow!("Invalid memory size"))?; + } + + if matches.is_present("architecture") { + opt.architecture = matches + .value_of("architecture") + .unwrap() + .parse::() + .with_context(|| anyhow!("Invalid architecture"))?; + } + + rainbow_println(include_str!("./flock")); + + futures::executor::block_on(arch_benchmark(&mut opt)).map_err(|e| e.into()) +} diff --git a/flock-cli/src/main.rs b/flock-cli/src/main.rs index 1e34dcac..201ee310 100644 --- a/flock-cli/src/main.rs +++ b/flock-cli/src/main.rs @@ -11,6 +11,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +mod arch; mod args; mod fsql; mod lambda; diff --git a/flock-cli/src/nexmark.rs b/flock-cli/src/nexmark.rs index fe96d410..1edde162 100644 --- a/flock-cli/src/nexmark.rs +++ b/flock-cli/src/nexmark.rs @@ -183,11 +183,7 @@ pub fn run(matches: &ArgMatches) -> Result<()> { } if matches.is_present("async type") { - opt.async_type = matches - .value_of("async type") - .unwrap() - .parse::() - .with_context(|| anyhow!("Invalid async type"))?; + opt.async_type = true; } if matches.is_present("memory size") { diff --git a/flock-cli/src/repl.rs b/flock-cli/src/repl.rs index cdfa0a42..63ced93f 100644 --- a/flock-cli/src/repl.rs +++ b/flock-cli/src/repl.rs @@ -11,6 +11,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use crate::arch; use crate::args; use crate::fsql; use crate::lambda; @@ -37,6 +38,7 @@ pub async fn main() -> Result<()> { .subcommand(ysb::command_args()) .subcommand(s3::command_args()) .subcommand(lambda::command_args()) + .subcommand(arch::command_args()) .subcommand(fsql::command_args()); let global_matches = app_cli.get_matches(); @@ -53,6 +55,7 @@ pub async fn main() -> Result<()> { "s3" => s3::command(matches), "lambda" => lambda::command(matches), "fsql" => fsql::command(matches), + "arch" => arch::command(matches), _ => { warn!("{} command is not implemented", command); Ok(()) diff --git a/flock-function/src/aws/actor.rs b/flock-function/src/aws/actor.rs index f5d02026..2501dbe0 100644 --- a/flock-function/src/aws/actor.rs +++ b/flock-function/src/aws/actor.rs @@ -23,7 +23,6 @@ use lazy_static::lazy_static; use log::info; use rand::{rngs::StdRng, Rng, SeedableRng}; use rayon::prelude::*; -use serde_json::json; use serde_json::Value; use std::collections::HashMap; use std::collections::HashSet; @@ -354,7 +353,7 @@ async fn invoke_next_functions( } Ok(Value::Null) } - CloudFunction::Group((group_name, _)) => { + CloudFunction::Group(..) => { if !ctx.is_shuffling().await? { let next_function = ring.get(&uuid.qid).expect("hash ring failure.").to_string(); let mut payload = to_payload( diff --git a/flock-function/src/aws/arch/mod.rs b/flock-function/src/aws/arch/mod.rs new file mode 100644 index 00000000..6b875e16 --- /dev/null +++ b/flock-function/src/aws/arch/mod.rs @@ -0,0 +1,17 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! The data source handler of the NEXMark benchmark. + +mod source; +pub use source::handler; diff --git a/flock-function/src/aws/arch/ops/filter.sql b/flock-function/src/aws/arch/ops/filter.sql new file mode 100644 index 00000000..000ff65d --- /dev/null +++ b/flock-function/src/aws/arch/ops/filter.sql @@ -0,0 +1 @@ +SELECT auction, price FROM bid WHERE auction % 123 = 0; diff --git a/flock-function/src/aws/arch/ops/group-by.sql b/flock-function/src/aws/arch/ops/group-by.sql new file mode 100644 index 00000000..945558f9 --- /dev/null +++ b/flock-function/src/aws/arch/ops/group-by.sql @@ -0,0 +1 @@ +SELECT auction, Count(*) FROM bid GROUP BY auction; diff --git a/flock-function/src/aws/arch/ops/join.sql b/flock-function/src/aws/arch/ops/join.sql new file mode 100644 index 00000000..0cb53156 --- /dev/null +++ b/flock-function/src/aws/arch/ops/join.sql @@ -0,0 +1 @@ +SELECT * FROM auction INNER JOIN bid ON a_id = auction; diff --git a/flock-function/src/aws/arch/ops/sort.sql b/flock-function/src/aws/arch/ops/sort.sql new file mode 100644 index 00000000..a104eed0 --- /dev/null +++ b/flock-function/src/aws/arch/ops/sort.sql @@ -0,0 +1 @@ +SELECT * FROM bid ORDER BY bidder; diff --git a/flock-function/src/aws/arch/source.rs b/flock-function/src/aws/arch/source.rs new file mode 100644 index 00000000..3ade54f7 --- /dev/null +++ b/flock-function/src/aws/arch/source.rs @@ -0,0 +1,135 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! The entry point for the arch benchmark on cloud functions. + +use flock::datasource::nexmark::event::{Auction, Bid}; +use flock::datasource::nexmark::register_nexmark_tables; +use flock::datasource::nexmark::NEXMarkSource; +use flock::prelude::*; +use log::info; +use serde_json::Value; +use std::sync::Arc; +use std::time::Instant; + +macro_rules! eval_operator { + ($CTX: ident, $EVENTS: ident, $OPERATOR:literal, $PATH:literal) => { + let auction_schema = Arc::new(Auction::schema()); + let bid_schema = Arc::new(Bid::schema()); + let auctions = event_bytes_to_batch(&$EVENTS.auctions, auction_schema, 1024); + let bids = event_bytes_to_batch(&$EVENTS.bids, bid_schema, 1024); + + let df_ctx = register_nexmark_tables().await?; + let sql = include_str!($PATH); + let plan = df_ctx.create_logical_plan(sql.as_ref())?; + let plan = df_ctx.optimize(&plan)?; + let plan = df_ctx.create_physical_plan(&plan).await?; + + $CTX.set_plan(CloudExecutionPlan::new(vec![plan], None)) + .await; + $CTX.feed_data_sources(vec![vec![bids.clone()], vec![auctions.clone()]]) + .await?; + + let mut times = vec![]; + for _ in 0..10 { + let start = Instant::now(); + $CTX.execute().await?; + let end = start.elapsed(); + times.push(end); + } + let avg_time = times + .iter() + .fold(std::time::Duration::new(0, 0), |acc, x| acc + *x) + .as_secs_f64() + / times.len() as f64; + + times + .into_iter() + .enumerate() + .for_each(|(i, x)| info!("#{}: {}", i, x.as_secs_f64())); + info!( + "[OK] {} operator finished in {} seconds.", + $OPERATOR, avg_time + ); + }; +} + +/// The endpoint of the data source generator function invocation. The data +/// source generator function is responsible for generating the data packets for +/// the query no matter what type of query it is. +/// +/// # Arguments +/// * `ctx` - The runtime context of the function. +/// * `payload` - The payload of the function. +/// +/// # Returns +/// A JSON object that contains the return value of the function invocation. +pub async fn handler(ctx: &mut ExecutionContext, payload: Payload) -> Result { + let events_per_second = match payload.datasource.clone() { + DataSource::Arch(events) => events, + _ => unreachable!(), + }; + + let seconds = 1; + let threads = 1; + let nexmark = NEXMarkSource::new(seconds, threads, events_per_second, Window::ElementWise); + + let stream = nexmark.generate_data()?; + let (events, (persons_num, auctions_num, bids_num)) = + stream.select(0, 0).expect("Failed to select event."); + + info!( + "Selecting events for epoch {}: {} persons, {} auctions, {} bids.", + 0, persons_num, auctions_num, bids_num + ); + + info!("[OK] Generated nexmark events."); + + // 1. filter operators + eval_operator!(ctx, events, "filter", "./ops/filter.sql"); + + // 2. join operators + eval_operator!(ctx, events, "join", "./ops/join.sql"); + + // 3. aggregate operators + eval_operator!(ctx, events, "aggregate", "./ops/group-by.sql"); + + // 4. sort operators + eval_operator!(ctx, events, "sort", "./ops/sort.sql"); + + Ok(Value::Null) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_arch_benchmark() -> Result<()> { + let mut ctx = ExecutionContext { + plan: CloudExecutionPlan::new(vec![FLOCK_EMPTY_PLAN.clone()], None), + name: FLOCK_DATA_SOURCE_FUNC_NAME.clone(), + next: CloudFunction::Sink(DataSinkType::Blackhole), + ..Default::default() + }; + + let payload = Payload { + datasource: DataSource::Arch(5000), + ..Default::default() + }; + + handler(&mut ctx, payload).await?; + + Ok(()) + } +} diff --git a/flock-function/src/aws/main.rs b/flock-function/src/aws/main.rs index af3b681b..6586c6f8 100644 --- a/flock-function/src/aws/main.rs +++ b/flock-function/src/aws/main.rs @@ -16,6 +16,7 @@ #![feature(get_mut_unchecked)] mod actor; +mod arch; mod cloud_context; mod nexmark; mod s3; @@ -29,9 +30,9 @@ use lambda_runtime::{service_fn, LambdaEvent}; use log::info; use serde_json::Value; -#[cfg(feature = "snmalloc")] -#[global_allocator] -static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; +// #[cfg(feature = "snmalloc")] +// #[global_allocator] +// static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; #[cfg(feature = "mimalloc")] #[global_allocator] @@ -52,6 +53,7 @@ async fn handler(event: LambdaEvent) -> Result { DataSource::NEXMarkEvent(_) => nexmark::handler(ctx, payload).await, DataSource::YSBEvent(_) => ysb::handler(ctx, payload).await, DataSource::S3(_) => s3::handler(ctx, payload).await, + DataSource::Arch(_) => arch::handler(ctx, payload).await, _ => unimplemented!(), } } diff --git a/flock-function/src/aws/nexmark/source.rs b/flock-function/src/aws/nexmark/source.rs index b005949b..e5a873bc 100644 --- a/flock-function/src/aws/nexmark/source.rs +++ b/flock-function/src/aws/nexmark/source.rs @@ -16,7 +16,6 @@ use crate::window::*; use flock::prelude::*; use log::info; -use serde_json::json; use serde_json::Value; use std::sync::Arc; diff --git a/flock/src/datasource/mod.rs b/flock/src/datasource/mod.rs index 9b1d4ab3..421cf1c3 100644 --- a/flock/src/datasource/mod.rs +++ b/flock/src/datasource/mod.rs @@ -26,6 +26,7 @@ use serde::{Deserialize, Serialize}; pub type RelationPartitions = Vec>; /// To determine the function type to be called: sync or async. pub type FastAggregate = bool; +type NumEvents = usize; /// A streaming data source trait. pub trait DataStream { @@ -103,6 +104,8 @@ pub enum DataSource { S3(NEXMarkSource), /// Data source from the local memory. Memory, + /// benchmarking on x86_64 and arm64. + Arch(NumEvents), /// Unknown data source. UnknownEvent, } diff --git a/flock/src/runtime/context.rs b/flock/src/runtime/context.rs index 83c5e70e..d42ccd8d 100644 --- a/flock/src/runtime/context.rs +++ b/flock/src/runtime/context.rs @@ -160,6 +160,11 @@ impl ExecutionContext { self.plan.get_execution_plans().await } + /// Sets the execution plan of the current execution context. + pub async fn set_plan(&mut self, plan: CloudExecutionPlan) { + self.plan = plan; + } + /// Executes the physical plan. /// /// `execute` must be called after the execution of `feed_one_source` or From 21f070df858e77b9db2be00970460c30858d8b34 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Tue, 1 Feb 2022 12:30:07 -0500 Subject: [PATCH 03/22] feat(configure): add option to disable handwritten arrow kernels (#456) --- configure | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 614c9a65..f789175a 100755 --- a/configure +++ b/configure @@ -28,6 +28,7 @@ Help() { echo "c Compile and deploy the benchmark." echo "r Run the benchmark." echo "a Build the benchmark with specific architechture. x86_64 or arm64. Default is x86_64." + echo "n Disable handwritten arrow kernels that explicitly use SIMD intrinsics." echo } @@ -67,7 +68,8 @@ Build_and_Deploy() { echo $(echogreen "[1/3]") $(echoblue "Compiling Flock Lambda Function...") echo cd flock-function - cargo build --target $target_arch --release --features "simd mimalloc" + echo "Building with features: $features" + cargo build --target $target_arch --release --features $features cd .. echo echo $(echogreen "[2/3]") $(echoblue "Compiling Flock CLI...") @@ -106,12 +108,13 @@ Run() { target="unknown" compile=false run=false +handwritten_arrow_kernels=true rustup install nightly-2022-01-20 rustup default nightly-2022-01-20 # Get the options -while getopts "hgcra:" option; do +while getopts "hgcra:n" option; do case $option in h) # display Help Help @@ -130,6 +133,9 @@ while getopts "hgcra:" option; do a) # build the benchmark with specific architechture. target=$OPTARG ;; + n) # disable handwritten arrow kernels that explicitly use SIMD intrinsics. + handwritten_arrow_kernels=false + ;; \?) # Invalid option echo $(echored "Error: Invalid option") echo @@ -163,6 +169,12 @@ if [ "$target_arch" != "$host_arch" ]; then export PKG_CONFIG_ALLOW_CROSS=1 fi +if [ "$handwritten_arrow_kernels" = true ]; then + features="simd mimalloc" +else + features="mimalloc" +fi + if [ "$compile" = true ]; then Build_and_Deploy fi From bc7c03f035279fe7285bcc7d6c67dea9e273e026 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Fri, 4 Feb 2022 02:56:04 -0500 Subject: [PATCH 04/22] fix: typo in configure --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index f789175a..49363712 100755 --- a/configure +++ b/configure @@ -69,7 +69,7 @@ Build_and_Deploy() { echo cd flock-function echo "Building with features: $features" - cargo build --target $target_arch --release --features $features + cargo build --target $target_arch --release --features "$features" cd .. echo echo $(echogreen "[2/3]") $(echoblue "Compiling Flock CLI...") From 94647630e6a366e74559dd83bc1b16d55de007d7 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Sat, 5 Feb 2022 02:47:33 -0500 Subject: [PATCH 05/22] refactor(window): to make it more readable (#457) --- flock-function/Cargo.toml | 2 +- flock-function/src/aws/nexmark/source.rs | 10 +- flock-function/src/aws/window.rs | 992 ------------------- flock-function/src/aws/window/elementwise.rs | 186 ++++ flock-function/src/aws/window/global.rs | 307 ++++++ flock-function/src/aws/window/hopping.rs | 124 +++ flock-function/src/aws/window/mod.rs | 55 + flock-function/src/aws/window/session.rs | 314 ++++++ flock-function/src/aws/window/tumbling.rs | 116 +++ flock-function/src/aws/ysb/source.rs | 2 +- flock/Cargo.toml | 2 +- 11 files changed, 1110 insertions(+), 1000 deletions(-) delete mode 100644 flock-function/src/aws/window.rs create mode 100644 flock-function/src/aws/window/elementwise.rs create mode 100644 flock-function/src/aws/window/global.rs create mode 100644 flock-function/src/aws/window/hopping.rs create mode 100644 flock-function/src/aws/window/mod.rs create mode 100644 flock-function/src/aws/window/session.rs create mode 100644 flock-function/src/aws/window/tumbling.rs diff --git a/flock-function/Cargo.toml b/flock-function/Cargo.toml index b6146764..3ce9ce75 100644 --- a/flock-function/Cargo.toml +++ b/flock-function/Cargo.toml @@ -22,7 +22,7 @@ datafusion = { git = "https://github.com/flock-lab/arrow-datafusion", branch = " env_logger = "^0.9" flock = { path = "../flock" } futures = "0.3.12" -hashring = "0.2.0" +hashring = { git = "https://github.com/flock-lab/hashring-rs", branch = "flock" } itertools = "0.10.0" lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master" } lazy_static = "1.4" diff --git a/flock-function/src/aws/nexmark/source.rs b/flock-function/src/aws/nexmark/source.rs index e5a873bc..7f123675 100644 --- a/flock-function/src/aws/nexmark/source.rs +++ b/flock-function/src/aws/nexmark/source.rs @@ -56,19 +56,19 @@ pub async fn handler(ctx: &mut ExecutionContext, payload: Payload) -> Result { - tumbling_window_tasks(payload, events, sec, window_size).await?; + tumbling::launch_tasks(payload, events, sec, window_size).await?; } Window::Hopping((window_size, hop_size)) => { - hopping_window_tasks(payload, events, sec, window_size, hop_size).await?; + hopping::launch_tasks(payload, events, sec, window_size, hop_size).await?; } Window::ElementWise => { - elementwise_tasks(ctx, payload, events, sec).await?; + elementwise::launch_tasks(ctx, payload, events, sec).await?; } Window::Session(Schedule::Seconds(timeout)) => { - session_window_tasks(payload, events, sec, timeout).await?; + session::launch_tasks(payload, events, sec, timeout).await?; } Window::Global(Schedule::Seconds(window_size)) => { - global_window_tasks(payload, events, sec, window_size).await?; + global::launch_tasks(payload, events, sec, window_size).await?; } _ => unimplemented!(), }; diff --git a/flock-function/src/aws/window.rs b/flock-function/src/aws/window.rs deleted file mode 100644 index 3938f3a1..00000000 --- a/flock-function/src/aws/window.rs +++ /dev/null @@ -1,992 +0,0 @@ -// Copyright (c) 2020-present, UMD Database Group. -// -// This program is free software: you can use, redistribute, and/or modify -// it under the terms of the GNU Affero General Public License, version 3 -// or later ("AGPL"), as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use crate::actor::*; -use crate::{consistent_hash_context, ConsistentHashContext, CONSISTENT_HASH_CONTEXT}; -use chrono::{DateTime, NaiveDateTime, Utc}; -use datafusion::arrow::array::{ - Int32Array, TimestampMillisecondArray, TimestampNanosecondArray, UInt64Array, -}; -use datafusion::arrow::record_batch::RecordBatch; -use datafusion::datasource::MemTable; -use datafusion::execution::context::ExecutionContext as DataFusionExecutionContext; -use datafusion::logical_plan::{col, count_distinct}; -use datafusion::physical_plan::collect_partitioned; -use datafusion::physical_plan::empty::EmptyExec; -use datafusion::physical_plan::expressions::col as expr_col; -use datafusion::physical_plan::Partitioning::{HashDiff, RoundRobinBatch}; -use flock::aws::{lambda, s3}; -use flock::datasource::nexmark::config::BASE_TIME; -use flock::prelude::*; -use log::{info, warn}; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Instant; - -/// Generate tumble windows workloads for the benchmark on cloud -/// function services. -/// -/// # Arguments -/// * `payload` - The payload of the function. -/// * `stream` - the source stream of events. -/// * `seconds` - the total number of seconds to generate workloads. -/// * `window_size` - the size of the window in seconds. -pub async fn tumbling_window_tasks( - payload: Payload, - stream: Arc, - seconds: usize, - window_size: usize, -) -> Result<()> { - if seconds < window_size { - warn!( - "seconds: {} is less than window_size: {}", - seconds, window_size - ); - } - let sync = infer_invocation_type(&payload.metadata)?; - let invocation_type = if sync { - FLOCK_LAMBDA_SYNC_CALL.to_string() - } else { - FLOCK_LAMBDA_ASYNC_CALL.to_string() - }; - - let (ring, group_name) = consistent_hash_context!(); - - let mut window: Box> = Box::new(vec![]); - - for time in 0..seconds / window_size { - let start = time * window_size; - let end = start + window_size; - - // Update the tumbling window, and generate the next batch of data. - window.drain(..); - for t in start..end { - window.push(stream.select_event_to_batches( - t, - 0, // generator id - payload.query_number, - sync, - )?); - } - - // Calculate the total data packets to be sent. - let size = window - .iter() - .map(|(a, b)| if a.len() > b.len() { a.len() } else { b.len() }) - .sum::(); - - let mut uuid_builder = UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); - - // Distribute the window data to a single function execution environment. - let function_name = ring - .get(&uuid_builder.qid) - .expect("hash ring failure.") - .to_string(); - - // Call the next stage of the dataflow graph. - info!( - "[OK] Send {} events from a window (epoch: {}-{}) to function: {}.", - size, - time, - time + window_size, - function_name - ); - - let mut eid = 0; - let empty = vec![]; - for (a, b) in window.iter() { - let num = if a.len() > b.len() { a.len() } else { b.len() }; - for i in 0..num { - let payload = serde_json::to_vec(&to_payload( - if i < a.len() { &a[i] } else { &empty }, - if i < b.len() { &b[i] } else { &empty }, - uuid_builder.next_uuid(), - sync, - ))?; - info!( - "[OK] Event {} - {} function payload bytes: {}", - eid, - function_name, - payload.len() - ); - lambda::invoke_function(&function_name, &invocation_type, Some(payload.into())) - .await?; - eid += 1; - } - } - } - - Ok(()) -} - -/// Generate hopping windows workloads for the benchmark on cloud -/// function services. -/// -/// # Arguments -/// * `payload` - The payload of the function. -/// * `stream` - the source stream of events. -/// * `seconds` - the total number of seconds to generate workloads. -/// * `window_size` - the size of the window in seconds. -/// * `hop_size` - the size of the hop in seconds. -pub async fn hopping_window_tasks( - payload: Payload, - stream: Arc, - seconds: usize, - window_size: usize, - hop_size: usize, -) -> Result<()> { - if seconds < window_size { - warn!( - "seconds: {} is less than window_size: {}", - seconds, window_size - ); - } - let sync = infer_invocation_type(&payload.metadata)?; - let invocation_type = if sync { - FLOCK_LAMBDA_SYNC_CALL.to_string() - } else { - FLOCK_LAMBDA_ASYNC_CALL.to_string() - }; - - let (ring, group_name) = consistent_hash_context!(); - let mut window: Box> = Box::new(vec![]); - - for time in (0..seconds).step_by(hop_size) { - if time + window_size > seconds { - break; - } - - // Move the hopping window forward. - let mut start_pos = 0; - if !window.is_empty() { - window.drain(..hop_size); - start_pos = window_size - hop_size; - } - - // Update the hopping window, and generate the next batch of data. - for t in time + start_pos..time + window_size { - window.push(stream.select_event_to_batches( - t, - 0, // generator id - payload.query_number, - sync, - )?); - } - - // Calculate the total data packets to be sent. - let size = window - .iter() - .map(|(a, b)| if a.len() > b.len() { a.len() } else { b.len() }) - .sum::(); - - let mut uuid_builder = UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); - - // Distribute the window data to a single function execution environment. - let function_name = ring - .get(&uuid_builder.qid) - .expect("hash ring failure.") - .to_string(); - - // Call the next stage of the dataflow graph. - info!( - "[OK] Send {} events from a window (epoch: {}-{}) to function: {}.", - size, - time, - time + window_size, - function_name - ); - - let mut eid = 0; - let empty = vec![]; - for (a, b) in window.iter() { - let num = if a.len() > b.len() { a.len() } else { b.len() }; - for i in 0..num { - let payload = serde_json::to_vec(&to_payload( - if i < a.len() { &a[i] } else { &empty }, - if i < b.len() { &b[i] } else { &empty }, - uuid_builder.next_uuid(), - sync, - ))?; - info!( - "[OK] Event {} - {} function's payload bytes: {}", - eid, - function_name, - payload.len() - ); - lambda::invoke_function(&function_name, &invocation_type, Some(payload.into())) - .await?; - eid += 1; - } - } - } - - Ok(()) -} - -/// Get back the input data from the registered table after the query is -/// executed to avoid copying the input data. -fn get_input_from_registered_table( - ctx: &mut DataFusionExecutionContext, - table_name: &str, -) -> Result>> { - Ok(unsafe { - Arc::get_mut_unchecked( - &mut ctx - .deregister_table(table_name) - .map_err(FlockError::DataFusion)? - .ok_or_else(|| { - FlockError::Internal(format!("Failed to deregister table `{}`", table_name)) - })?, - ) - .as_mut_any() - .downcast_mut::() - .unwrap() - .batches() - }) -} - -/// Add each unique partition to a distinct session window. -/// -/// # Arguments -/// * `partitions` - the partitions to be added. -/// * `windows` - the session windows. -/// * `timeouts` - the session timeouts. -/// -/// # Return -/// The updated session windows. -fn add_partitions_to_session_windows( - partitions: Vec>, - windows: &mut HashMap>>, - timeout: usize, -) -> Result>>> { - Ok(partitions - .into_iter() - .filter(|p| !p.is_empty()) - .map(|p| { - let mut session = vec![]; - let bidder = p[0] - .column(1 /* bidder field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - let current_timestamp = p[0] - .column(3 /* b_date_time field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - let curr_date_time = DateTime::::from_utc( - NaiveDateTime::from_timestamp(current_timestamp / 1000, 0), - Utc, - ); - - if !windows.contains_key(&(bidder as usize)) { - windows - .entry(bidder as usize) - .or_insert_with(Vec::new) - .push(p); - } else { - // get the last batch in the window. - let batch = windows - .get(&(bidder as usize)) - .unwrap() - .last() - .unwrap() - .last() - .unwrap(); - // get the last bid record's timestamp. - let last_timestamp = batch - .column(3 /* b_date_time field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(batch.num_rows() - 1); - let last_date_time = DateTime::::from_utc( - NaiveDateTime::from_timestamp(last_timestamp / 1000, 0), - Utc, - ); - - // If the current date time isn't 10 seconds later than the last date time, - // then we can add the current batch to the window . Otherwise, we have a new - // session window. - if curr_date_time.signed_duration_since(last_date_time) - > chrono::Duration::seconds(timeout as i64) - { - session = windows.remove(&(bidder as usize)).unwrap(); - } - windows - .entry(bidder as usize) - .or_insert_with(Vec::new) - .push(p); - } - session - }) - .collect::>>>()) -} - -/// Find new session windows after timeout. -/// -/// # Arguments -/// * `windows` - the session windows. -/// * `timeout` - the session timeout. -/// * `epoch_count` - the number of epochs have been processed. -/// -/// # Return -/// The keys of the new session windows. -fn find_timeout_session_windows( - windows: &HashMap>>, - timeout: usize, - epoch_count: usize, -) -> Result> { - let mut to_remove = vec![]; - - windows.iter().for_each(|(bidder, batches)| { - // Get the last batch - let batch = batches.last().unwrap().last().unwrap(); - - // Get the last bid's date time - let last_timestamp = batch - .column(3 /* b_date_time field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(batch.num_rows() - 1); - - let last_date_time = - DateTime::::from_utc(NaiveDateTime::from_timestamp(last_timestamp / 1000, 0), Utc); - - let epoch_gap_time = DateTime::::from_utc( - NaiveDateTime::from_timestamp(BASE_TIME as i64 / 1000 + epoch_count as i64, 0), - Utc, - ); - - if epoch_gap_time.signed_duration_since(last_date_time) - > chrono::Duration::seconds(timeout as i64) - { - to_remove.push(bidder.to_owned()); - } - }); - - Ok(to_remove) -} - -/// Session windows group events that arrive at similar times, filtering out -/// periods of time where there is no data. A session window begins when the -/// first event occurs. If another event occurs within the specified timeout -/// from the last ingested event, then the window extends to include the new -/// event. Otherwise if no events occur within the timeout, then the window is -/// closed at the timeout. -pub async fn session_window_tasks( - payload: Payload, - stream: Arc, - seconds: usize, - timeout: usize, -) -> Result<()> { - if seconds < timeout { - warn!("seconds: {} is less than timeout: {}", seconds, timeout); - } - let sync = infer_invocation_type(&payload.metadata)?; - let (group_key, table_name) = infer_session_keys(&payload.metadata)?; - let (ring, group_name) = consistent_hash_context!(); - - let (invocation_type, granule_size) = if sync { - (FLOCK_LAMBDA_SYNC_CALL.to_string(), *FLOCK_SYNC_GRANULE_SIZE) - } else { - ( - FLOCK_LAMBDA_ASYNC_CALL.to_string(), - *FLOCK_ASYNC_GRANULE_SIZE, - ) - }; - - let mut ctx = DataFusionExecutionContext::new(); - let mut windows: HashMap>> = HashMap::new(); - - let events = (0..seconds) - .map(|t| { - let (r1, _) = stream - .select_event_to_batches( - t, - 0, // generator id - payload.query_number, - sync, - ) - .unwrap(); - r1.to_vec() - }) - .collect::>>>(); - - let schema = events[0][0][0].schema(); - - for (time, batches) in events.into_iter().enumerate() { - info!("Processing events in epoch: {}", time); - let table = MemTable::try_new(schema.clone(), batches)?; - ctx.deregister_table(&*table_name)?; - ctx.register_table(&*table_name, Arc::new(table))?; - - // Equivalent to `SELECT COUNT(DISTINCT group_key) FROM table_name;` - let output = ctx - .table(&*table_name)? - .aggregate(vec![], vec![count_distinct(col(&group_key))])? - .collect() - .await?; - - let distinct_keys = output[0] - .column(0) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - - // Each partition has a unique key after `repartition` execution. - let partitions = repartition( - get_input_from_registered_table(&mut ctx, &table_name)?, - HashDiff(vec![expr_col(&group_key, &schema)?], distinct_keys as usize), - ) - .await?; - - // Update the window. - let mut sessions = add_partitions_to_session_windows(partitions, &mut windows, timeout)?; - let to_remove = find_timeout_session_windows(&windows, timeout, time)?; - to_remove.iter().for_each(|bidder| { - sessions.push(windows.remove(bidder).unwrap()); - }); - - let tasks = coalesce_windows(sessions, granule_size)? - .into_iter() - .filter(|session| !session.is_empty()) - .map(|session| { - let function_group = group_name.clone(); - let invoke_type = invocation_type.clone(); - - let query_code = group_name.split('-').next().unwrap(); - let timestamp = Utc::now().timestamp(); - let rand_id = uuid::Uuid::new_v4().as_u128(); - let qid = format!("{}-{}-{}", query_code, timestamp, rand_id); - - // Distribute the window data to a single function execution environment. - let function_name = ring.get(&qid).expect("hash ring failure.").to_string(); - info!("Session window -> function name: {}", function_name); - - tokio::spawn(async move { - let output = repartition(session, RoundRobinBatch(1)).await?; - let window = coalesce_batches(output, granule_size * 2).await?; - let size = window[0].len(); - let mut uuid_builder = - UuidBuilder::new_with_ts_uuid(&function_group, timestamp, rand_id, size); - - // Call the next stage of the dataflow graph. - info!( - "[OK] Send {} events from a session window to function: {}.", - size, function_name - ); - - for (eid, partition) in window.iter().enumerate() { - let payload = serde_json::to_vec(&to_payload( - partition, - &[], - uuid_builder.next_uuid(), - sync, - ))?; - info!( - "[OK] Event {} - {} function's payload bytes: {}", - eid, - function_name, - payload.len() - ); - lambda::invoke_function(&function_name, &invoke_type, Some(payload.into())) - .await?; - } - Ok(()) - }) - }) - .collect::>>>(); - futures::future::join_all(tasks).await; - } - - Ok(()) -} - -/// Add each unique partition to a distinct tumbling window. -/// -/// # Arguments -/// * `partitions` - the partitions to be added. -/// * `windows` - the current tumbling windows. -/// * `window_size` - the size of the window. -/// -/// # Return -/// The updated session windows. -fn add_partitions_to_tumbling_windows( - partitions: Vec>, - windows: &mut HashMap>>, - window_size: usize, -) -> Result>>> { - Ok(partitions - .into_iter() - .filter(|p| !p.is_empty()) - .map(|p| { - let mut window = vec![]; - let bidder = p[0] - .column(1 /* bidder field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - let current_timestamp = p[0] - .column(4 /* p_time field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - let current_process_time = DateTime::::from_utc( - NaiveDateTime::from_timestamp(current_timestamp / 1000 / 1000 / 1000, 0), - Utc, - ); - - if !windows.contains_key(&(bidder as usize)) { - windows - .entry(bidder as usize) - .or_insert_with(Vec::new) - .push(p); - } else { - // get the first batch in the window. - let batch = windows - .get(&(bidder as usize)) - .unwrap() - .first() - .unwrap() - .first() - .unwrap(); - // get the first bid's process time. - let first_timestamp = batch - .column(4 /* p_time field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - let first_process_time = DateTime::::from_utc( - NaiveDateTime::from_timestamp(first_timestamp / 1000 / 1000 / 1000, 0), - Utc, - ); - - // If the current process time isn't 10 seconds later than the beginning of - // the entry, then we can add the current batch to the map. Otherwise, we - // have a new tumbling window. - if current_process_time.signed_duration_since(first_process_time) - > chrono::Duration::seconds(window_size as i64) - { - window = windows.remove(&(bidder as usize)).unwrap(); - } - windows - .entry(bidder as usize) - .or_insert_with(Vec::new) - .push(p); - } - window - }) - .collect::>>>()) -} - -/// Find new tumbling windows after timeout. -/// -/// # Arguments -/// * `windows` - the tumbling windows. -/// * `timeout` - the tumbling timeout. -/// -/// # Return -/// The keys of the new tumbling windows. -fn find_timeout_tumbling_windows( - windows: &HashMap>>, - timeout: usize, -) -> Result> { - let mut to_remove = vec![]; - - windows.iter().for_each(|(bidder, batches)| { - // get the first bid's prcessing time - let first_timestamp = batches[0][0] - .column(4 /* p_time field */) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - - let first_process_time = DateTime::::from_utc( - NaiveDateTime::from_timestamp(first_timestamp / 1000 / 1000 / 1000, 0), - Utc, - ); - if Utc::now().signed_duration_since(first_process_time) - > chrono::Duration::seconds(timeout as i64) - { - to_remove.push(bidder.to_owned()); - } - }); - - Ok(to_remove) -} - -/// This function is used to coalesce smaller session windows or global windows -/// to bigger ones so that the number of events in each payload is greater than -/// the granule size, and close to the payload limit. -fn coalesce_windows( - windows: Vec>>, - granule_size: usize, -) -> Result>>> { - #[allow(unused_assignments)] - let mut curr_size = 0; - let mut total_size = 0; - let mut res = vec![]; - let mut tmp = vec![]; - for window in windows { - curr_size = window - .iter() - .map(|v| v.iter().map(|b| b.num_rows()).sum::()) - .sum::(); - total_size += curr_size; - if total_size <= granule_size * 2 || tmp.is_empty() { - tmp.push(window); - } else { - res.push(tmp.into_iter().flatten().collect::>>()); - tmp = vec![window]; - total_size = curr_size; - } - } - if !tmp.is_empty() { - res.push(tmp.into_iter().flatten().collect::>>()); - } - Ok(res) -} - -/// A global windows assigner assigns all elements with the same key to the same -/// single global window. This windowing scheme is only useful if you also -/// specify a custom trigger. Otherwise, no computation will be performed, as -/// the global window does not have a natural end at which we could process the -/// aggregated elements. -/// -/// # Arguments -/// * `payload` - The payload of the function invocation. -/// * `stream` - The data stream. -/// * `seconds` - The number of seconds to group events into. -/// * `window_size` - The size of the window. -pub async fn global_window_tasks( - payload: Payload, - stream: Arc, - seconds: usize, - window_size: usize, -) -> Result<()> { - if seconds < window_size { - warn!( - "seconds: {} is less than window_size: {}", - seconds, window_size - ); - } - let sync = infer_invocation_type(&payload.metadata)?; - let (group_key, table_name) = infer_session_keys(&payload.metadata)?; - let add_process_time_sql = infer_add_process_time_query(&payload.metadata)?; - let (ring, group_name) = consistent_hash_context!(); - - let (invocation_type, granule_size) = if sync { - (FLOCK_LAMBDA_SYNC_CALL.to_string(), *FLOCK_SYNC_GRANULE_SIZE) - } else { - ( - FLOCK_LAMBDA_ASYNC_CALL.to_string(), - *FLOCK_ASYNC_GRANULE_SIZE, - ) - }; - - let mut ctx = DataFusionExecutionContext::new(); - let mut windows: HashMap>> = HashMap::new(); - - let events = (0..seconds) - .map(|t| { - let (r1, _) = stream - .select_event_to_batches( - t, - 0, // generator id - payload.query_number, - sync, - ) - .unwrap(); - r1.to_vec() - }) - .collect::>>>(); - - let schema = events[0][0][0].schema(); - - for (time, batches) in events.into_iter().enumerate() { - info!("Processing events in epoch: {}", time); - let now = Instant::now(); - let table = MemTable::try_new(schema.clone(), batches)?; - ctx.deregister_table(&*table_name)?; - ctx.register_table(&*table_name, Arc::new(table))?; - - // Equivalent to `SELECT COUNT(DISTINCT group_key) FROM table_name;` - let output = ctx - .table(&*table_name)? - .aggregate(vec![], vec![count_distinct(col(&group_key))])? - .collect() - .await?; - - let distinct_keys = output[0] - .column(0) - .as_any() - .downcast_ref::() - .unwrap() - .value(0); - - // Equivalent to `SELECT *, now() as p_time FROM table_name;` - let output = collect_partitioned(physical_plan(&ctx, &add_process_time_sql).await?).await?; - let schema_with_ptime = output[0][0].schema(); - - // Each partition has a unique key after `repartition` execution. - let partitions = repartition( - output, - HashDiff( - vec![expr_col(&group_key, &schema_with_ptime)?], - distinct_keys as usize, - ), - ) - .await?; - - // Update the window. - let mut tumblings = - add_partitions_to_tumbling_windows(partitions, &mut windows, window_size)?; - let to_remove = find_timeout_tumbling_windows(&windows, window_size)?; - to_remove.iter().for_each(|bidder| { - tumblings.push(windows.remove(bidder).unwrap()); - }); - - let tasks = coalesce_windows(tumblings, granule_size)? - .into_iter() - .filter(|window| !window.is_empty()) - .map(|window| { - let function_group = group_name.clone(); - let invoke_type = invocation_type.clone(); - - let query_code = group_name.split('-').next().unwrap(); - let timestamp = Utc::now().timestamp(); - let rand_id = uuid::Uuid::new_v4().as_u128(); - let qid = format!("{}-{}-{}", query_code, timestamp, rand_id); - - // Distribute the window data to a single function execution environment. - let function_name = ring.get(&qid).expect("hash ring failure.").to_string(); - info!("Tumbling window -> function name: {}", function_name); - - tokio::spawn(async move { - let window = repartition(window, RoundRobinBatch(1)).await?; - let window = coalesce_batches(window, granule_size * 2).await?; - let size = window[0].len(); - let mut uuid_builder = - UuidBuilder::new_with_ts_uuid(&function_group, timestamp, rand_id, size); - - // Call the next stage of the dataflow graph. - info!( - "[OK] Send {} events from a tumbling window to function: {}.", - size, function_name - ); - - for (eid, partition) in window.iter().enumerate() { - let payload = serde_json::to_vec(&to_payload( - partition, - &[], - uuid_builder.next_uuid(), - sync, - ))?; - info!( - "[OK] Event {} - {} function's payload bytes: {}", - eid, - function_name, - payload.len() - ); - lambda::invoke_function(&function_name, &invoke_type, Some(payload.into())) - .await?; - } - Ok(()) - }) - }) - .collect::>>>(); - futures::future::join_all(tasks).await; - - let elapsed = now.elapsed().as_millis() as u64; - if elapsed < 1000 { - std::thread::sleep(std::time::Duration::from_millis(1000 - elapsed)); - } - } - - Ok(()) -} - -/// Generate normal elementwose workloads for the benchmark on cloud -/// function services. -/// -/// # Arguments -/// * `payload` - The payload of the function. -/// * `stream` - the source stream of events. -/// * `seconds` - the total number of seconds to generate workloads. -pub async fn elementwise_tasks( - ctx: &mut ExecutionContext, - payload: Payload, - stream: Arc, - seconds: usize, -) -> Result<()> { - let query_number = payload.query_number; - let metadata = payload.metadata; - let (ring, group_name) = consistent_hash_context!(); - let sync = infer_invocation_type(&metadata)?; - let invocation_type = if sync { - FLOCK_LAMBDA_SYNC_CALL.to_string() - } else { - FLOCK_LAMBDA_ASYNC_CALL.to_string() - }; - - for epoch in 0..seconds { - info!("[OK] Send events (epoch: {}).", epoch); - let events = stream.clone(); - if ring.len() == 1 { - // lambda default concurrency is 1000. - assert!(!ctx.plan.execution_plans.is_empty()); - let exec_plans = &ctx.plan.execution_plans; - if exec_plans[0].as_any().downcast_ref::().is_some() { - // centralized mode - let function_name = group_name.clone(); - let uuid = - UuidBuilder::new_with_ts(&function_name, Utc::now().timestamp(), 1).next_uuid(); - let mut payload = - events.select_event_to_payload(epoch, 0, query_number, uuid, sync)?; - payload.metadata = metadata.clone(); - let bytes = serde_json::to_vec(&payload)?; - info!( - "[OK] {} function's payload bytes: {}", - function_name, - bytes.len() - ); - lambda::invoke_function(&function_name, &invocation_type, Some(bytes.into())) - .await?; - } else { - // distributed mode - let partitions = events.select_event_to_batches( - epoch, - 0, // generator id - payload.query_number, - sync, - )?; - let mut input = vec![]; - for b in vec![partitions.0, partitions.1] { - if !b.is_empty() { - input.push(b); - } - } - - ctx.feed_data_sources(input).await?; - let output = Arc::new(ctx.execute_partitioned().await?); - let size = output[0].len(); - let mut uuid_builder = - UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); - - // Creates the S3 bucket for the current query if state backend is S3. - if ctx - .state_backend - .as_any() - .downcast_ref::() - .is_some() - { - s3::create_bucket(&uuid_builder.qid).await?; - } - - let tasks = (0..size) - .map(|i| { - let data = output.clone(); - let function_name = group_name.clone(); - let meta = metadata.clone(); - let invoke_type = invocation_type.clone(); - let uuid = uuid_builder.next_uuid(); - tokio::spawn(async move { - let mut payload = to_payload( - &data[0][i], - if data.len() == 1 { &[] } else { &data[1][i] }, - uuid, - sync, - ); - payload.query_number = query_number; - payload.metadata = meta; - - let bytes = serde_json::to_vec(&payload)?; - info!( - "[OK] {} function's payload bytes: {}", - function_name, - bytes.len() - ); - lambda::invoke_function( - &function_name, - &invoke_type, - Some(bytes.into()), - ) - .await - .map(|_| ()) - }) - }) - .collect::>>>(); - futures::future::join_all(tasks).await; - ctx.clean_data_sources().await?; - } - } else { - // Calculate the total data packets to be sent. - // transfrom tuple (a, b) to (Arc::new(a), Arc::new(b)) - let (a, b) = events.select_event_to_batches( - epoch, - 0, // generator id - payload.query_number, - sync, - )?; - let size = if a.len() > b.len() { a.len() } else { b.len() }; - - let mut uuid_builder = - UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); - - // Distribute the epoch data to a single function execution environment. - let function_name = ring - .get(&uuid_builder.qid) - .expect("hash ring failure.") - .to_string(); - - // Call the next stage of the dataflow graph. - info!( - "[OK] Send {} events from epoch {} to function: {}.", - size, epoch, function_name - ); - - let empty = vec![]; - for i in 0..size { - let mut payload = to_payload( - if i < a.len() { &a[i] } else { &empty }, - if i < b.len() { &b[i] } else { &empty }, - uuid_builder.next_uuid(), - sync, - ); - payload.query_number = query_number; - payload.metadata = metadata.clone(); - - let bytes = serde_json::to_vec(&payload)?; - info!( - "[OK] Event {} - {} function's payload bytes: {}", - i, - function_name, - bytes.len() - ); - lambda::invoke_function(&function_name, &invocation_type, Some(bytes.into())) - .await?; - } - } - } - - Ok(()) -} diff --git a/flock-function/src/aws/window/elementwise.rs b/flock-function/src/aws/window/elementwise.rs new file mode 100644 index 00000000..e3991480 --- /dev/null +++ b/flock-function/src/aws/window/elementwise.rs @@ -0,0 +1,186 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::actor::*; +use crate::{consistent_hash_context, ConsistentHashContext, CONSISTENT_HASH_CONTEXT}; +use chrono::Utc; +use datafusion::physical_plan::empty::EmptyExec; +use flock::aws::{lambda, s3}; +use flock::prelude::*; +use log::info; +use std::sync::Arc; + +/// Generate normal elementwose workloads for the benchmark on cloud +/// function services. +/// +/// # Arguments +/// * `payload` - The payload of the function. +/// * `stream` - the source stream of events. +/// * `seconds` - the total number of seconds to generate workloads. +pub async fn launch_tasks( + ctx: &mut ExecutionContext, + payload: Payload, + stream: Arc, + seconds: usize, +) -> Result<()> { + let query_number = payload.query_number; + let metadata = payload.metadata; + let (ring, group_name) = consistent_hash_context!(); + let sync = infer_invocation_type(&metadata)?; + let invocation_type = if sync { + FLOCK_LAMBDA_SYNC_CALL.to_string() + } else { + FLOCK_LAMBDA_ASYNC_CALL.to_string() + }; + + for epoch in 0..seconds { + info!("[OK] Send events (epoch: {}).", epoch); + let events = stream.clone(); + if ring.len() == 1 { + // lambda default concurrency is 1000. + assert!(!ctx.plan.execution_plans.is_empty()); + let exec_plans = &ctx.plan.execution_plans; + if exec_plans[0].as_any().downcast_ref::().is_some() { + // centralized mode + let function_name = group_name.clone(); + let uuid = + UuidBuilder::new_with_ts(&function_name, Utc::now().timestamp(), 1).next_uuid(); + let mut payload = + events.select_event_to_payload(epoch, 0, query_number, uuid, sync)?; + payload.metadata = metadata.clone(); + let bytes = serde_json::to_vec(&payload)?; + info!( + "[OK] {} function's payload bytes: {}", + function_name, + bytes.len() + ); + lambda::invoke_function(&function_name, &invocation_type, Some(bytes.into())) + .await?; + } else { + // distributed mode + let partitions = events.select_event_to_batches( + epoch, + 0, // generator id + payload.query_number, + sync, + )?; + let mut input = vec![]; + for b in vec![partitions.0, partitions.1] { + if !b.is_empty() { + input.push(b); + } + } + + ctx.feed_data_sources(input).await?; + let output = Arc::new(ctx.execute_partitioned().await?); + let size = output[0].len(); + let mut uuid_builder = + UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); + + // Creates the S3 bucket for the current query if state backend is S3. + if ctx + .state_backend + .as_any() + .downcast_ref::() + .is_some() + { + s3::create_bucket(&uuid_builder.qid).await?; + } + + let tasks = (0..size) + .map(|i| { + let data = output.clone(); + let function_name = group_name.clone(); + let meta = metadata.clone(); + let invoke_type = invocation_type.clone(); + let uuid = uuid_builder.next_uuid(); + tokio::spawn(async move { + let mut payload = to_payload( + &data[0][i], + if data.len() == 1 { &[] } else { &data[1][i] }, + uuid, + sync, + ); + payload.query_number = query_number; + payload.metadata = meta; + + let bytes = serde_json::to_vec(&payload)?; + info!( + "[OK] {} function's payload bytes: {}", + function_name, + bytes.len() + ); + lambda::invoke_function( + &function_name, + &invoke_type, + Some(bytes.into()), + ) + .await + .map(|_| ()) + }) + }) + .collect::>>>(); + futures::future::join_all(tasks).await; + ctx.clean_data_sources().await?; + } + } else { + // Calculate the total data packets to be sent. + let (a, b) = events.select_event_to_batches( + epoch, + 0, // generator id + payload.query_number, + sync, + )?; + let size = if a.len() > b.len() { a.len() } else { b.len() }; + + let mut uuid_builder = + UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); + + // Distribute the epoch data to a single function execution environment. + let function_name = ring + .get(&uuid_builder.qid) + .expect("hash ring failure.") + .to_string(); + + // Call the next stage of the dataflow graph. + info!( + "[OK] Send {} events from epoch {} to function: {}.", + size, epoch, function_name + ); + + let empty = vec![]; + for i in 0..size { + let mut payload = to_payload( + if i < a.len() { &a[i] } else { &empty }, + if i < b.len() { &b[i] } else { &empty }, + uuid_builder.next_uuid(), + sync, + ); + payload.query_number = query_number; + payload.metadata = metadata.clone(); + + let bytes = serde_json::to_vec(&payload)?; + info!( + "[OK] Event {} - {} function's payload bytes: {}", + i, + function_name, + bytes.len() + ); + lambda::invoke_function(&function_name, &invocation_type, Some(bytes.into())) + .await?; + } + } + } + + Ok(()) +} diff --git a/flock-function/src/aws/window/global.rs b/flock-function/src/aws/window/global.rs new file mode 100644 index 00000000..177a81b5 --- /dev/null +++ b/flock-function/src/aws/window/global.rs @@ -0,0 +1,307 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::coalesce_windows; +use crate::actor::*; +use crate::{consistent_hash_context, ConsistentHashContext, CONSISTENT_HASH_CONTEXT}; +use chrono::{DateTime, NaiveDateTime, Utc}; +use datafusion::arrow::array::{Int32Array, TimestampNanosecondArray, UInt64Array}; +use datafusion::arrow::record_batch::RecordBatch; +use datafusion::datasource::MemTable; +use datafusion::execution::context::ExecutionContext as DataFusionExecutionContext; +use datafusion::logical_plan::{col, count_distinct}; +use datafusion::physical_plan::collect_partitioned; +use datafusion::physical_plan::expressions::col as expr_col; +use datafusion::physical_plan::Partitioning::{HashDiff, RoundRobinBatch}; +use flock::aws::lambda; +use flock::prelude::*; +use log::{info, warn}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Instant; + +/// Add each unique partition to a distinct tumbling window. +/// +/// # Arguments +/// * `partitions` - the partitions to be added. +/// * `windows` - the current tumbling windows. +/// * `window_size` - the size of the window. +/// +/// # Return +/// The updated session windows. +fn add_partitions_to_tumbling_windows( + partitions: Vec>, + windows: &mut HashMap>>, + window_size: usize, +) -> Result>>> { + Ok(partitions + .into_iter() + .filter(|p| !p.is_empty()) + .map(|p| { + let mut window = vec![]; + let bidder = p[0] + .column(1 /* bidder field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + let current_timestamp = p[0] + .column(4 /* p_time field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + let current_process_time = DateTime::::from_utc( + NaiveDateTime::from_timestamp(current_timestamp / 1000 / 1000 / 1000, 0), + Utc, + ); + + if !windows.contains_key(&(bidder as usize)) { + windows + .entry(bidder as usize) + .or_insert_with(Vec::new) + .push(p); + } else { + // get the first batch in the window. + let batch = windows + .get(&(bidder as usize)) + .unwrap() + .first() + .unwrap() + .first() + .unwrap(); + // get the first bid's process time. + let first_timestamp = batch + .column(4 /* p_time field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + let first_process_time = DateTime::::from_utc( + NaiveDateTime::from_timestamp(first_timestamp / 1000 / 1000 / 1000, 0), + Utc, + ); + + // If the current process time isn't 10 seconds later than the beginning of + // the entry, then we can add the current batch to the map. Otherwise, we + // have a new tumbling window. + if current_process_time.signed_duration_since(first_process_time) + > chrono::Duration::seconds(window_size as i64) + { + window = windows.remove(&(bidder as usize)).unwrap(); + } + windows + .entry(bidder as usize) + .or_insert_with(Vec::new) + .push(p); + } + window + }) + .collect::>>>()) +} + +/// Find new tumbling windows after timeout. +/// +/// # Arguments +/// * `windows` - the tumbling windows. +/// * `timeout` - the tumbling timeout. +/// +/// # Return +/// The keys of the new tumbling windows. +fn find_timeout_tumbling_windows( + windows: &HashMap>>, + timeout: usize, +) -> Result> { + let mut to_remove = vec![]; + + windows.iter().for_each(|(bidder, batches)| { + // get the first bid's prcessing time + let first_timestamp = batches[0][0] + .column(4 /* p_time field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + + let first_process_time = DateTime::::from_utc( + NaiveDateTime::from_timestamp(first_timestamp / 1000 / 1000 / 1000, 0), + Utc, + ); + if Utc::now().signed_duration_since(first_process_time) + > chrono::Duration::seconds(timeout as i64) + { + to_remove.push(bidder.to_owned()); + } + }); + + Ok(to_remove) +} + +/// A global windows assigner assigns all elements with the same key to the same +/// single global window. This windowing scheme is only useful if you also +/// specify a custom trigger. Otherwise, no computation will be performed, as +/// the global window does not have a natural end at which we could process the +/// aggregated elements. +/// +/// # Arguments +/// * `payload` - The payload of the function invocation. +/// * `stream` - The data stream. +/// * `seconds` - The number of seconds to group events into. +/// * `window_size` - The size of the window. +pub async fn launch_tasks( + payload: Payload, + stream: Arc, + seconds: usize, + window_size: usize, +) -> Result<()> { + if seconds < window_size { + warn!( + "seconds: {} is less than window_size: {}", + seconds, window_size + ); + } + let sync = infer_invocation_type(&payload.metadata)?; + let (group_key, table_name) = infer_session_keys(&payload.metadata)?; + let add_process_time_sql = infer_add_process_time_query(&payload.metadata)?; + let (ring, group_name) = consistent_hash_context!(); + + let (invocation_type, granule_size) = if sync { + (FLOCK_LAMBDA_SYNC_CALL.to_string(), *FLOCK_SYNC_GRANULE_SIZE) + } else { + ( + FLOCK_LAMBDA_ASYNC_CALL.to_string(), + *FLOCK_ASYNC_GRANULE_SIZE, + ) + }; + + let mut ctx = DataFusionExecutionContext::new(); + let mut windows: HashMap>> = HashMap::new(); + + let events = (0..seconds) + .map(|t| { + let (r1, _) = stream + .select_event_to_batches( + t, + 0, // generator id + payload.query_number, + sync, + ) + .unwrap(); + r1.to_vec() + }) + .collect::>>>(); + + let schema = events[0][0][0].schema(); + + for (time, batches) in events.into_iter().enumerate() { + info!("Processing events in epoch: {}", time); + let now = Instant::now(); + let table = MemTable::try_new(schema.clone(), batches)?; + ctx.deregister_table(&*table_name)?; + ctx.register_table(&*table_name, Arc::new(table))?; + + // Equivalent to `SELECT COUNT(DISTINCT group_key) FROM table_name;` + let output = ctx + .table(&*table_name)? + .aggregate(vec![], vec![count_distinct(col(&group_key))])? + .collect() + .await?; + + let distinct_keys = output[0] + .column(0) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + + // Equivalent to `SELECT *, now() as p_time FROM table_name;` + let output = collect_partitioned(physical_plan(&ctx, &add_process_time_sql).await?).await?; + let schema_with_ptime = output[0][0].schema(); + + // Each partition has a unique key after `repartition` execution. + let partitions = repartition( + output, + HashDiff( + vec![expr_col(&group_key, &schema_with_ptime)?], + distinct_keys as usize, + ), + ) + .await?; + + // Update the window. + let mut tumblings = + add_partitions_to_tumbling_windows(partitions, &mut windows, window_size)?; + let to_remove = find_timeout_tumbling_windows(&windows, window_size)?; + to_remove.iter().for_each(|bidder| { + tumblings.push(windows.remove(bidder).unwrap()); + }); + + let tasks = coalesce_windows(tumblings, granule_size)? + .into_iter() + .filter(|window| !window.is_empty()) + .map(|window| { + let function_group = group_name.clone(); + let invoke_type = invocation_type.clone(); + + let query_code = group_name.split('-').next().unwrap(); + let timestamp = Utc::now().timestamp(); + let rand_id = uuid::Uuid::new_v4().as_u128(); + let qid = format!("{}-{}-{}", query_code, timestamp, rand_id); + + // Distribute the window data to a single function execution environment. + let function_name = ring.get(&qid).expect("hash ring failure.").to_string(); + info!("Tumbling window -> function name: {}", function_name); + + tokio::spawn(async move { + let window = repartition(window, RoundRobinBatch(1)).await?; + let window = coalesce_batches(window, granule_size * 2).await?; + let size = window[0].len(); + let mut uuid_builder = + UuidBuilder::new_with_ts_uuid(&function_group, timestamp, rand_id, size); + + // Call the next stage of the dataflow graph. + info!( + "[OK] Send {} events from a tumbling window to function: {}.", + size, function_name + ); + + for (eid, partition) in window.iter().enumerate() { + let payload = serde_json::to_vec(&to_payload( + partition, + &[], + uuid_builder.next_uuid(), + sync, + ))?; + info!( + "[OK] Event {} - {} function's payload bytes: {}", + eid, + function_name, + payload.len() + ); + lambda::invoke_function(&function_name, &invoke_type, Some(payload.into())) + .await?; + } + Ok(()) + }) + }) + .collect::>>>(); + futures::future::join_all(tasks).await; + + let elapsed = now.elapsed().as_millis() as u64; + if elapsed < 1000 { + std::thread::sleep(std::time::Duration::from_millis(1000 - elapsed)); + } + } + + Ok(()) +} diff --git a/flock-function/src/aws/window/hopping.rs b/flock-function/src/aws/window/hopping.rs new file mode 100644 index 00000000..d46cf76f --- /dev/null +++ b/flock-function/src/aws/window/hopping.rs @@ -0,0 +1,124 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::actor::*; +use crate::{consistent_hash_context, ConsistentHashContext, CONSISTENT_HASH_CONTEXT}; +use chrono::Utc; +use flock::aws::lambda; +use flock::prelude::*; +use log::{info, warn}; +use std::sync::Arc; + +/// Generate hopping windows workloads for the benchmark on cloud +/// function services. +/// +/// # Arguments +/// * `payload` - The payload of the function. +/// * `stream` - the source stream of events. +/// * `seconds` - the total number of seconds to generate workloads. +/// * `window_size` - the size of the window in seconds. +/// * `hop_size` - the size of the hop in seconds. +pub async fn launch_tasks( + payload: Payload, + stream: Arc, + seconds: usize, + window_size: usize, + hop_size: usize, +) -> Result<()> { + if seconds < window_size { + warn!( + "seconds: {} is less than window_size: {}", + seconds, window_size + ); + } + let sync = infer_invocation_type(&payload.metadata)?; + let invocation_type = if sync { + FLOCK_LAMBDA_SYNC_CALL.to_string() + } else { + FLOCK_LAMBDA_ASYNC_CALL.to_string() + }; + + let (ring, group_name) = consistent_hash_context!(); + let mut window: Box> = Box::new(vec![]); + + for time in (0..seconds).step_by(hop_size) { + if time + window_size > seconds { + break; + } + + // Move the hopping window forward. + let mut start_pos = 0; + if !window.is_empty() { + window.drain(..hop_size); + start_pos = window_size - hop_size; + } + + // Update the hopping window, and generate the next batch of data. + for t in time + start_pos..time + window_size { + window.push(stream.select_event_to_batches( + t, + 0, // generator id + payload.query_number, + sync, + )?); + } + + // Calculate the total data packets to be sent. + let size = window + .iter() + .map(|(a, b)| if a.len() > b.len() { a.len() } else { b.len() }) + .sum::(); + + let mut uuid_builder = UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); + + // Distribute the window data to a single function execution environment. + let function_name = ring + .get(&uuid_builder.qid) + .expect("hash ring failure.") + .to_string(); + + // Call the next stage of the dataflow graph. + info!( + "[OK] Send {} events from a window (epoch: {}-{}) to function: {}.", + size, + time, + time + window_size, + function_name + ); + + let mut eid = 0; + let empty = vec![]; + for (a, b) in window.iter() { + let num = if a.len() > b.len() { a.len() } else { b.len() }; + for i in 0..num { + let payload = serde_json::to_vec(&to_payload( + if i < a.len() { &a[i] } else { &empty }, + if i < b.len() { &b[i] } else { &empty }, + uuid_builder.next_uuid(), + sync, + ))?; + info!( + "[OK] Event {} - {} function's payload bytes: {}", + eid, + function_name, + payload.len() + ); + lambda::invoke_function(&function_name, &invocation_type, Some(payload.into())) + .await?; + eid += 1; + } + } + } + + Ok(()) +} diff --git a/flock-function/src/aws/window/mod.rs b/flock-function/src/aws/window/mod.rs new file mode 100644 index 00000000..efd79ce4 --- /dev/null +++ b/flock-function/src/aws/window/mod.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! The time window types for the stream queries. + +pub mod elementwise; +pub mod global; +pub mod hopping; +pub mod session; +pub mod tumbling; + +use datafusion::arrow::record_batch::RecordBatch; +use flock::error::Result; + +/// This function is used to coalesce smaller session windows or global windows +/// to bigger ones so that the number of events in each payload is greater than +/// the granule size, and close to the payload limit. +fn coalesce_windows( + windows: Vec>>, + granule_size: usize, +) -> Result>>> { + #[allow(unused_assignments)] + let mut curr_size = 0; + let mut total_size = 0; + let mut res = vec![]; + let mut tmp = vec![]; + for window in windows { + curr_size = window + .iter() + .map(|v| v.iter().map(|b| b.num_rows()).sum::()) + .sum::(); + total_size += curr_size; + if total_size <= granule_size * 2 || tmp.is_empty() { + tmp.push(window); + } else { + res.push(tmp.into_iter().flatten().collect::>>()); + tmp = vec![window]; + total_size = curr_size; + } + } + if !tmp.is_empty() { + res.push(tmp.into_iter().flatten().collect::>>()); + } + Ok(res) +} diff --git a/flock-function/src/aws/window/session.rs b/flock-function/src/aws/window/session.rs new file mode 100644 index 00000000..bcbd1d11 --- /dev/null +++ b/flock-function/src/aws/window/session.rs @@ -0,0 +1,314 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::coalesce_windows; +use crate::actor::*; +use crate::{consistent_hash_context, ConsistentHashContext, CONSISTENT_HASH_CONTEXT}; +use chrono::{DateTime, NaiveDateTime, Utc}; +use datafusion::arrow::array::{Int32Array, TimestampMillisecondArray, UInt64Array}; +use datafusion::arrow::record_batch::RecordBatch; +use datafusion::datasource::MemTable; +use datafusion::execution::context::ExecutionContext as DataFusionExecutionContext; +use datafusion::logical_plan::{col, count_distinct}; +use datafusion::physical_plan::expressions::col as expr_col; +use datafusion::physical_plan::Partitioning::{HashDiff, RoundRobinBatch}; +use flock::aws::lambda; +use flock::datasource::nexmark::config::BASE_TIME; +use flock::prelude::*; +use log::{info, warn}; +use std::collections::HashMap; +use std::sync::Arc; + +/// Get back the input data from the registered table after the query is +/// executed to avoid copying the input data. +fn get_input_from_registered_table( + ctx: &mut DataFusionExecutionContext, + table_name: &str, +) -> Result>> { + Ok(unsafe { + Arc::get_mut_unchecked( + &mut ctx + .deregister_table(table_name) + .map_err(FlockError::DataFusion)? + .ok_or_else(|| { + FlockError::Internal(format!("Failed to deregister table `{}`", table_name)) + })?, + ) + .as_mut_any() + .downcast_mut::() + .unwrap() + .batches() + }) +} + +/// Add each unique partition to a distinct session window. +/// +/// # Arguments +/// * `partitions` - the partitions to be added. +/// * `windows` - the session windows. +/// * `timeouts` - the session timeouts. +/// +/// # Return +/// The updated session windows. +fn add_partitions_to_session_windows( + partitions: Vec>, + windows: &mut HashMap>>, + timeout: usize, +) -> Result>>> { + Ok(partitions + .into_iter() + .filter(|p| !p.is_empty()) + .map(|p| { + let mut session = vec![]; + let bidder = p[0] + .column(1 /* bidder field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + let current_timestamp = p[0] + .column(3 /* b_date_time field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + let curr_date_time = DateTime::::from_utc( + NaiveDateTime::from_timestamp(current_timestamp / 1000, 0), + Utc, + ); + + if !windows.contains_key(&(bidder as usize)) { + windows + .entry(bidder as usize) + .or_insert_with(Vec::new) + .push(p); + } else { + // get the last batch in the window. + let batch = windows + .get(&(bidder as usize)) + .unwrap() + .last() + .unwrap() + .last() + .unwrap(); + // get the last bid record's timestamp. + let last_timestamp = batch + .column(3 /* b_date_time field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(batch.num_rows() - 1); + let last_date_time = DateTime::::from_utc( + NaiveDateTime::from_timestamp(last_timestamp / 1000, 0), + Utc, + ); + + // If the current date time isn't 10 seconds later than the last date time, + // then we can add the current batch to the window . Otherwise, we have a new + // session window. + if curr_date_time.signed_duration_since(last_date_time) + > chrono::Duration::seconds(timeout as i64) + { + session = windows.remove(&(bidder as usize)).unwrap(); + } + windows + .entry(bidder as usize) + .or_insert_with(Vec::new) + .push(p); + } + session + }) + .collect::>>>()) +} + +/// Find new session windows after timeout. +/// +/// # Arguments +/// * `windows` - the session windows. +/// * `timeout` - the session timeout. +/// * `epoch_count` - the number of epochs have been processed. +/// +/// # Return +/// The keys of the new session windows. +fn find_timeout_session_windows( + windows: &HashMap>>, + timeout: usize, + epoch_count: usize, +) -> Result> { + let mut to_remove = vec![]; + + windows.iter().for_each(|(bidder, batches)| { + // Get the last batch + let batch = batches.last().unwrap().last().unwrap(); + + // Get the last bid's date time + let last_timestamp = batch + .column(3 /* b_date_time field */) + .as_any() + .downcast_ref::() + .unwrap() + .value(batch.num_rows() - 1); + + let last_date_time = + DateTime::::from_utc(NaiveDateTime::from_timestamp(last_timestamp / 1000, 0), Utc); + + let epoch_gap_time = DateTime::::from_utc( + NaiveDateTime::from_timestamp(BASE_TIME as i64 / 1000 + epoch_count as i64, 0), + Utc, + ); + + if epoch_gap_time.signed_duration_since(last_date_time) + > chrono::Duration::seconds(timeout as i64) + { + to_remove.push(bidder.to_owned()); + } + }); + + Ok(to_remove) +} + +/// Session windows group events that arrive at similar times, filtering out +/// periods of time where there is no data. A session window begins when the +/// first event occurs. If another event occurs within the specified timeout +/// from the last ingested event, then the window extends to include the new +/// event. Otherwise if no events occur within the timeout, then the window is +/// closed at the timeout. +pub async fn launch_tasks( + payload: Payload, + stream: Arc, + seconds: usize, + timeout: usize, +) -> Result<()> { + if seconds < timeout { + warn!("seconds: {} is less than timeout: {}", seconds, timeout); + } + let sync = infer_invocation_type(&payload.metadata)?; + let (group_key, table_name) = infer_session_keys(&payload.metadata)?; + let (ring, group_name) = consistent_hash_context!(); + + let (invocation_type, granule_size) = if sync { + (FLOCK_LAMBDA_SYNC_CALL.to_string(), *FLOCK_SYNC_GRANULE_SIZE) + } else { + ( + FLOCK_LAMBDA_ASYNC_CALL.to_string(), + *FLOCK_ASYNC_GRANULE_SIZE, + ) + }; + + let mut ctx = DataFusionExecutionContext::new(); + let mut windows: HashMap>> = HashMap::new(); + + let events = (0..seconds) + .map(|t| { + let (r1, _) = stream + .select_event_to_batches( + t, + 0, // generator id + payload.query_number, + sync, + ) + .unwrap(); + r1.to_vec() + }) + .collect::>>>(); + + let schema = events[0][0][0].schema(); + + for (time, batches) in events.into_iter().enumerate() { + info!("Processing events in epoch: {}", time); + let table = MemTable::try_new(schema.clone(), batches)?; + ctx.deregister_table(&*table_name)?; + ctx.register_table(&*table_name, Arc::new(table))?; + + // Equivalent to `SELECT COUNT(DISTINCT group_key) FROM table_name;` + let output = ctx + .table(&*table_name)? + .aggregate(vec![], vec![count_distinct(col(&group_key))])? + .collect() + .await?; + + let distinct_keys = output[0] + .column(0) + .as_any() + .downcast_ref::() + .unwrap() + .value(0); + + // Each partition has a unique key after `repartition` execution. + let partitions = repartition( + get_input_from_registered_table(&mut ctx, &table_name)?, + HashDiff(vec![expr_col(&group_key, &schema)?], distinct_keys as usize), + ) + .await?; + + // Update the window. + let mut sessions = add_partitions_to_session_windows(partitions, &mut windows, timeout)?; + let to_remove = find_timeout_session_windows(&windows, timeout, time)?; + to_remove.iter().for_each(|bidder| { + sessions.push(windows.remove(bidder).unwrap()); + }); + + let tasks = coalesce_windows(sessions, granule_size)? + .into_iter() + .filter(|session| !session.is_empty()) + .map(|session| { + let function_group = group_name.clone(); + let invoke_type = invocation_type.clone(); + + let query_code = group_name.split('-').next().unwrap(); + let timestamp = Utc::now().timestamp(); + let rand_id = uuid::Uuid::new_v4().as_u128(); + let qid = format!("{}-{}-{}", query_code, timestamp, rand_id); + + // Distribute the window data to a single function execution environment. + let function_name = ring.get(&qid).expect("hash ring failure.").to_string(); + info!("Session window -> function name: {}", function_name); + + tokio::spawn(async move { + let output = repartition(session, RoundRobinBatch(1)).await?; + let window = coalesce_batches(output, granule_size * 2).await?; + let size = window[0].len(); + let mut uuid_builder = + UuidBuilder::new_with_ts_uuid(&function_group, timestamp, rand_id, size); + + // Call the next stage of the dataflow graph. + info!( + "[OK] Send {} events from a session window to function: {}.", + size, function_name + ); + + for (eid, partition) in window.iter().enumerate() { + let payload = serde_json::to_vec(&to_payload( + partition, + &[], + uuid_builder.next_uuid(), + sync, + ))?; + info!( + "[OK] Event {} - {} function's payload bytes: {}", + eid, + function_name, + payload.len() + ); + lambda::invoke_function(&function_name, &invoke_type, Some(payload.into())) + .await?; + } + Ok(()) + }) + }) + .collect::>>>(); + futures::future::join_all(tasks).await; + } + + Ok(()) +} diff --git a/flock-function/src/aws/window/tumbling.rs b/flock-function/src/aws/window/tumbling.rs new file mode 100644 index 00000000..904ded71 --- /dev/null +++ b/flock-function/src/aws/window/tumbling.rs @@ -0,0 +1,116 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::actor::*; +use crate::{consistent_hash_context, ConsistentHashContext, CONSISTENT_HASH_CONTEXT}; +use chrono::Utc; +use flock::aws::lambda; +use flock::prelude::*; +use log::{info, warn}; +use std::sync::Arc; + +/// Generate tumble windows workloads for the benchmark on cloud +/// function services. +/// +/// # Arguments +/// * `payload` - The payload of the function. +/// * `stream` - the source stream of events. +/// * `seconds` - the total number of seconds to generate workloads. +/// * `window_size` - the size of the window in seconds. +pub async fn launch_tasks( + payload: Payload, + stream: Arc, + seconds: usize, + window_size: usize, +) -> Result<()> { + if seconds < window_size { + warn!( + "seconds: {} is less than window_size: {}", + seconds, window_size + ); + } + let sync = infer_invocation_type(&payload.metadata)?; + let invocation_type = if sync { + FLOCK_LAMBDA_SYNC_CALL.to_string() + } else { + FLOCK_LAMBDA_ASYNC_CALL.to_string() + }; + + let (ring, group_name) = consistent_hash_context!(); + + let mut window: Box> = Box::new(vec![]); + + for time in 0..seconds / window_size { + let start = time * window_size; + let end = start + window_size; + + // Update the tumbling window, and generate the next batch of data. + window.drain(..); + for t in start..end { + window.push(stream.select_event_to_batches( + t, + 0, // generator id + payload.query_number, + sync, + )?); + } + + // Calculate the total data packets to be sent. + let size = window + .iter() + .map(|(a, b)| if a.len() > b.len() { a.len() } else { b.len() }) + .sum::(); + + let mut uuid_builder = UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); + + // Distribute the window data to a single function execution environment. + let function_name = ring + .get(&uuid_builder.qid) + .expect("hash ring failure.") + .to_string(); + + // Call the next stage of the dataflow graph. + info!( + "[OK] Send {} events from a window (epoch: {}-{}) to function: {}.", + size, + time, + time + window_size, + function_name + ); + + let mut eid = 0; + let empty = vec![]; + for (a, b) in window.iter() { + let num = if a.len() > b.len() { a.len() } else { b.len() }; + for i in 0..num { + let payload = serde_json::to_vec(&to_payload( + if i < a.len() { &a[i] } else { &empty }, + if i < b.len() { &b[i] } else { &empty }, + uuid_builder.next_uuid(), + sync, + ))?; + info!( + "[OK] Event {} - {} function payload bytes: {}", + eid, + function_name, + payload.len() + ); + lambda::invoke_function(&function_name, &invocation_type, Some(payload.into())) + .await?; + eid += 1; + } + } + } + + Ok(()) +} diff --git a/flock-function/src/aws/ysb/source.rs b/flock-function/src/aws/ysb/source.rs index f960010c..26fcc1e8 100644 --- a/flock-function/src/aws/ysb/source.rs +++ b/flock-function/src/aws/ysb/source.rs @@ -55,7 +55,7 @@ pub async fn handler(ctx: &ExecutionContext, payload: Payload) -> Result info!("[OK] Generate YSB events."); if let Window::Tumbling(Schedule::Seconds(window_size)) = source.window { - tumbling_window_tasks(payload, events, sec, window_size).await?; + tumbling::launch_tasks(payload, events, sec, window_size).await?; } else { unreachable!(); } diff --git a/flock/Cargo.toml b/flock/Cargo.toml index 6c2f5edc..275229c3 100644 --- a/flock/Cargo.toml +++ b/flock/Cargo.toml @@ -26,7 +26,7 @@ fixedbitset = { version = "0.4.0", optional = true } futures = "0.3.12" glob = { version = "0.3", optional = true } hashbrown = "0.12" -hashring = "0.2.0" +hashring = { git = "https://github.com/flock-lab/hashring-rs", branch = "flock" } humantime = "2.1.0" indoc = "1.0.3" itertools = "0.10.0" From aa415d03e0bcde29388e26115e11c011d8852319 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Sat, 5 Feb 2022 18:21:18 -0500 Subject: [PATCH 06/22] fix: large plan in s3 as a fallback (#458) --- flock/src/datasource/nexmark/nexmark.rs | 4 ++-- flock/src/runtime/plan.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/flock/src/datasource/nexmark/nexmark.rs b/flock/src/datasource/nexmark/nexmark.rs index 0957ef2e..bcc08612 100644 --- a/flock/src/datasource/nexmark/nexmark.rs +++ b/flock/src/datasource/nexmark/nexmark.rs @@ -180,7 +180,7 @@ impl DataStream for NEXMarkStream { }; let (r1, r2) = match query_number.expect("Query number is not set.") { 0 | 1 | 2 | 5 | 7 | 10..=13 => ( - event_bytes_to_batch(&event.bids, NEXMARK_BID.clone(), granule_size * 2), + event_bytes_to_batch(&event.bids, NEXMARK_BID.clone(), (granule_size as f32 * 2.4) as usize), vec![], ), 3 | 8 => ( @@ -189,7 +189,7 @@ impl DataStream for NEXMarkStream { ), 4 | 6 | 9 => ( event_bytes_to_batch(&event.auctions, NEXMARK_AUCTION.clone(), granule_size / 8), - event_bytes_to_batch(&event.bids, NEXMARK_BID.clone(), granule_size * 2), + event_bytes_to_batch(&event.bids, NEXMARK_BID.clone(), (granule_size as f32 * 2.4) as usize), ), _ => unimplemented!(), }; diff --git a/flock/src/runtime/plan.rs b/flock/src/runtime/plan.rs index 2793b484..ff2d3608 100644 --- a/flock/src/runtime/plan.rs +++ b/flock/src/runtime/plan.rs @@ -19,6 +19,7 @@ use crate::aws::s3; use crate::error::Result; use datafusion::execution::context::ExecutionContext; use datafusion::physical_plan::displayable; +use datafusion::physical_plan::empty::EmptyExec; use datafusion::physical_plan::hash_aggregate::HashAggregateExec; use datafusion::physical_plan::hash_join::HashJoinExec; use datafusion::physical_plan::sort::SortExec; @@ -73,7 +74,10 @@ impl CloudExecutionPlan { /// Returns the execution plan. pub async fn get_execution_plans(&mut self) -> Result>> { - if self.execution_plans.is_empty() { + if self.execution_plans.is_empty() + || (self.execution_plans.len() == 1 + && self.execution_plans[0].as_any().is::()) + { if self.object_storage.is_some() { info!("Loading plan from S3 {:?}", self.object_storage); let (bucket, key) = self.object_storage.as_ref().unwrap(); From 647c3836304c793bfdd70cc23fe8e65a90b44d11 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Sun, 6 Feb 2022 09:33:36 -0500 Subject: [PATCH 07/22] fix(bench): add nexmark q9 plan in s3 (#459) --- benchmarks/src/nexmark/main.rs | 4 +++- flock/src/configs/flock.toml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/benchmarks/src/nexmark/main.rs b/benchmarks/src/nexmark/main.rs index 7997728d..a230f6e7 100644 --- a/benchmarks/src/nexmark/main.rs +++ b/benchmarks/src/nexmark/main.rs @@ -52,6 +52,7 @@ lazy_static! { pub static ref NEXMARK_SOURCE_LOG_GROUP: String = "/aws/lambda/flock_datasource".to_string(); pub static ref NEXMARK_Q4_S3_KEY: String = FLOCK_CONF["nexmark"]["q4_s3_key"].to_string(); pub static ref NEXMARK_Q6_S3_KEY: String = FLOCK_CONF["nexmark"]["q6_s3_key"].to_string(); + pub static ref NEXMARK_Q9_S3_KEY: String = FLOCK_CONF["nexmark"]["q9_s3_key"].to_string(); pub static ref NEXMARK_Q13_S3_SIDE_INPUT_KEY: String = FLOCK_CONF["nexmark"]["q13_s3_side_input_key"].to_string(); } @@ -153,10 +154,11 @@ pub async fn plan_placement( physcial_plan: Arc, ) -> Result<(Arc, Option<(String, String)>)> { match query_number { - 4 | 6 => { + 4 | 6 | 9 => { let (s3_bucket, s3_key) = match query_number { 4 => (FLOCK_S3_BUCKET.clone(), NEXMARK_Q4_S3_KEY.clone()), 6 => (FLOCK_S3_BUCKET.clone(), NEXMARK_Q6_S3_KEY.clone()), + 9 => (FLOCK_S3_BUCKET.clone(), NEXMARK_Q9_S3_KEY.clone()), _ => unreachable!(), }; s3::put_object_if_missing(&s3_bucket, &s3_key, serde_json::to_vec(&physcial_plan)?) diff --git a/flock/src/configs/flock.toml b/flock/src/configs/flock.toml index 3ef9ce76..154ee460 100644 --- a/flock/src/configs/flock.toml +++ b/flock/src/configs/flock.toml @@ -99,6 +99,7 @@ permissions = "0777" q13_s3_side_input_key = "nexmark_q13_side_input" q4_s3_key = "nexmark_q4_plan" q6_s3_key = "nexmark_q6_plan" +q9_s3_key = "nexmark_q9_plan" s3_key = "nexmark" # YSB configuration From a60587dd012b5e6b12630214908e267a18b796f0 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Sun, 13 Feb 2022 12:27:42 -0500 Subject: [PATCH 08/22] fix: nexmark queries (#461) * fix: nexmark queries * fix: revert --- benchmarks/src/nexmark/main.rs | 2 +- benchmarks/src/s3/main.rs | 5 ++--- flock-function/src/aws/window/session.rs | 7 +++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/benchmarks/src/nexmark/main.rs b/benchmarks/src/nexmark/main.rs index a230f6e7..3439bfd0 100644 --- a/benchmarks/src/nexmark/main.rs +++ b/benchmarks/src/nexmark/main.rs @@ -218,7 +218,7 @@ pub async fn create_nexmark_functions( "Creating lambda function: {}", rainbow_string(FLOCK_DATA_SOURCE_FUNC_NAME.clone()) ); - lambda::create_function(&nexmark_source_ctx, 2048 /* MB */, &opt.architecture).await?; + lambda::create_function(&nexmark_source_ctx, 4096 /* MB */, &opt.architecture).await?; // Create the function for the nexmark worker. match next_func_name.clone() { diff --git a/benchmarks/src/s3/main.rs b/benchmarks/src/s3/main.rs index b3441212..8cc9c3bf 100644 --- a/benchmarks/src/s3/main.rs +++ b/benchmarks/src/s3/main.rs @@ -37,9 +37,8 @@ pub fn set_nexmark_config(opt: &mut NexmarkBenchmarkOpt) -> Result<()> { opt.async_type = false; opt.generators = 1; match opt.query_number { - 0 | 1 | 2 | 3 | 4 | 6 | 9 | 13 => opt.seconds = 1, // ElementWise - 5 => opt.seconds = 10, // Hopping - 7..=8 => opt.seconds = 10, // Tumbling + 0..=4 | 6 | 9 | 13 => opt.seconds = 1, // ElementWise + 5 | 7..=8 | 11..=12 => opt.seconds = 10, _ => unreachable!(), }; Ok(()) diff --git a/flock-function/src/aws/window/session.rs b/flock-function/src/aws/window/session.rs index bcbd1d11..570e1df9 100644 --- a/flock-function/src/aws/window/session.rs +++ b/flock-function/src/aws/window/session.rs @@ -28,6 +28,7 @@ use flock::prelude::*; use log::{info, warn}; use std::collections::HashMap; use std::sync::Arc; +use std::time::Instant; /// Get back the input data from the registered table after the query is /// executed to avoid copying the input data. @@ -226,6 +227,7 @@ pub async fn launch_tasks( for (time, batches) in events.into_iter().enumerate() { info!("Processing events in epoch: {}", time); + let now = Instant::now(); let table = MemTable::try_new(schema.clone(), batches)?; ctx.deregister_table(&*table_name)?; ctx.register_table(&*table_name, Arc::new(table))?; @@ -308,6 +310,11 @@ pub async fn launch_tasks( }) .collect::>>>(); futures::future::join_all(tasks).await; + + let elapsed = now.elapsed().as_millis() as u64; + if elapsed < 1000 { + std::thread::sleep(std::time::Duration::from_millis(1000 - elapsed)); + } } Ok(()) From 009f7b4dab2656d522bac0dddfbac52b27cedc53 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Sun, 13 Feb 2022 19:27:50 -0500 Subject: [PATCH 09/22] fix(payload): keep schema in payload if data is empty (#462) * fix(payload): keep schema in payload if data is mepty * feat: improve consistent hashing * typo * fix: calculate the total billed duration --- flock-cli/src/nexmark.rs | 2 +- flock-function/src/aws/actor.rs | 19 ++++++-- flock/src/datasource/nexmark/nexmark.rs | 12 ++++- flock/src/runtime/arena/mod.rs | 2 + flock/src/runtime/context.rs | 8 ++++ scripts/cloudwatch_log.sh | 59 +++++++++++++++++++++++++ 6 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 scripts/cloudwatch_log.sh diff --git a/flock-cli/src/nexmark.rs b/flock-cli/src/nexmark.rs index 1edde162..7364a4bd 100644 --- a/flock-cli/src/nexmark.rs +++ b/flock-cli/src/nexmark.rs @@ -134,7 +134,7 @@ fn run_args() -> App<'static> { .long("partitions") .help("Sets the number of partitions for the Arrow Datafusion target") .takes_value(true) - .possible_values(&["1", "2", "4", "8", "16", "32"]) + .possible_values(&["1", "2", "4", "8", "16", "24", "32"]) .default_value("8"), ) } diff --git a/flock-function/src/aws/actor.rs b/flock-function/src/aws/actor.rs index 2501dbe0..df86b0cc 100644 --- a/flock-function/src/aws/actor.rs +++ b/flock-function/src/aws/actor.rs @@ -276,6 +276,7 @@ async fn invoke_next_functions( } else { FLOCK_LAMBDA_ASYNC_CALL.to_string() }; + let schema = schema_to_bytes(ctx.schema(0).await?); match &ctx.next { CloudFunction::Sink(sink_type) => { @@ -306,10 +307,12 @@ async fn invoke_next_functions( let meta = metadata.clone(); let invoke_type = invocation_type.clone(); let uuid = uuid_builder.next_uuid(); + let schema_bytes = schema.clone(); tokio::spawn(async move { let mut payload = to_payload(&data[i], &[], uuid, sync); payload.query_number = query_number; payload.metadata = meta; + payload.schema = schema_bytes; let bytes = serde_json::to_vec(&payload)?; info!( @@ -340,6 +343,7 @@ async fn invoke_next_functions( uuid, sync, ); + payload.schema = schema; payload.query_number = query_number; payload.metadata = metadata; let bytes = serde_json::to_vec(&payload)?; @@ -362,6 +366,7 @@ async fn invoke_next_functions( uuid, sync, ); + payload.schema = schema; payload.query_number = query_number; payload.metadata = metadata; let bytes = serde_json::to_vec(&payload)?; @@ -420,6 +425,9 @@ async fn invoke_next_functions( } else { let output = Arc::new(output); let mut rng = StdRng::seed_from_u64(0xDEAD); // Predictable RNG clutch + let mut arr = [0u8; 64]; + rng.fill(&mut arr); + let func_idx = ring.get_index(&arr).expect("hash ring failure."); let tasks = (0..output.len()) .map(|i| { let my_output = output.clone(); @@ -427,7 +435,7 @@ async fn invoke_next_functions( let state_backend = ctx.state_backend.clone(); let current_function = ctx.name.clone(); let invoke_type = invocation_type.clone(); - + let schema_bytes = schema.clone(); let mut my_uuid = uuid.clone(); if let Some(new_seq_num) = shuffle_id { // This is REALLY important and tricky. @@ -452,14 +460,17 @@ async fn invoke_next_functions( // F0[2], F1[2], F2[2] .. Fn[2] ---> lambda function z // .. // F0[n], F1[n], F2[n] .. Fn[n] ---> lambda function v - let mut arr = [0u8; 64]; - rng.fill(&mut arr); - let next_function = ring.get(&arr).expect("hash ring failure.").to_string(); + + let next_function = ring + .get_by_index((func_idx + i) % ring.len()) + .expect("hash ring failure.") + .to_string(); tokio::spawn(async move { let mut payload = to_payload(&my_output[i], &[], my_uuid, sync); payload.query_number = query_number; payload.metadata = my_metadata; + payload.schema = schema_bytes; // set shuffle id to each data partition since they will be aggregated // at different functions. payload.shuffle_id = Some(i + 1); // Starts from 1. diff --git a/flock/src/datasource/nexmark/nexmark.rs b/flock/src/datasource/nexmark/nexmark.rs index bcc08612..8ae1f241 100644 --- a/flock/src/datasource/nexmark/nexmark.rs +++ b/flock/src/datasource/nexmark/nexmark.rs @@ -180,7 +180,11 @@ impl DataStream for NEXMarkStream { }; let (r1, r2) = match query_number.expect("Query number is not set.") { 0 | 1 | 2 | 5 | 7 | 10..=13 => ( - event_bytes_to_batch(&event.bids, NEXMARK_BID.clone(), (granule_size as f32 * 2.4) as usize), + event_bytes_to_batch( + &event.bids, + NEXMARK_BID.clone(), + (granule_size as f32 * 2.4) as usize, + ), vec![], ), 3 | 8 => ( @@ -189,7 +193,11 @@ impl DataStream for NEXMarkStream { ), 4 | 6 | 9 => ( event_bytes_to_batch(&event.auctions, NEXMARK_AUCTION.clone(), granule_size / 8), - event_bytes_to_batch(&event.bids, NEXMARK_BID.clone(), (granule_size as f32 * 2.4) as usize), + event_bytes_to_batch( + &event.bids, + NEXMARK_BID.clone(), + (granule_size as f32 * 2.4) as usize, + ), ), _ => unimplemented!(), }; diff --git a/flock/src/runtime/arena/mod.rs b/flock/src/runtime/arena/mod.rs index fdc0ef78..ddd00ec8 100644 --- a/flock/src/runtime/arena/mod.rs +++ b/flock/src/runtime/arena/mod.rs @@ -140,6 +140,7 @@ impl Arena { window .r1_flight_data .into_par_iter() + .filter(|d| !d.is_empty()) .map(|d| to_batches(unmarshal(d, encoding.clone()), schema1.clone())) .collect() })); @@ -151,6 +152,7 @@ impl Arena { window .r2_flight_data .into_par_iter() + .filter(|d| !d.is_empty()) .map(|d| to_batches(unmarshal(d, encoding.clone()), schema2.clone())) .collect() })); diff --git a/flock/src/runtime/context.rs b/flock/src/runtime/context.rs index d42ccd8d..c6d751a2 100644 --- a/flock/src/runtime/context.rs +++ b/flock/src/runtime/context.rs @@ -215,6 +215,14 @@ impl ExecutionContext { .collect()) } + /// The output schema of the current execution context. + /// + /// # Arguments + /// * `index` - The index of the subplan. + pub async fn schema(&mut self, index: usize) -> Result { + Ok(self.plan.get_execution_plans().await?[index].schema()) + } + /// Clean the data source in the given context. pub async fn clean_data_sources(&mut self) -> Result<()> { // Breadth-first search diff --git a/scripts/cloudwatch_log.sh b/scripts/cloudwatch_log.sh new file mode 100644 index 00000000..47aef76e --- /dev/null +++ b/scripts/cloudwatch_log.sh @@ -0,0 +1,59 @@ +# Copyright (c) 2020-present, UMD Database Group. +# +# This program is free software: you can use, redistribute, and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# or later ("AGPL"), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +#!/usr/bin/env sh + +set -e + +# declare -a log_group_names=( +# "/aws/lambda/q4-02-00" "/aws/lambda/q4-02-01" "/aws/lambda/q4-02-02" +# "/aws/lambda/q4-02-03" "/aws/lambda/q4-02-04" "/aws/lambda/q4-02-05" +# "/aws/lambda/q4-02-06" "/aws/lambda/q4-02-07" "/aws/lambda/q4-02-08" +# "/aws/lambda/q4-02-09" "/aws/lambda/q4-02-10" "/aws/lambda/q4-02-11" +# "/aws/lambda/q4-02-12" "/aws/lambda/q4-02-13" "/aws/lambda/q4-02-14" +# "/aws/lambda/q4-02-15") + +# log_group_names is a list from `aws/lambda/q4-03-00` to `aws/lambda/q4-03-15` +declare -a log_group_names=( + "/aws/lambda/q4-03-00" "/aws/lambda/q4-03-01" "/aws/lambda/q4-03-02" + "/aws/lambda/q4-03-03" "/aws/lambda/q4-03-04" "/aws/lambda/q4-03-05" + "/aws/lambda/q4-03-06" "/aws/lambda/q4-03-07" "/aws/lambda/q4-03-08" + "/aws/lambda/q4-03-09" "/aws/lambda/q4-03-10" "/aws/lambda/q4-03-11" + "/aws/lambda/q4-03-12" "/aws/lambda/q4-03-13" "/aws/lambda/q4-03-14" + "/aws/lambda/q4-03-15") + +for log_group_name in "${log_group_names[@]}"; do + log_stream_names=$(aws logs describe-log-streams \ + --log-group-name "$log_group_name" \ + --order-by LastEventTime \ + --descending \ + --max-items 1 \ + --query 'logStreams[*].logStreamName' \ + --output text) + + # Remove `None` from the list of log stream names + log_stream_names=$(echo "$log_stream_names" | grep -v None) + + for log_stream_name in $log_stream_names; do + aws logs get-log-events \ + --log-group-name "$log_group_name" \ + --log-stream-name "$log_stream_name" \ + --output text + done > cloudwatch.log + + # grep -r "Billed Duration" cloudwatch.log | awk '{print $11}' + printf '%s ' "$log_group_name" + grep -r "Billed Duration" cloudwatch.log | awk '{print $11}' | awk '{s+=$1} END {printf "billed duration: %.0f\n", s}' +done > billed_duration.log + +cat billed_duration.log | awk '{print $4}' | awk '{s+=$1} END {printf "total billed duration: %.0f\n", s}' From b07c125aa20d06640473d2487beeb4810fec8c45 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Tue, 15 Feb 2022 01:42:48 -0500 Subject: [PATCH 10/22] feat(ysb): dist ysb benchmark (#464) * feat(ysb): dist ysb benchmark * fix: ysb dist data flow --- benchmarks/src/ysb/centralized.rs | 180 +++++++++++++++++++++ benchmarks/src/ysb/distributed.rs | 184 ++++++++++++++++++++++ benchmarks/src/ysb/main.rs | 172 ++++---------------- flock-cli/src/ysb.rs | 46 +++++- flock-function/src/aws/nexmark/source.rs | 2 +- flock-function/src/aws/window/mod.rs | 11 +- flock-function/src/aws/window/tumbling.rs | 180 ++++++++++++++------- flock-function/src/aws/ysb/source.rs | 4 +- flock/src/datasource/ysb/mod.rs | 13 +- 9 files changed, 588 insertions(+), 204 deletions(-) create mode 100644 benchmarks/src/ysb/centralized.rs create mode 100644 benchmarks/src/ysb/distributed.rs diff --git a/benchmarks/src/ysb/centralized.rs b/benchmarks/src/ysb/centralized.rs new file mode 100644 index 00000000..83b80d4a --- /dev/null +++ b/benchmarks/src/ysb/centralized.rs @@ -0,0 +1,180 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#[path = "../rainbow.rs"] +mod rainbow; + +use super::create_ysb_source; +use super::ysb_query; +use crate::YSBBenchmarkOpt; +use datafusion::arrow::util::pretty::pretty_format_batches; +use datafusion::physical_plan::ExecutionPlan; +use flock::aws::{cloudwatch, lambda}; +use flock::prelude::*; +use humantime::parse_duration; +use lazy_static::lazy_static; +use log::info; +use rainbow::{rainbow_println, rainbow_string}; +use rusoto_lambda::InvocationResponse; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::task::JoinHandle; +use ysb::register_ysb_tables; + +lazy_static! { + pub static ref YSB_SOURCE_LOG_GROUP: String = "/aws/lambda/flock_datasource".to_string(); +} + +pub async fn ysb_benchmark(opt: &mut YSBBenchmarkOpt) -> Result<()> { + rainbow_println("================================================================"); + rainbow_println(" Running the benchmark "); + rainbow_println("================================================================"); + info!("Running the YSB benchmark with the following options:\n"); + rainbow_println(format!("{:#?}\n", opt)); + + let ysb_conf = create_ysb_source(opt); + let ctx = register_ysb_tables().await?; + let plan = physical_plan(&ctx, &ysb_query()).await?; + let root_actor = create_ysb_functions(opt, plan).await?; + + // The source generator function needs the metadata to determine the type of the + // workers such as single function or a group. We don't want to keep this info + // in the environment as part of the source function. Otherwise, we have to + // *delete* and **recreate** the source function every time we change the query. + let mut metadata = HashMap::new(); + metadata.insert("workers".to_string(), serde_json::to_string(&root_actor)?); + metadata.insert( + "invocation_type".to_string(), + if opt.async_type { + "async".to_string() + } else { + "sync".to_string() + }, + ); + + let tasks = (0..opt.generators) + .into_iter() + .map(|i| { + let s = ysb_conf.clone(); + let m = metadata.clone(); + tokio::spawn(async move { + info!( + "[OK] Invoking YSB source function: {} by generator {}\n", + rainbow_string(&*FLOCK_DATA_SOURCE_FUNC_NAME), + i + ); + let p = serde_json::to_vec(&Payload { + datasource: DataSource::YSBEvent(s), + metadata: Some(m), + ..Default::default() + })? + .into(); + lambda::invoke_function( + &FLOCK_DATA_SOURCE_FUNC_NAME, + &FLOCK_LAMBDA_ASYNC_CALL, + Some(p), + ) + .await + }) + }) + // this collect *is needed* so that the join below can switch between tasks. + .collect::>>>(); + + futures::future::join_all(tasks).await; + + info!("Waiting for the current invocations to be logged."); + tokio::time::sleep(parse_duration("5s").unwrap()).await; + cloudwatch::fetch(&YSB_SOURCE_LOG_GROUP, parse_duration("1min").unwrap()).await?; + + let sink_type = DataSinkType::new(&opt.data_sink_type)?; + if sink_type != DataSinkType::Blackhole { + let data_sink = + DataSink::read("ysb".to_string(), sink_type, DataSinkFormat::default()).await?; + info!( + "[OK] Received {} batches from the data sink.", + data_sink.record_batches.len() + ); + info!("[OK] Last data sink function: {}", data_sink.function_name); + let function_log_group = format!("/aws/lambda/{}", data_sink.function_name); + cloudwatch::fetch(&function_log_group, parse_duration("1min").unwrap()).await?; + println!("{}", pretty_format_batches(&data_sink.record_batches)?); + } + + Ok(()) +} + +/// Create lambda functions for a given YSB query. +/// The returned function is the worker group as a whole which will be executed +/// by the YSB data generator function. +async fn create_ysb_functions( + opt: &YSBBenchmarkOpt, + physcial_plan: Arc, +) -> Result { + let worker_func_name = "ysb-00".to_string(); + let next_func_name = + CloudFunction::Group((worker_func_name.clone(), *FLOCK_FUNCTION_CONCURRENCY)); + + let ysb_source_ctx = ExecutionContext { + plan: CloudExecutionPlan::new(vec![FLOCK_EMPTY_PLAN.clone()], None), + name: FLOCK_DATA_SOURCE_FUNC_NAME.clone(), + next: next_func_name.clone(), + ..Default::default() + }; + + let ysb_worker_ctx = ExecutionContext { + plan: CloudExecutionPlan::new(vec![physcial_plan], None), + name: worker_func_name.clone(), + next: CloudFunction::Sink(DataSinkType::new(&opt.data_sink_type)?), + ..Default::default() + }; + + // Create the function for the ysb source generator. + info!( + "Creating lambda function: {}", + rainbow_string(FLOCK_DATA_SOURCE_FUNC_NAME.clone()) + ); + lambda::create_function(&ysb_source_ctx, 4096, &opt.architecture).await?; + + // Create the function for the ysb worker. + match next_func_name.clone() { + CloudFunction::Group((name, concurrency)) => { + info!( + "Creating lambda function group: {}", + rainbow_string(format!("{:?}", ysb_source_ctx.next)) + ); + + let tasks = (0..concurrency) + .into_iter() + .map(|i| { + let mut worker_ctx = ysb_worker_ctx.clone(); + let group_name = name.clone(); + let memory_size = opt.memory_size; + let architecture = opt.architecture.clone(); + tokio::spawn(async move { + worker_ctx.name = format!("{}-{:02}", group_name, i); + info!( + "Creating function member: {}", + rainbow_string(&worker_ctx.name) + ); + lambda::create_function(&worker_ctx, memory_size, &architecture).await?; + lambda::set_concurrency(&worker_ctx.name, 1).await + }) + }) + .collect::>>>(); + futures::future::join_all(tasks).await; + } + _ => unreachable!(), + } + + Ok(next_func_name) +} diff --git a/benchmarks/src/ysb/distributed.rs b/benchmarks/src/ysb/distributed.rs new file mode 100644 index 00000000..5c7b1f0b --- /dev/null +++ b/benchmarks/src/ysb/distributed.rs @@ -0,0 +1,184 @@ +// Copyright (c) 2020-present, UMD Database Group. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +extern crate daggy; + +#[path = "../rainbow.rs"] +mod rainbow; + +use super::create_ysb_source; +use super::ysb_query; +use crate::YSBBenchmarkOpt; + +use daggy::NodeIndex; +use datafusion::execution::context::ExecutionConfig; +use flock::aws::lambda; +use flock::distributed_plan::QueryDag; +use flock::prelude::*; +use humantime::parse_duration; +use lazy_static::lazy_static; +use log::info; +use rainbow::{rainbow_println, rainbow_string}; +use rusoto_lambda::InvocationResponse; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::task::JoinHandle; +use ysb::register_ysb_tables_with_config; + +lazy_static! { + pub static ref YSB_SOURCE_LOG_GROUP: String = "/aws/lambda/flock_datasource".to_string(); +} + +pub async fn ysb_benchmark(opt: &mut YSBBenchmarkOpt) -> Result<()> { + rainbow_println("================================================================"); + rainbow_println(" Running the benchmark "); + rainbow_println("================================================================"); + info!("Running the YSB benchmark with the following options:\n"); + rainbow_println(format!("{:#?}\n", opt)); + + let query_code = "ysb"; + let ysb_conf = create_ysb_source(opt); + + let config = ExecutionConfig::new().with_target_partitions(opt.target_partitions); + let ctx = register_ysb_tables_with_config(config).await?; + + let plan = physical_plan(&ctx, &ysb_query()).await?; + let sink_type = DataSinkType::new(&opt.data_sink_type)?; + + let state_backend: Arc = match opt.state_backend.as_str() { + "hashmap" => Arc::new(HashMapStateBackend::new()), + "s3" => Arc::new(S3StateBackend::new()), + "efs" => Arc::new(EfsStateBackend::new()), + _ => unreachable!(), + }; + + let mut launcher = + AwsLambdaLauncher::try_new(query_code, plan, sink_type, state_backend).await?; + launcher.create_cloud_contexts(*FLOCK_FUNCTION_CONCURRENCY)?; + + info!( + "Streaming: {}", + rainbow_string(format!("{:?}", ysb_conf.window)) + ); + info!("SQL query:\n\n{}", ysb_query()); + + let stages = launcher.dag.get_all_stages(); + for (i, stage) in stages.iter().enumerate() { + info!("{}", rainbow_string(format!("=== Query Stage {} ===", i))); + info!( + "Current function type: {}", + rainbow_string(format!("{:?}", stage.get_function_type())) + ); + info!( + "Next function name: {}", + rainbow_string(format!("{:?}", stage.context.as_ref().unwrap().next)) + ); + info!("Physical Plan:\n{}", stage.get_plan_str()); + } + + let dag = &mut launcher.dag; + create_ysb_functions(dag, opt, *FLOCK_FUNCTION_CONCURRENCY).await?; + + let mut metadata = HashMap::new(); + metadata.insert( + "invocation_type".to_string(), + if opt.async_type { + "async".to_string() + } else { + "sync".to_string() + }, + ); + + let tasks = (0..opt.generators) + .into_iter() + .map(|i| { + let s = ysb_conf.clone(); + let m = metadata.clone(); + let f = format!("ysb-{:02}", 0); + tokio::spawn(async move { + info!( + "[OK] Invoking YSB source function: {} by generator {}\n", + rainbow_string(&f), + i + ); + let p = serde_json::to_vec(&Payload { + datasource: DataSource::YSBEvent(s), + metadata: Some(m), + ..Default::default() + })? + .into(); + lambda::invoke_function(&f, &FLOCK_LAMBDA_ASYNC_CALL, Some(p)).await + }) + }) + // this collect *is needed* so that the join below can switch between tasks. + .collect::>>>(); + + futures::future::join_all(tasks).await; + + Ok(()) +} + +/// Create lambda functions for a given YSB query. +async fn create_ysb_functions( + dag: &mut QueryDag, + opt: &YSBBenchmarkOpt, + group_size: usize, +) -> Result<()> { + let count = dag.node_count(); + assert!(count < 100); + + let func_types = (0..count) + .map(|i| dag.get_node(NodeIndex::new(i)).unwrap().get_function_type()) + .collect::>(); + + for i in (0..count).rev() { + let node = dag.get_node_mut(NodeIndex::new(i)).unwrap(); + if func_types[i] == CloudFunctionType::Group { + let group_name = format!("ysb-{:02}", count - 1 - i); + info!( + "Creating lambda function group: {}", + rainbow_string(format!("({}, {})", group_name, group_size)) + ); + let tasks = (0..group_size) + .into_iter() + .map(|j| { + let mut ctx = node.context.clone().unwrap(); + let name = group_name.clone(); + let memory_size = opt.memory_size; + let architecture = opt.architecture.clone(); + tokio::spawn(async move { + ctx.name = format!("{}-{:02}", name, j); + lambda::create_function(&ctx, memory_size, &architecture).await?; + info!("Created function member: {}", rainbow_string(&ctx.name)); + lambda::set_concurrency(&ctx.name, 1).await + }) + }) + .collect::>>>(); + futures::future::join_all(tasks).await; + tokio::time::sleep(parse_duration("2s").unwrap()).await; + } else { + lambda::create_function( + node.context.as_ref().unwrap(), + opt.memory_size, + &opt.architecture, + ) + .await?; + info!( + "Created lambda function: {}", + rainbow_string(format!("ysb-{:02}", count - 1 - i)) + ); + } + } + + Ok(()) +} diff --git a/benchmarks/src/ysb/main.rs b/benchmarks/src/ysb/main.rs index 83aeb602..676868c1 100644 --- a/benchmarks/src/ysb/main.rs +++ b/benchmarks/src/ysb/main.rs @@ -11,21 +11,21 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +#[path = "../rainbow.rs"] +mod rainbow; + +#[path = "./centralized.rs"] +mod centralized; + +#[path = "./distributed.rs"] +mod distributed; + use datafusion::arrow::datatypes::SchemaRef; -use datafusion::arrow::util::pretty::pretty_format_batches; -use datafusion::physical_plan::ExecutionPlan; -use flock::aws::{cloudwatch, lambda}; use flock::prelude::*; -use humantime::parse_duration; use lazy_static::lazy_static; -use log::info; -use rusoto_lambda::InvocationResponse; -use std::collections::HashMap; use std::sync::Arc; use structopt::StructOpt; -use tokio::task::JoinHandle; use ysb::event::{AdEvent, Campaign}; -use ysb::register_ysb_tables; use ysb::YSBSource; lazy_static! { @@ -64,13 +64,26 @@ pub struct YSBBenchmarkOpt { /// The system architecture to use #[structopt(short = "a", long = "arch", default_value = "x86_64")] pub architecture: String, + + /// Distributed mode or not + #[structopt(short = "d", long = "distributed")] + pub distributed: bool, + + /// The state backend to use + #[structopt(short = "b", long = "state_backend", default_value = "hashmap")] + pub state_backend: String, + + /// The target partitions to use in Arrow DataFusion. + /// This is only used in distributed mode. + #[structopt(short = "p", long = "target_partitions", default_value = "8")] + pub target_partitions: usize, } #[tokio::main] #[allow(dead_code)] async fn main() -> Result<()> { env_logger::init(); - ysb_benchmark(YSBBenchmarkOpt::from_args()).await?; + ysb_benchmark(&mut YSBBenchmarkOpt::from_args()).await?; Ok(()) } @@ -79,139 +92,12 @@ fn create_ysb_source(opt: &YSBBenchmarkOpt) -> YSBSource { YSBSource::new(opt.seconds, opt.generators, opt.events_per_second, window) } -/// Create lambda functions for a given YSB query. -/// The returned function is the worker group as a whole which will be executed -/// by the YSB data generator function. -async fn create_ysb_functions( - opt: &YSBBenchmarkOpt, - physcial_plan: Arc, -) -> Result { - let worker_func_name = "ysb-00".to_string(); - let next_func_name = - CloudFunction::Group((worker_func_name.clone(), *FLOCK_FUNCTION_CONCURRENCY)); - - let ysb_source_ctx = ExecutionContext { - plan: CloudExecutionPlan::new(vec![FLOCK_EMPTY_PLAN.clone()], None), - name: FLOCK_DATA_SOURCE_FUNC_NAME.clone(), - next: next_func_name.clone(), - ..Default::default() - }; - - let ysb_worker_ctx = ExecutionContext { - plan: CloudExecutionPlan::new(vec![physcial_plan], None), - name: worker_func_name.clone(), - next: CloudFunction::Sink(DataSinkType::new(&opt.data_sink_type)?), - ..Default::default() - }; - - // Create the function for the ysb source generator. - info!( - "Creating lambda function: {}", - FLOCK_DATA_SOURCE_FUNC_NAME.clone() - ); - lambda::create_function(&ysb_source_ctx, 1024, &opt.architecture).await?; - - // Create the function for the ysb worker. - match next_func_name.clone() { - CloudFunction::Group((name, concurrency)) => { - info!("Creating lambda function group: {:?}", ysb_source_ctx.next); - - let tasks = (0..concurrency) - .into_iter() - .map(|i| { - let mut worker_ctx = ysb_worker_ctx.clone(); - let group_name = name.clone(); - let memory_size = opt.memory_size; - let architecture = opt.architecture.clone(); - tokio::spawn(async move { - worker_ctx.name = format!("{}-{:02}", group_name, i); - info!("Creating function member: {}", worker_ctx.name); - lambda::create_function(&worker_ctx, memory_size, &architecture).await?; - lambda::set_concurrency(&worker_ctx.name, 1).await - }) - }) - .collect::>>>(); - futures::future::join_all(tasks).await; - } - _ => unreachable!(), - } - - Ok(next_func_name) -} - -pub async fn ysb_benchmark(opt: YSBBenchmarkOpt) -> Result<()> { - info!( - "Running the YSB benchmark with the following options:\n{:#?}", - opt - ); - let ysb_conf = create_ysb_source(&opt); - let ctx = register_ysb_tables().await?; - let plan = physical_plan(&ctx, &ysb_query()).await?; - let root_actor = create_ysb_functions(&opt, plan).await?; - - // The source generator function needs the metadata to determine the type of the - // workers such as single function or a group. We don't want to keep this info - // in the environment as part of the source function. Otherwise, we have to - // *delete* and **recreate** the source function every time we change the query. - let mut metadata = HashMap::new(); - metadata.insert("workers".to_string(), serde_json::to_string(&root_actor)?); - metadata.insert( - "invocation_type".to_string(), - if opt.async_type { - "async".to_string() - } else { - "sync".to_string() - }, - ); - - let tasks = (0..opt.generators) - .into_iter() - .map(|i| { - let s = ysb_conf.clone(); - let m = metadata.clone(); - tokio::spawn(async move { - info!( - "[OK] Invoking YSB source function: {} by generator {}", - *FLOCK_DATA_SOURCE_FUNC_NAME, i - ); - let p = serde_json::to_vec(&Payload { - datasource: DataSource::YSBEvent(s), - metadata: Some(m), - ..Default::default() - })? - .into(); - lambda::invoke_function( - &FLOCK_DATA_SOURCE_FUNC_NAME, - &FLOCK_LAMBDA_ASYNC_CALL, - Some(p), - ) - .await - }) - }) - // this collect *is needed* so that the join below can switch between tasks. - .collect::>>>(); - - futures::future::join_all(tasks).await; - - info!("Waiting for the current invocations to be logged."); - tokio::time::sleep(parse_duration("5s").unwrap()).await; - cloudwatch::fetch(&YSB_SOURCE_LOG_GROUP, parse_duration("1min").unwrap()).await?; - - let sink_type = DataSinkType::new(&opt.data_sink_type)?; - if sink_type != DataSinkType::Blackhole { - let data_sink = - DataSink::read("ysb".to_string(), sink_type, DataSinkFormat::default()).await?; - info!( - "[OK] Received {} batches from the data sink.", - data_sink.record_batches.len() - ); - info!("[OK] Last data sink function: {}", data_sink.function_name); - let function_log_group = format!("/aws/lambda/{}", data_sink.function_name); - cloudwatch::fetch(&function_log_group, parse_duration("1min").unwrap()).await?; - println!("{}", pretty_format_batches(&data_sink.record_batches)?); +pub async fn ysb_benchmark(opt: &mut YSBBenchmarkOpt) -> Result<()> { + if opt.distributed { + distributed::ysb_benchmark(opt).await + } else { + centralized::ysb_benchmark(opt).await } - - Ok(()) } /// Returns YSB query string. @@ -222,7 +108,9 @@ fn ysb_query() -> String { #[cfg(test)] mod tests { use super::*; + use datafusion::arrow::util::pretty::pretty_format_batches; use flock::transmute::event_bytes_to_batch; + use ysb::register_ysb_tables; #[tokio::test] async fn ysb_sql_query() -> Result<()> { diff --git a/flock-cli/src/ysb.rs b/flock-cli/src/ysb.rs index d23e0292..c779dca7 100644 --- a/flock-cli/src/ysb.rs +++ b/flock-cli/src/ysb.rs @@ -102,6 +102,30 @@ fn run_args() -> App<'static> { .possible_values(&["x86_64", "arm64"]) .default_value("x86_64"), ) + .arg( + Arg::new("distributed") + .short('d') + .long("distributed") + .help("Runs the YSB benchmark with distributed workers"), + ) + .arg( + Arg::new("state backend") + .short('b') + .long("state-backend") + .help("Sets the state backend for the worker function") + .takes_value(true) + .possible_values(&["hashmap", "s3", "efs"]) + .default_value("hashmap"), + ) + .arg( + Arg::new("Arrow Datafusion target partitions") + .short('p') + .long("partitions") + .help("Sets the number of partitions for the Arrow Datafusion target") + .takes_value(true) + .possible_values(&["1", "2", "4", "8", "16", "24", "32"]) + .default_value("8"), + ) } pub fn run(matches: &ArgMatches) -> Result<()> { @@ -163,7 +187,27 @@ pub fn run(matches: &ArgMatches) -> Result<()> { .with_context(|| anyhow!("Invalid architecture"))?; } + if matches.is_present("distributed") { + opt.distributed = true; + } + + if matches.is_present("state backend") { + opt.state_backend = matches + .value_of("state backend") + .unwrap() + .parse::() + .with_context(|| anyhow!("Invalid state backend"))?; + } + + if matches.is_present("Arrow Datafusion target partitions") { + opt.target_partitions = matches + .value_of("Arrow Datafusion target partitions") + .unwrap() + .parse::() + .with_context(|| anyhow!("Invalid Arrow Datafusion target partitions"))?; + } + rainbow_println(include_str!("./flock")); - futures::executor::block_on(ysb_benchmark(opt)).map_err(|e| e.into()) + futures::executor::block_on(ysb_benchmark(&mut opt)).map_err(|e| e.into()) } diff --git a/flock-function/src/aws/nexmark/source.rs b/flock-function/src/aws/nexmark/source.rs index 7f123675..0f83c7d6 100644 --- a/flock-function/src/aws/nexmark/source.rs +++ b/flock-function/src/aws/nexmark/source.rs @@ -56,7 +56,7 @@ pub async fn handler(ctx: &mut ExecutionContext, payload: Payload) -> Result { - tumbling::launch_tasks(payload, events, sec, window_size).await?; + tumbling::launch_tasks(ctx, payload, events, sec, window_size).await?; } Window::Hopping((window_size, hop_size)) => { hopping::launch_tasks(payload, events, sec, window_size, hop_size).await?; diff --git a/flock-function/src/aws/window/mod.rs b/flock-function/src/aws/window/mod.rs index efd79ce4..c6ed79d8 100644 --- a/flock-function/src/aws/window/mod.rs +++ b/flock-function/src/aws/window/mod.rs @@ -20,7 +20,8 @@ pub mod session; pub mod tumbling; use datafusion::arrow::record_batch::RecordBatch; -use flock::error::Result; +use datafusion::physical_plan::empty::EmptyExec; +use flock::prelude::*; /// This function is used to coalesce smaller session windows or global windows /// to bigger ones so that the number of events in each payload is greater than @@ -53,3 +54,11 @@ fn coalesce_windows( } Ok(res) } + +/// Is distributed execution enabled? +fn is_distributed(ctx: &ExecutionContext) -> bool { + ctx.plan.execution_plans[0] + .as_any() + .downcast_ref::() + .is_none() +} diff --git a/flock-function/src/aws/window/tumbling.rs b/flock-function/src/aws/window/tumbling.rs index 904ded71..241917fe 100644 --- a/flock-function/src/aws/window/tumbling.rs +++ b/flock-function/src/aws/window/tumbling.rs @@ -11,10 +11,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use super::is_distributed; use crate::actor::*; use crate::{consistent_hash_context, ConsistentHashContext, CONSISTENT_HASH_CONTEXT}; use chrono::Utc; -use flock::aws::lambda; +use flock::aws::{lambda, s3}; use flock::prelude::*; use log::{info, warn}; use std::sync::Arc; @@ -28,8 +29,9 @@ use std::sync::Arc; /// * `seconds` - the total number of seconds to generate workloads. /// * `window_size` - the size of the window in seconds. pub async fn launch_tasks( + ctx: &mut ExecutionContext, payload: Payload, - stream: Arc, + stream: Arc, seconds: usize, window_size: usize, ) -> Result<()> { @@ -39,75 +41,145 @@ pub async fn launch_tasks( seconds, window_size ); } - let sync = infer_invocation_type(&payload.metadata)?; + let metadata = payload.metadata; + let (ring, group_name) = consistent_hash_context!(); + let sync = infer_invocation_type(&metadata)?; let invocation_type = if sync { FLOCK_LAMBDA_SYNC_CALL.to_string() } else { FLOCK_LAMBDA_ASYNC_CALL.to_string() }; - let (ring, group_name) = consistent_hash_context!(); - let mut window: Box> = Box::new(vec![]); for time in 0..seconds / window_size { let start = time * window_size; let end = start + window_size; - // Update the tumbling window, and generate the next batch of data. - window.drain(..); - for t in start..end { - window.push(stream.select_event_to_batches( - t, - 0, // generator id - payload.query_number, - sync, - )?); - } - - // Calculate the total data packets to be sent. - let size = window - .iter() - .map(|(a, b)| if a.len() > b.len() { a.len() } else { b.len() }) - .sum::(); + if is_distributed(ctx) { + // Distribute the workloads to the cloud function services. + let mut input1 = vec![]; + let mut input2 = vec![]; + for t in start..end { + let (r1, r2) = stream.select_event_to_batches(t, 0, None, sync)?; + if !r1.is_empty() { + input1.push(r1); + } + if !r2.is_empty() { + input2.push(r2); + } + } + let mut input = vec![]; + if !input1.is_empty() { + input.push(input1.into_iter().flatten().collect()); + } + if !input2.is_empty() { + input.push(input2.into_iter().flatten().collect()); + } - let mut uuid_builder = UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); + ctx.feed_data_sources(input).await?; + let output = Arc::new(ctx.execute_partitioned().await?); + let size = output[0].len(); + let mut uuid_builder = + UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); - // Distribute the window data to a single function execution environment. - let function_name = ring - .get(&uuid_builder.qid) - .expect("hash ring failure.") - .to_string(); + // Creates the S3 bucket for the current query if state backend is S3. + if ctx + .state_backend + .as_any() + .downcast_ref::() + .is_some() + { + s3::create_bucket(&uuid_builder.qid).await?; + } - // Call the next stage of the dataflow graph. - info!( - "[OK] Send {} events from a window (epoch: {}-{}) to function: {}.", - size, - time, - time + window_size, - function_name - ); + let tasks = (0..size) + .map(|i| { + let data = output.clone(); + let function_name = group_name.clone(); + let meta = metadata.clone(); + let invoke_type = invocation_type.clone(); + let uuid = uuid_builder.next_uuid(); + tokio::spawn(async move { + let mut payload = to_payload( + &data[0][i], + if data.len() == 1 { &[] } else { &data[1][i] }, + uuid, + sync, + ); + payload.metadata = meta; - let mut eid = 0; - let empty = vec![]; - for (a, b) in window.iter() { - let num = if a.len() > b.len() { a.len() } else { b.len() }; - for i in 0..num { - let payload = serde_json::to_vec(&to_payload( - if i < a.len() { &a[i] } else { &empty }, - if i < b.len() { &b[i] } else { &empty }, - uuid_builder.next_uuid(), + let bytes = serde_json::to_vec(&payload)?; + info!( + "[OK] {} function's payload bytes: {}", + function_name, + bytes.len() + ); + lambda::invoke_function(&function_name, &invoke_type, Some(bytes.into())) + .await + .map(|_| ()) + }) + }) + .collect::>>>(); + futures::future::join_all(tasks).await; + ctx.clean_data_sources().await?; + } else { + // Update the tumbling window, and generate the next batch of data. + window.drain(..); + for t in start..end { + window.push(stream.select_event_to_batches( + t, + 0, // generator id + payload.query_number, sync, - ))?; - info!( - "[OK] Event {} - {} function payload bytes: {}", - eid, - function_name, - payload.len() - ); - lambda::invoke_function(&function_name, &invocation_type, Some(payload.into())) - .await?; - eid += 1; + )?); + } + + // Calculate the total data packets to be sent. + let size = window + .iter() + .map(|(a, b)| if a.len() > b.len() { a.len() } else { b.len() }) + .sum::(); + + let mut uuid_builder = + UuidBuilder::new_with_ts(group_name, Utc::now().timestamp(), size); + + // Distribute the window data to a single function execution environment. + let function_name = ring + .get(&uuid_builder.qid) + .expect("hash ring failure.") + .to_string(); + + // Call the next stage of the dataflow graph. + info!( + "[OK] Send {} events from a window (epoch: {}-{}) to function: {}.", + size, + time, + time + window_size, + function_name + ); + + let mut eid = 0; + let empty = vec![]; + for (a, b) in window.iter() { + let num = if a.len() > b.len() { a.len() } else { b.len() }; + for i in 0..num { + let payload = serde_json::to_vec(&to_payload( + if i < a.len() { &a[i] } else { &empty }, + if i < b.len() { &b[i] } else { &empty }, + uuid_builder.next_uuid(), + sync, + ))?; + info!( + "[OK] Event {} - {} function payload bytes: {}", + eid, + function_name, + payload.len() + ); + lambda::invoke_function(&function_name, &invocation_type, Some(payload.into())) + .await?; + eid += 1; + } } } } diff --git a/flock-function/src/aws/ysb/source.rs b/flock-function/src/aws/ysb/source.rs index 26fcc1e8..bfb53633 100644 --- a/flock-function/src/aws/ysb/source.rs +++ b/flock-function/src/aws/ysb/source.rs @@ -30,7 +30,7 @@ use std::sync::Arc; /// /// # Returns /// A JSON object that contains the return value of the function invocation. -pub async fn handler(ctx: &ExecutionContext, payload: Payload) -> Result { +pub async fn handler(ctx: &mut ExecutionContext, payload: Payload) -> Result { // Copy data source from the payload. let mut source = match payload.datasource.clone() { DataSource::YSBEvent(source) => source, @@ -55,7 +55,7 @@ pub async fn handler(ctx: &ExecutionContext, payload: Payload) -> Result info!("[OK] Generate YSB events."); if let Window::Tumbling(Schedule::Seconds(window_size)) = source.window { - tumbling::launch_tasks(payload, events, sec, window_size).await?; + tumbling::launch_tasks(ctx, payload, events, sec, window_size).await?; } else { unreachable!(); } diff --git a/flock/src/datasource/ysb/mod.rs b/flock/src/datasource/ysb/mod.rs index 92f8c751..be940f5b 100644 --- a/flock/src/datasource/ysb/mod.rs +++ b/flock/src/datasource/ysb/mod.rs @@ -70,11 +70,12 @@ pub mod ysb; pub use self::ysb::{YSBEvent, YSBSource, YSBStream}; use self::event::{AdEvent, Campaign}; +use crate::configs::FLOCK_TARGET_PARTITIONS; use crate::error::Result; use datafusion::arrow::datatypes::Schema; use datafusion::arrow::record_batch::RecordBatch; use datafusion::datasource::MemTable; -use datafusion::execution::context::ExecutionContext; +use datafusion::execution::context::{ExecutionConfig, ExecutionContext}; use std::sync::Arc; /// The YSB tables. @@ -90,8 +91,8 @@ pub fn get_nexmark_schema(table: &str) -> Schema { } /// Register the YSB tables with empty data. -pub async fn register_ysb_tables() -> Result { - let mut ctx = ExecutionContext::new(); +pub async fn register_ysb_tables_with_config(config: ExecutionConfig) -> Result { + let mut ctx = ExecutionContext::with_config(config); let ad_event_schema = Arc::new(AdEvent::schema()); let ad_event_table = MemTable::try_new( ad_event_schema.clone(), @@ -108,3 +109,9 @@ pub async fn register_ysb_tables() -> Result { Ok(ctx) } + +/// Register the YSB tables with empty data. +pub async fn register_ysb_tables() -> Result { + let config = ExecutionConfig::new().with_target_partitions(*FLOCK_TARGET_PARTITIONS); + register_ysb_tables_with_config(config).await +} From 6c202938b3e955dac82530bb813ccda4f83f2709 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 01:50:26 -0500 Subject: [PATCH 11/22] build(deps): update sqlparser requirement from 0.13.0 to 0.14.0 (#460) Updates the requirements on [sqlparser](https://github.com/sqlparser-rs/sqlparser-rs) to permit the latest version. - [Release notes](https://github.com/sqlparser-rs/sqlparser-rs/releases) - [Changelog](https://github.com/sqlparser-rs/sqlparser-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/sqlparser-rs/sqlparser-rs/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: sqlparser dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- flock-cli/Cargo.toml | 2 +- flock/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flock-cli/Cargo.toml b/flock-cli/Cargo.toml index 6f8f00df..5ed2dd12 100644 --- a/flock-cli/Cargo.toml +++ b/flock-cli/Cargo.toml @@ -29,7 +29,7 @@ rusoto_lambda = { git = "https://github.com/flock-lab/rusoto", branch = "flock" rusoto_s3 = { git = "https://github.com/flock-lab/rusoto", branch = "flock" } rust-ini = "0.17" rustyline = { version = "9.0.0", optional = true } -sqlparser = { version = "0.13.0", features = [ "json_example" ] } +sqlparser = { version = "0.14.0", features = [ "json_example" ] } tokio = { version = "1.4", features = [ "macros", "io-util", "sync", "rt-multi-thread" ] } zip = "0.5.12" diff --git a/flock/Cargo.toml b/flock/Cargo.toml index 275229c3..def5a03b 100644 --- a/flock/Cargo.toml +++ b/flock/Cargo.toml @@ -57,7 +57,7 @@ serde_bytes = "0.11" serde_json = "1.0" snap = "1.0.3" snmalloc-rs = { version = "0.2", optional = true, features = [ "cache-friendly" ] } -sqlparser = "0.13.0" +sqlparser = "0.14.0" structopt = { git = "https://github.com/flock-lab/structopt", branch = "master", default-features = false } text_io = "0.1.8" tokio = { version = "1.4", features = [ "macros", "io-util", "sync", "rt-multi-thread" ] } From b42b8abb586c3e8270e780bfd80a944207b996d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:43:02 -0500 Subject: [PATCH 12/22] build(deps): update rust-ini requirement from 0.17 to 0.18 (#466) Updates the requirements on [rust-ini](https://github.com/zonyitoo/rust-ini) to permit the latest version. - [Release notes](https://github.com/zonyitoo/rust-ini/releases) - [Commits](https://github.com/zonyitoo/rust-ini/compare/v0.17.0...v0.18.0) --- updated-dependencies: - dependency-name: rust-ini dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- flock-cli/Cargo.toml | 2 +- flock/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flock-cli/Cargo.toml b/flock-cli/Cargo.toml index 5ed2dd12..5a9c5ea9 100644 --- a/flock-cli/Cargo.toml +++ b/flock-cli/Cargo.toml @@ -27,7 +27,7 @@ log = "0.4.14" rusoto_core = { git = "https://github.com/flock-lab/rusoto", branch = "flock" } rusoto_lambda = { git = "https://github.com/flock-lab/rusoto", branch = "flock" } rusoto_s3 = { git = "https://github.com/flock-lab/rusoto", branch = "flock" } -rust-ini = "0.17" +rust-ini = "0.18" rustyline = { version = "9.0.0", optional = true } sqlparser = { version = "0.14.0", features = [ "json_example" ] } tokio = { version = "1.4", features = [ "macros", "io-util", "sync", "rt-multi-thread" ] } diff --git a/flock/Cargo.toml b/flock/Cargo.toml index def5a03b..722226c1 100644 --- a/flock/Cargo.toml +++ b/flock/Cargo.toml @@ -51,7 +51,7 @@ rusoto_lambda = { git = "https://github.com/flock-lab/rusoto", branch = "flock" rusoto_logs = { git = "https://github.com/flock-lab/rusoto", branch = "flock" } rusoto_s3 = { git = "https://github.com/flock-lab/rusoto", branch = "flock" } rusoto_sqs = { git = "https://github.com/flock-lab/rusoto", branch = "flock" } -rust-ini = "0.17" +rust-ini = "0.18" serde = { version = "1.0", features = [ "derive" ] } serde_bytes = "0.11" serde_json = "1.0" From 700c00a67a351d872bb84509e9b93cf725b8a86f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:43:17 -0500 Subject: [PATCH 13/22] build(deps): update aws_lambda_events requirement from 0.5 to 0.6 (#465) Updates the requirements on [aws_lambda_events](https://github.com/LegNeato/aws-lambda-events) to permit the latest version. - [Release notes](https://github.com/LegNeato/aws-lambda-events/releases) - [Commits](https://github.com/LegNeato/aws-lambda-events/compare/vv0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: aws_lambda_events dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- flock-function/Cargo.toml | 2 +- flock/Cargo.toml | 2 +- playground/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flock-function/Cargo.toml b/flock-function/Cargo.toml index 3ce9ce75..bf1566f7 100644 --- a/flock-function/Cargo.toml +++ b/flock-function/Cargo.toml @@ -13,7 +13,7 @@ simd = [ "datafusion/simd" ] [dependencies] async-trait = "0.1.42" -aws_lambda_events = "0.5" +aws_lambda_events = "0.6" base64 = "0.13.0" bytes = "1.1.0" chrono = "0.4.19" diff --git a/flock/Cargo.toml b/flock/Cargo.toml index 722226c1..ea16f3cf 100644 --- a/flock/Cargo.toml +++ b/flock/Cargo.toml @@ -13,7 +13,7 @@ simd = [ "datafusion/simd" ] [dependencies] async-trait = "0.1.42" -aws_lambda_events = "0.5" +aws_lambda_events = "0.6" base64 = "0.13.0" bytes = "1.0.1" chrono = "0.4.19" diff --git a/playground/Cargo.toml b/playground/Cargo.toml index a482c186..7040d491 100644 --- a/playground/Cargo.toml +++ b/playground/Cargo.toml @@ -13,7 +13,7 @@ simd = [ "datafusion/simd" ] [dependencies] async-trait = "0.1.42" -aws_lambda_events = "0.5" +aws_lambda_events = "0.6" chrono = "0.4.19" datafusion = { git = "https://github.com/flock-lab/arrow-datafusion", branch = "flock" } env_logger = "^0.9" From de05a4e3d967a8f9cf47b6cd2685e81b76f9040b Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Fri, 25 Feb 2022 19:22:41 -0500 Subject: [PATCH 14/22] Update scheduled-build.yml --- .github/workflows/scheduled-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scheduled-build.yml b/.github/workflows/scheduled-build.yml index 0e3e8d59..d287e0e6 100644 --- a/.github/workflows/scheduled-build.yml +++ b/.github/workflows/scheduled-build.yml @@ -15,6 +15,7 @@ defaults: jobs: build: + if: ${{ false }} # disable for now strategy: fail-fast: false matrix: From 33948cee822adb5350072fa891903af6e9c3dca9 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Thu, 7 Apr 2022 11:00:52 -0400 Subject: [PATCH 15/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8716f2a..bd21f4a7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -## Flock: A Practical Serverless Streaming SQL Query Engine +## Flock: A SQL-on-FaaS Streaming Query Engine at Scale [![CI](https://github.com/flock-lab/flock/workflows/CI/badge.svg?branch=code&event=pull_request)](https://github.com/flock-lab/flock/actions) [![codecov](https://codecov.io/gh/flock-lab/flock/branch/master/graph/badge.svg?token=1FOM4DJUZJ)](https://codecov.io/gh/flock-lab/flock) From c71f8562f1afd68ef23fb75a70e9c8be15e8e33c Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Thu, 7 Apr 2022 19:06:54 -0400 Subject: [PATCH 16/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd21f4a7..17b65e7c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -## Flock: A SQL-on-FaaS Streaming Query Engine at Scale +## Flock: A Low-Cost Streaming Query Engine on FaaS Platforms [![CI](https://github.com/flock-lab/flock/workflows/CI/badge.svg?branch=code&event=pull_request)](https://github.com/flock-lab/flock/actions) [![codecov](https://codecov.io/gh/flock-lab/flock/branch/master/graph/badge.svg?token=1FOM4DJUZJ)](https://codecov.io/gh/flock-lab/flock) From 572c541c955b7cc839949122cba5f75ca6a074e7 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Mon, 14 Nov 2022 13:07:40 -0800 Subject: [PATCH 17/22] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 17b65e7c..4ac2abdf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ - - ## Flock: A Low-Cost Streaming Query Engine on FaaS Platforms [![CI](https://github.com/flock-lab/flock/workflows/CI/badge.svg?branch=code&event=pull_request)](https://github.com/flock-lab/flock/actions) From 601164704e2007882c4579fe7e30ca36eca1f135 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Mon, 14 Nov 2022 13:08:14 -0800 Subject: [PATCH 18/22] Delete flock.png --- docs/img/flock.png | Bin 65568 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/img/flock.png diff --git a/docs/img/flock.png b/docs/img/flock.png deleted file mode 100644 index 9e49af2ce560f2cae06fe811dc35d8c28c867729..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65568 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 Date: Mon, 14 Nov 2022 13:08:28 -0800 Subject: [PATCH 19/22] Delete flock2.png --- docs/img/flock2.png | Bin 136914 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/img/flock2.png diff --git a/docs/img/flock2.png b/docs/img/flock2.png deleted file mode 100644 index 99806cd04718fdc4150df4e76cc03a7f684063fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136914 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 Date: Tue, 15 Nov 2022 05:17:44 +0800 Subject: [PATCH 20/22] fix: use main branch for aws-lambda repo (#469) --- flock-function/Cargo.toml | 2 +- flock/Cargo.toml | 2 +- playground/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flock-function/Cargo.toml b/flock-function/Cargo.toml index bf1566f7..c9952ea8 100644 --- a/flock-function/Cargo.toml +++ b/flock-function/Cargo.toml @@ -24,7 +24,7 @@ flock = { path = "../flock" } futures = "0.3.12" hashring = { git = "https://github.com/flock-lab/hashring-rs", branch = "flock" } itertools = "0.10.0" -lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master" } +lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "main" } lazy_static = "1.4" log = "0.4.14" mimalloc = { version = "0.1", optional = true, default-features = false } diff --git a/flock/Cargo.toml b/flock/Cargo.toml index ea16f3cf..f4fe9cd5 100644 --- a/flock/Cargo.toml +++ b/flock/Cargo.toml @@ -31,7 +31,7 @@ humantime = "2.1.0" indoc = "1.0.3" itertools = "0.10.0" json = "0.12.4" -lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master" } +lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "main" } lazy_static = "1.4" log = "0.4.14" lz4 = "1.23.1" diff --git a/playground/Cargo.toml b/playground/Cargo.toml index 7040d491..7ff10cd8 100644 --- a/playground/Cargo.toml +++ b/playground/Cargo.toml @@ -21,7 +21,7 @@ flock = { path = "../flock" } futures = "0.3.12" indoc = "1.0.3" itertools = "0.10.0" -lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master" } +lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "main" } lazy_static = "1.4" log = "0.4.14" mimalloc = { version = "0.1", optional = true, default-features = false } From 7c94725a917fcbee251f3344ad1be11c58531bd0 Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Thu, 28 Dec 2023 21:31:17 -0800 Subject: [PATCH 21/22] Update README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 4ac2abdf..17e623df 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,29 @@ CLA assistant [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE) +Flock is a cloud-native streaming query engine that leverages the on-demand elasticity of Function-as-a-Service (FaaS) platforms to perform real-time data analytics. Traditional server-centric deployments often suffer from resource under- or over-provisioning, leading to resource wastage or performance degradation. Flock addresses these issues by providing more fine-grained elasticity that can dynamically match the per-query basis with continuous scaling, and its billing methods are more fine-grained with millisecond granularity, making it a low-cost solution for stream processing. Our approach, payload invocation, eliminates the need for external storage services and eliminates the requirement for a query coordinator in the data architecture. Our evaluation shows that Flock significantly outperforms state-of-the-art systems in terms of cost, especially on ARM processors, making it a promising solution for real-time data analytics on FaaS platforms. + + + The generic lambda function code is built in advance and uploaded to AWS S3. | FaaS Service | AWS Lambda | GCP Functions | Azure Functions | Architectures | SIMD | [YSB](https://github.com/yahoo/streaming-benchmarks) | [NEXMark](https://beam.apache.org/documentation/sdks/java/testing/nexmark/) | | :----------: | :--------: | :-----------: | :-------------: | :-----------: | :--: | :--------------------------------------------------: | :-------------------------------------------------------------------------: | | **Flock** | 🏅🏅🏅🏅 | 👉 TBD | 👉 TBD | **Arm**, x86 | ✅ | ✅ | ✅ | +## Paper + +``` +@misc{gang2023flock, + title={Flock: A Low-Cost Streaming Query Engine on FaaS Platforms}, + author={Gang Liao and Amol Deshpande and Daniel J. Abadi}, + year={2023}, + eprint={2312.16735}, + archivePrefix={arXiv}, + primaryClass={cs.DB} +} +``` + ## Build From Source Code You can enable the features `simd` (to use SIMD instructions) and/or `mimalloc` or `snmalloc` (to use either the mimalloc or snmalloc allocator) as features by passing them in as --features: From 4ac4164c55c9e4c5b76828dff15b620bde048b0d Mon Sep 17 00:00:00 2001 From: Gang Liao Date: Thu, 28 Dec 2023 21:31:56 -0800 Subject: [PATCH 22/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 17e623df..71767808 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The generic lambda function code is built in advance and uploaded to AWS S3. | :----------: | :--------: | :-----------: | :-------------: | :-----------: | :--: | :--------------------------------------------------: | :-------------------------------------------------------------------------: | | **Flock** | 🏅🏅🏅🏅 | 👉 TBD | 👉 TBD | **Arm**, x86 | ✅ | ✅ | ✅ | -## Paper +## [Arxiv Paper](https://arxiv.org/abs/2312.16735) ``` @misc{gang2023flock,