Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[xitca-web] add bench for async orm #9287

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
536 changes: 256 additions & 280 deletions frameworks/Rust/xitca-web/Cargo.lock

Large diffs are not rendered by default.

44 changes: 23 additions & 21 deletions frameworks/Rust/xitca-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,32 @@ 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"]

Expand All @@ -65,19 +63,21 @@ serde_json = { version = "1" }
xitca-web = { version = "0.6", 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 +95,7 @@ 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" }
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
Loading