Skip to content

Commit

Permalink
[xitca-web] add bench for async orm (TechEmpower#9287)
Browse files Browse the repository at this point in the history
* [xitca-web] add bench for async orm

* dep fix

* use release build of xitca-postgres

* fix framework naming

* dep update

* fix build

* add unrealistic db impl

* dedup

* improve compile time

* update to pre-release version of xitca-web
  • Loading branch information
fakeshadow authored Oct 10, 2024
1 parent 643bbbd commit 8d4f5ed
Show file tree
Hide file tree
Showing 15 changed files with 945 additions and 685 deletions.
576 changes: 274 additions & 302 deletions frameworks/Rust/xitca-web/Cargo.lock

Large diffs are not rendered by default.

61 changes: 35 additions & 26 deletions frameworks/Rust/xitca-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,65 +19,66 @@ path = "./src/main_wasm.rs"
required-features = ["web"]

[[bin]]
name = "xitca-web-axum"
path = "./src/main_axum.rs"
required-features = ["axum", "io-uring", "perf", "pg-sync", "template"]
name = "xitca-web-orm"
path = "./src/main_orm.rs"
required-features = ["pg-orm-async", "template", "web-codegen"]

[[bin]]
name = "xitca-web-sync"
path = "./src/main_sync.rs"
required-features = ["pg-orm", "template", "web-codegen"]

[features]
# pg optional
# pg client optional
pg = ["dep:xitca-postgres"]
# pg send/sync optional
pg-sync = ["dep:xitca-postgres"]
# pg orm optional
pg-orm = ["dep:diesel"]
# diesel orm optional
pg-orm = ["diesel/r2d2"]
# diesel async orm optional
pg-orm-async = ["dep:diesel", "dep:diesel-async", "dep:xitca-postgres-diesel", "futures-util"]
# http router optional
router = ["xitca-http/router"]
# web optional
web = ["dep:xitca-web"]
# web codegen optional
# web with macros optional
web-codegen = ["xitca-web/codegen", "xitca-web/urlencoded"]
# template optional
template = ["dep:sailfish"]
# io-uring optional
io-uring = ["xitca-http/io-uring", "xitca-server/io-uring"]
# axum optional
axum = ["dep:axum", "dep:http-body", "dep:tower", "dep:tower-http", "xitca-web/tower-http-compat" ]
io-uring = ["dep:tokio-uring", "xitca-http/io-uring", "xitca-server/io-uring"]
# unrealistic performance optimization
perf = ["dep:mimalloc", "tokio/parking_lot"]

[dependencies]
xitca-http = "0.6"
xitca-io = "0.4"
xitca-server = "0.4"
xitca-service = "0.2"
xitca-http = "0.7"
xitca-io = "0.4.1"
xitca-server = "0.5"
xitca-service = "0.3"
xitca-unsafe-collection = "0.2"

atoi = "2"
httparse = "1"
serde = { version = "1" }
serde_json = { version = "1" }

# web optional
xitca-web = { version = "0.6", features = ["json"], optional = true }
xitca-web = { version = "0.7", features = ["json"], optional = true }

# raw-pg optional
xitca-postgres = { version = "0.1", optional = true }
xitca-postgres = { version = "0.2", optional = true }

# orm optional
diesel = { version = "2", features = ["postgres", "r2d2"], optional = true }
diesel = { version = "2", features = ["postgres"], optional = true }

# orm async optional
diesel-async = { version = "0.5", features = ["bb8", "postgres"], optional = true }
xitca-postgres-diesel = { version = "0.1", optional = true }
futures-util = { version = "0.3", default-features = false, optional = true }

# template optional
sailfish = { version = "0.9", default-features = false, features = ["derive", "perf-inline"], optional = true }
sailfish = { version = "0.9", default-features = false, features = ["perf-inline"], optional = true }

# axum optional
axum = { version = "0.7", optional = true, default-features = false, features = ["json", "query"] }
http-body = { version = "1", optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["set-header"], optional = true }
# io-uring optional
tokio-uring = { version = "0.5", optional = true }

# perf optional
mimalloc = { version = "0.1", default-features = false, optional = true }
Expand All @@ -95,5 +96,13 @@ codegen-units = 1
panic = "abort"

[patch.crates-io]
xitca-postgres = { git = "https://github.com/HFQR/xitca-web.git", rev = "0cda225" }
xitca-postgres-diesel = { git = "https://github.com/fakeshadow/xitca-postgres-diesel", rev = "ae93ee9" }

diesel-async = { git = "https://github.com/weiznich/diesel_async", rev = "5b8262b" }
mio = { git = "https://github.com/fakeshadow/mio", rev = "9bae6012b7ecfc6083350785f71a5e8265358178" }

xitca-codegen = { git = "http://github.com/HFQR/xitca-web", rev = "d3066ba" }
xitca-http = { git = "http://github.com/HFQR/xitca-web", rev = "d3066ba" }
xitca-server = { git = "http://github.com/HFQR/xitca-web", rev = "d3066ba" }
xitca-service = { git = "http://github.com/HFQR/xitca-web", rev = "d3066ba" }
xitca-web = { git = "http://github.com/HFQR/xitca-web", rev = "d3066ba" }
18 changes: 9 additions & 9 deletions frameworks/Rust/xitca-web/benchmark_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@
"approach": "Stripped",
"classification": "Platform",
"database": "Postgres",
"framework": "xitca-web [unrealistic]",
"framework": "xitca-web",
"language": "Rust",
"orm": "Raw",
"platform": "None",
"webserver": "xitca-server",
"os": "Linux",
"database_os": "Linux",
"display_name": "xitca-web [unrealistic]",
"display_name": "xitca-web [iou]",
"notes": "",
"versus": ""
},
Expand All @@ -53,7 +53,7 @@
"approach": "Realistic",
"classification": "Micro",
"database": "none",
"framework": "xitca-web [wasm]",
"framework": "xitca-web",
"language": "rust",
"orm": "raw",
"platform": "none",
Expand All @@ -64,7 +64,7 @@
"notes": "",
"versus": ""
},
"axum": {
"orm": {
"json_url": "/json",
"plaintext_url": "/plaintext",
"db_url": "/db",
Expand All @@ -73,16 +73,16 @@
"update_url": "/updates?q=",
"port": 8080,
"approach": "realistic",
"classification": "micro",
"classification": "fullstack",
"database": "postgres",
"framework": "axum [xitca]",
"framework": "xitca-web",
"language": "rust",
"orm": "raw",
"orm": "full",
"platform": "none",
"webserver": "xitca-server",
"os": "linux",
"database_os": "linux",
"display_name": "axum [xitca]",
"display_name": "xitca-web [orm]",
"notes": "",
"versus": ""
},
Expand All @@ -97,7 +97,7 @@
"approach": "realistic",
"classification": "micro",
"database": "postgres",
"framework": "xitca-web [sync]",
"framework": "xitca-web",
"language": "rust",
"orm": "full",
"platform": "none",
Expand Down
143 changes: 37 additions & 106 deletions frameworks/Rust/xitca-web/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,101 +1,63 @@
// clippy is dumb and have no idea what should be lazy or not
#![allow(clippy::unnecessary_lazy_evaluations)]
#[path = "./db_util.rs"]
mod db_util;

use xitca_io::bytes::BytesMut;
use xitca_postgres::{pipeline::Pipeline, pool::Pool, AsyncLendingIterator, Type};
use std::cell::RefCell;

use xitca_postgres::{
iter::AsyncLendingIterator, pipeline::Pipeline, pool::Pool, statement::Statement, Execute, ExecuteMut,
};

use super::{
ser::{Fortune, Fortunes, World},
util::{bulk_update_gen, HandleResult, Rand, DB_URL},
util::{HandleResult, DB_URL},
};

use db_util::{sort_update_params, update_query, Shared, FORTUNE_STMT, WORLD_STMT};

pub struct Client {
pool: Pool,
#[cfg(not(feature = "pg-sync"))]
shared: std::cell::RefCell<Shared>,
#[cfg(feature = "pg-sync")]
shared: std::sync::Mutex<Shared>,
shared: RefCell<Shared>,
updates: Box<[Box<str>]>,
}

type Shared = (Rand, BytesMut);

const FORTUNE_SQL: &str = "SELECT * FROM fortune";

const FORTUNE_SQL_TYPES: &[Type] = &[];

const WORLD_SQL: &str = "SELECT * FROM world WHERE id=$1";

const WORLD_SQL_TYPES: &[Type] = &[Type::INT4];

fn update_query(num: usize) -> Box<str> {
bulk_update_gen(|query| {
use std::fmt::Write;
(1..=num).fold((1, query), |(idx, query), _| {
write!(query, "(${}::int,${}::int),", idx, idx + 1).unwrap();
(idx + 2, query)
});
})
.into_boxed_str()
}

pub async fn create() -> HandleResult<Client> {
let pool = Pool::builder(DB_URL).capacity(1).build()?;

let shared = (Rand::default(), BytesMut::new());

let updates = core::iter::once(Box::from(""))
.chain((1..=500).map(update_query))
.collect();

Ok(Client {
pool,
#[cfg(not(feature = "pg-sync"))]
shared: std::cell::RefCell::new(shared),
#[cfg(feature = "pg-sync")]
shared: std::sync::Mutex::new(shared),
updates,
pool: Pool::builder(DB_URL).capacity(1).build()?,
shared: Default::default(),
updates: core::iter::once(Box::from(""))
.chain((1..=500).map(update_query))
.collect(),
})
}

impl Client {
#[cfg(not(feature = "pg-sync"))]
fn shared(&self) -> std::cell::RefMut<'_, Shared> {
self.shared.borrow_mut()
}

#[cfg(feature = "pg-sync")]
fn shared(&self) -> std::sync::MutexGuard<'_, Shared> {
self.shared.lock().unwrap()
}

pub async fn get_world(&self) -> HandleResult<World> {
let mut conn = self.pool.get().await?;
let stmt = conn.prepare(WORLD_SQL, WORLD_SQL_TYPES).await?;
let id = self.shared().0.gen_id();
let mut res = conn.consume().query_raw(&stmt, [id])?;
let row = res.try_next().await?.ok_or_else(|| "World does not exist")?;
Ok(World::new(row.get_raw(0), row.get_raw(1)))
let stmt = WORLD_STMT.execute_mut(&mut conn).await?;
let id = self.shared.borrow_mut().0.gen_id();
let mut res = stmt.bind([id]).query(&conn.consume()).await?;
let row = res.try_next().await?.ok_or("request World does not exist")?;
Ok(World::new(row.get(0), row.get(1)))
}

pub async fn get_worlds(&self, num: u16) -> HandleResult<Vec<World>> {
let len = num as usize;

let mut conn = self.pool.get().await?;
let stmt = conn.prepare(WORLD_SQL, WORLD_SQL_TYPES).await?;
let stmt = WORLD_STMT.execute_mut(&mut conn).await?;

let mut res = {
let (ref mut rng, ref mut buf) = *self.shared();
let (ref mut rng, ref mut buf) = *self.shared.borrow_mut();
let mut pipe = Pipeline::with_capacity_from_buf(len, buf);
(0..num).try_for_each(|_| pipe.query_raw(&stmt, [rng.gen_id()]))?;
conn.consume().pipeline(pipe)?
(0..num).try_for_each(|_| stmt.bind([rng.gen_id()]).query_mut(&mut pipe))?;
pipe.query(&conn.consume())?
};

let mut worlds = Vec::with_capacity(len);

while let Some(mut item) = res.try_next().await? {
while let Some(row) = item.try_next().await? {
worlds.push(World::new(row.get_raw(0), row.get_raw(1)))
worlds.push(World::new(row.get(0), row.get(1)))
}
}

Expand All @@ -105,25 +67,24 @@ impl Client {
pub async fn update(&self, num: u16) -> HandleResult<Vec<World>> {
let len = num as usize;

let update = self.updates.get(len).ok_or_else(|| "num out of bound")?;

let update = self.updates.get(len).ok_or("request num is out of range")?;
let mut conn = self.pool.get().await?;
let world_stmt = conn.prepare(WORLD_SQL, WORLD_SQL_TYPES).await?;
let update_stmt = conn.prepare(update, &[]).await?;
let world_stmt = WORLD_STMT.execute_mut(&mut conn).await?;
let update_stmt = Statement::named(update, &[]).execute_mut(&mut conn).await?;

let mut params = Vec::with_capacity(len);

let mut res = {
let (ref mut rng, ref mut buf) = *self.shared();
let (ref mut rng, ref mut buf) = *self.shared.borrow_mut();
let mut pipe = Pipeline::with_capacity_from_buf(len + 1, buf);
(0..num).try_for_each(|_| {
let w_id = rng.gen_id();
let r_id = rng.gen_id();
params.push([w_id, r_id]);
pipe.query_raw(&world_stmt, [w_id])
world_stmt.bind([w_id]).query_mut(&mut pipe)
})?;
pipe.query_raw(&update_stmt, sort_update_params(&params))?;
conn.consume().pipeline(pipe)?
update_stmt.bind(sort_update_params(&params)).query_mut(&mut pipe)?;
pipe.query(&conn.consume())?
};

let mut worlds = Vec::with_capacity(len);
Expand All @@ -133,7 +94,7 @@ impl Client {
while let Some(mut item) = res.try_next().await? {
while let Some(row) = item.try_next().await? {
let r_id = r_ids.next().unwrap()[1];
worlds.push(World::new(row.get_raw(0), r_id))
worlds.push(World::new(row.get(0), r_id))
}
}

Expand All @@ -145,45 +106,15 @@ impl Client {
items.push(Fortune::new(0, "Additional fortune added at request time."));

let mut conn = self.pool.get().await?;
let stmt = conn.prepare(FORTUNE_SQL, FORTUNE_SQL_TYPES).await?;
let mut res = conn.consume().query_raw::<[i32; 0]>(&stmt, [])?;
let stmt = FORTUNE_STMT.execute_mut(&mut conn).await?;
let mut res = stmt.query(&conn.consume()).await?;

while let Some(row) = res.try_next().await? {
items.push(Fortune::new(row.get_raw(0), row.get_raw::<String>(1)));
items.push(Fortune::new(row.get(0), row.get::<String>(1)));
}

items.sort_by(|it, next| it.message.cmp(&next.message));

Ok(Fortunes::new(items))
}
}

fn sort_update_params(params: &[[i32; 2]]) -> impl ExactSizeIterator<Item = i32> {
let mut params = params.to_owned();
params.sort_by(|a, b| a[0].cmp(&b[0]));

struct ParamIter<I>(I);

impl<I> Iterator for ParamIter<I>
where
I: Iterator,
{
type Item = I::Item;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}

// impl depends on compiler optimization to flat Vec<[T]> to Vec<T> when inferring
// it's size hint. possible to cause runtime panic.
impl<I> ExactSizeIterator for ParamIter<I> where I: Iterator {}

ParamIter(params.into_iter().flatten())
}
Loading

0 comments on commit 8d4f5ed

Please sign in to comment.