Browse Source

[trillium-rs] Update trillium framework benchmark (#8608)

* update trillium framework benchmark

* add jemallocator feature

* Add jbr to maintainers

* update dependencies
Jacob Rothstein 1 year ago
parent
commit
94b85856e6

File diff suppressed because it is too large
+ 388 - 226
frameworks/Rust/trillium/Cargo.lock


+ 16 - 10
frameworks/Rust/trillium/Cargo.toml

@@ -3,22 +3,28 @@ name = "trillium-techempower"
 version = "0.1.0"
 edition = "2021"
 
+[features]
+jemallocator = ["dep:jemallocator"]
+
 [dependencies]
 askama = "0.11.1"
-fastrand = "1.7.0"
-futures-lite = "1.12.0"
-futures-util = "0.3.21"
-serde = { version = "1.0.136", features = ["derive"] }
-serde_json = "1.0.79"
-trillium = "0.2.2"
+fastrand = "2.0.1"
+futures-lite = "2.1.0"
+serde = { version = "1.0.193", features = ["derive"] }
+serde_json = "1.0.108"
+trillium = "0.2.11"
 trillium-api = "0.1.0"
 trillium-askama = "0.3.0"
-trillium-async-std = "0.2.0"
-trillium-logger = "0.4.0"
-trillium-router = "0.3.2"
+trillium-smol = "0.3.1"
+trillium-logger = "0.4.3"
+trillium-router = "0.3.5"
+unicycle = "0.9.4"
+env_logger = "0.10.1"
+moka = { version = "0.12.1", features = ["future"] }
+jemallocator = {version="0.5.4", optional = true}
 
 [dependencies.sea-orm]
-version = "0.6.0"
+version = "0.12.9"
 default-features = false
 features = ["runtime-async-std-native-tls", "sqlx-postgres", "macros"]
 

+ 4 - 0
frameworks/Rust/trillium/README.md

@@ -53,3 +53,7 @@ PostgreSQL.
 ### Test 6: Plaintext
 
     http://localhost:8080/plaintext
+
+### Test 7: Caching
+
+    http://localhost:8080/cached/20

+ 3 - 0
frameworks/Rust/trillium/benchmark_config.json

@@ -1,5 +1,6 @@
 {
   "framework": "trillium",
+  "maintainers": ["jbr"],
   "tests": [
     {
       "default": {
@@ -8,6 +9,8 @@
         "query_url": "/queries/",
         "plaintext_url": "/plaintext",
         "fortune_url": "/fortunes",
+        "cached_query_url": "/cached-queries/",
+        "update_url": "/updates/",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Micro",

+ 2 - 6
frameworks/Rust/trillium/src/application.rs

@@ -1,14 +1,10 @@
 use crate::db::Db;
 use crate::routes::router;
-use trillium_logger::Logger;
 
 pub fn application() -> impl trillium::Handler {
     (
-        if cfg!(debug_assertions) {
-            Some(Logger::new())
-        } else {
-            None
-        },
+        #[cfg(debug_assertions)]
+        trillium_logger::logger(),
         Db::default(),
         router(),
     )

+ 15 - 10
frameworks/Rust/trillium/src/db.rs

@@ -5,9 +5,23 @@ use trillium::{async_trait, Conn, Handler, Info};
 #[derive(Debug, Default)]
 pub struct Db(Option<DatabaseConnection>);
 
+pub mod cached_world;
 pub mod fortune;
 pub mod world;
 
+impl Db {
+    pub(crate) async fn connection() -> DatabaseConnection {
+        let db_url = env::var("DATABASE_URL").expect("env var DATABASE_URL not found");
+
+        let connect_options = ConnectOptions::new(db_url.clone());
+
+        Database::connect(connect_options)
+            .await
+            .map_err(|e| format!("could not connect to {}: {}", &db_url, e))
+            .unwrap()
+    }
+}
+
 #[async_trait]
 impl Handler for Db {
     async fn run(&self, conn: Conn) -> Conn {
@@ -16,16 +30,7 @@ impl Handler for Db {
 
     async fn init(&mut self, _info: &mut Info) {
         if self.0.is_none() {
-            let db_url = env::var("DATABASE_URL").expect("env var DATABASE_URL not found");
-
-            let connect_options = ConnectOptions::new(db_url.clone());
-
-            let db = Database::connect(connect_options)
-                .await
-                .map_err(|e| format!("could not connect to {}: {}", &db_url, e))
-                .unwrap();
-
-            self.0 = Some(db);
+            self.0 = Some(Self::connection().await);
         }
     }
 }

+ 24 - 0
frameworks/Rust/trillium/src/db/cached_world.rs

@@ -0,0 +1,24 @@
+use sea_orm::entity::prelude::*;
+use serde::Serialize;
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize)]
+#[sea_orm(table_name = "World")]
+#[serde(rename_all = "camelCase")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub id: i32,
+
+    #[sea_orm(column_name = "randomnumber")]
+    pub random_number: i32,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter)]
+pub enum Relation {}
+
+impl RelationTrait for Relation {
+    fn def(&self) -> RelationDef {
+        unimplemented!()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}

+ 14 - 1
frameworks/Rust/trillium/src/db/fortune.rs

@@ -1,7 +1,8 @@
 use sea_orm::entity::prelude::*;
 use serde::Serialize;
+use std::cmp::Ordering;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Eq)]
 #[sea_orm(table_name = "Fortune")]
 pub struct Model {
     #[sea_orm(primary_key)]
@@ -20,3 +21,15 @@ impl RelationTrait for Relation {
 }
 
 impl ActiveModelBehavior for ActiveModel {}
+
+impl PartialOrd for Model {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Model {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.message.cmp(&other.message)
+    }
+}

+ 16 - 2
frameworks/Rust/trillium/src/main.rs

@@ -1,9 +1,23 @@
+#[cfg(feature = "jemallcator")]
+#[global_allocator]
+static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
+
 mod application;
 mod db;
 mod routes;
-
 use application::application;
+use trillium::HttpConfig;
 
 fn main() {
-    trillium_async_std::run(application())
+    #[cfg(debug_assertions)]
+    env_logger::init();
+
+    let http_config = HttpConfig::default()
+        .with_response_buffer_len(256)
+        .with_request_buffer_initial_len(256)
+        .with_response_header_initial_capacity(5);
+
+    trillium_smol::config()
+        .with_http_config(http_config)
+        .run(application())
 }

+ 4 - 1
frameworks/Rust/trillium/src/routes.rs

@@ -1,11 +1,11 @@
 use trillium_router::Router;
+mod cached_queries;
 mod db;
 mod fortune;
 mod json;
 mod plaintext;
 mod queries;
 mod updates;
-
 pub fn router() -> Router {
     Router::build(|mut router| {
         router.get("/fortunes", fortune::handler);
@@ -16,5 +16,8 @@ pub fn router() -> Router {
         router.get("/plaintext", plaintext::handler);
         router.get("/updates/:updates", updates::handler);
         router.get("/updates", updates::handler);
+        let cached_queries = cached_queries::handler();
+        router.get("/cached-queries/:count", cached_queries.clone());
+        router.get("/cached-queries", cached_queries);
     })
 }

+ 80 - 0
frameworks/Rust/trillium/src/routes/cached_queries.rs

@@ -0,0 +1,80 @@
+use crate::db::{
+    cached_world::{Entity as CachedWorlds, Model as CachedWorld},
+    Db, DbConnExt,
+};
+use futures_lite::StreamExt;
+use moka::future::Cache;
+use sea_orm::{DatabaseConnection, DbErr, EntityTrait};
+use std::{iter, sync::Arc};
+use trillium::{Conn, Handler, Info, Status};
+use trillium_api::ApiConnExt;
+use trillium_router::RouterConnExt;
+use unicycle::FuturesUnordered;
+
+pub fn handler() -> CachedWorldHandler {
+    CachedWorldHandler {
+        cache: Cache::new(10_000),
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct CachedWorldHandler {
+    cache: Cache<i32, CachedWorld>,
+}
+
+impl CachedWorldHandler {
+    #[inline(always)]
+    fn count_param(conn: &Conn) -> usize {
+        conn.param("count")
+            .and_then(|s| s.parse().ok())
+            .unwrap_or(1)
+            .min(500)
+            .max(1)
+    }
+
+    #[inline(always)]
+    async fn fetch_cached(
+        &self,
+        db: &DatabaseConnection,
+        id: i32,
+    ) -> Result<CachedWorld, Arc<DbErr>> {
+        self.cache
+            .try_get_with(id, async {
+                CachedWorlds::find_by_id(id)
+                    .one(db)
+                    .await?
+                    .ok_or_else(|| DbErr::RecordNotFound(String::from("not found")))
+            })
+            .await
+    }
+}
+
+#[trillium::async_trait]
+impl Handler for CachedWorldHandler {
+    async fn init(&mut self, _info: &mut Info) {
+        if self.cache.entry_count() == 0 {
+            let db = Db::connection().await;
+            let mut stream = CachedWorlds::find().stream(&db).await.unwrap();
+            while let Some(Ok(world)) = stream.next().await {
+                self.cache.insert(world.id, world).await
+            }
+            self.cache.run_pending_tasks().await;
+        }
+    }
+
+    async fn run(&self, conn: Conn) -> Conn {
+        let count = Self::count_param(&conn);
+        let db = conn.db();
+        let worlds: Result<Vec<_>, _> =
+            iter::repeat_with(|| self.fetch_cached(db, fastrand::i32(1..=10_000)))
+                .take(count)
+                .collect::<FuturesUnordered<_>>()
+                .try_collect()
+                .await;
+
+        match worlds {
+            Ok(worlds) => conn.with_json(&worlds),
+            Err(_) => conn.with_status(Status::InternalServerError),
+        }
+    }
+}

+ 7 - 12
frameworks/Rust/trillium/src/routes/db.rs

@@ -1,18 +1,13 @@
 use crate::db::{world::Entity as World, DbConnExt};
 use sea_orm::entity::prelude::*;
-use trillium::{conn_unwrap, Conn};
+use trillium::{Conn, Status};
 use trillium_api::ApiConnExt;
 
 pub async fn handler(conn: Conn) -> Conn {
-    let random = fastrand::i32(1..10000);
-    let world = conn_unwrap!(
-        World::find_by_id(random)
-            .one(conn.db())
-            .await
-            .ok()
-            .flatten(),
-        conn
-    );
-
-    conn.with_json(&world)
+    let id = fastrand::i32(1..=10_000);
+    match World::find_by_id(id).one(conn.db()).await {
+        Ok(Some(world)) => conn.with_json(&world),
+        Err(_) => conn.with_status(Status::InternalServerError),
+        Ok(None) => conn.with_status(Status::NotFound),
+    }
 }

+ 15 - 7
frameworks/Rust/trillium/src/routes/fortune.rs

@@ -2,26 +2,34 @@ use crate::db::{
     fortune::{Entity as Fortunes, Model as Fortune},
     DbConnExt,
 };
-use sea_orm::entity::prelude::*;
-use trillium::{conn_try, Conn, KnownHeaderName::ContentType};
+use futures_lite::StreamExt;
+use sea_orm::EntityTrait;
+use std::collections::BTreeSet;
+use trillium::{Conn, KnownHeaderName::ContentType, Status};
 use trillium_askama::{AskamaConnExt, Template};
 
 #[derive(Template)]
 #[template(path = "fortunes.html")]
 struct FortuneTemplate<'a> {
-    fortunes: &'a [Fortune],
+    fortunes: &'a BTreeSet<Fortune>,
 }
 
 pub async fn handler(conn: Conn) -> Conn {
-    let db = conn.db();
+    let db = conn.db().clone();
 
-    let mut fortunes = conn_try!(Fortunes::find().all(db).await, conn);
-    fortunes.push(Fortune {
+    let mut fortunes = BTreeSet::new();
+    fortunes.insert(Fortune {
         id: 0,
         message: String::from("Additional fortune added at request time."),
     });
 
-    fortunes.sort_by(|a, b| a.message.cmp(&b.message));
+    let Ok(mut stream) = Fortunes::find().stream(&db).await else {
+        return conn.with_status(Status::InternalServerError);
+    };
+
+    while let Some(Ok(fortune)) = stream.next().await {
+        fortunes.insert(fortune);
+    }
 
     conn.render(FortuneTemplate {
         fortunes: &fortunes,

+ 4 - 5
frameworks/Rust/trillium/src/routes/json.rs

@@ -1,8 +1,7 @@
-use serde_json::{json, to_string};
-use trillium::{conn_try, Conn, KnownHeaderName};
+use serde_json::json;
+use trillium::Conn;
+use trillium_api::ApiConnExt;
 
 pub async fn handler(conn: Conn) -> Conn {
-    let body = conn_try!(to_string(&json!({"message": "Hello, World!"})), conn);
-    conn.ok(body)
-        .with_header(KnownHeaderName::ContentType, "application/json")
+    conn.with_json(&json!({"message": "Hello, World!"}))
 }

+ 20 - 17
frameworks/Rust/trillium/src/routes/queries.rs

@@ -1,11 +1,11 @@
 use crate::db::{world::Entity as Worlds, DbConnExt};
 use futures_lite::StreamExt;
-use futures_util::stream::futures_unordered::FuturesUnordered;
-use sea_orm::entity::prelude::*;
+use sea_orm::{entity::prelude::*, TransactionTrait};
 use std::iter;
-use trillium::{conn_try, Conn};
+use trillium::{Conn, Status};
 use trillium_api::ApiConnExt;
 use trillium_router::RouterConnExt;
+use unicycle::FuturesUnordered;
 
 pub async fn handler(conn: Conn) -> Conn {
     let queries = conn
@@ -15,20 +15,23 @@ pub async fn handler(conn: Conn) -> Conn {
         .min(500)
         .max(1);
 
-    let db = conn.db();
+    let Ok(tx) = conn.db().begin().await else {
+        return conn.with_status(Status::InternalServerError);
+    };
 
-    let vec_of_worlds: Result<Vec<_>, DbErr> =
-        iter::repeat_with(|| Worlds::find_by_id(fastrand::i32(1..10000)).one(db))
-            .take(queries)
-            .collect::<FuturesUnordered<_>>()
-            .map(|x| match x {
-                Ok(None) => Err(DbErr::RecordNotFound(String::from("not found"))),
-                other => other,
-            })
-            .try_collect()
-            .await;
+    let worlds = iter::repeat_with(|| async {
+        Worlds::find_by_id(fastrand::i32(1..=10_000))
+            .one(&tx)
+            .await?
+            .ok_or_else(|| DbErr::RecordNotFound(String::from("not found")))
+    })
+    .take(queries)
+    .collect::<FuturesUnordered<_>>()
+    .try_collect::<_, _, Vec<_>>()
+    .await;
 
-    let vec_of_worlds = conn_try!(vec_of_worlds, conn);
-
-    conn.with_json(&vec_of_worlds)
+    match worlds {
+        Ok(worlds) => conn.with_json(&worlds),
+        Err(_) => conn.with_status(Status::InternalServerError),
+    }
 }

+ 21 - 21
frameworks/Rust/trillium/src/routes/updates.rs

@@ -1,14 +1,11 @@
-use crate::db::{
-    world::{Entity as Worlds, Model as World},
-    DbConnExt,
-};
-
-use futures_util::stream::{futures_unordered::FuturesUnordered, StreamExt};
+use crate::db::{world::Entity as Worlds, DbConnExt};
+use futures_lite::StreamExt;
 use sea_orm::{entity::prelude::*, IntoActiveModel, Set};
 use std::iter;
-use trillium::Conn;
+use trillium::{Conn, Status};
 use trillium_api::ApiConnExt;
 use trillium_router::RouterConnExt;
+use unicycle::FuturesUnordered;
 
 pub async fn handler(conn: Conn) -> Conn {
     let queries = conn
@@ -19,19 +16,22 @@ pub async fn handler(conn: Conn) -> Conn {
         .max(1);
 
     let db = conn.db();
+    let worlds = iter::repeat_with(|| async {
+        let mut world = Worlds::find_by_id(fastrand::i32(1..=10_000))
+            .one(db)
+            .await?
+            .ok_or_else(|| DbErr::RecordNotFound(String::from("not found")))?
+            .into_active_model();
+        world.random_number = Set(fastrand::i32(1..=10_000));
+        world.update(db).await
+    })
+    .take(queries)
+    .collect::<FuturesUnordered<_>>()
+    .try_collect::<_, _, Vec<_>>()
+    .await;
 
-    let vec_of_worlds: Vec<World> =
-        iter::repeat_with(|| Worlds::find_by_id(fastrand::i32(1..10000)).one(db))
-            .take(queries)
-            .collect::<FuturesUnordered<_>>()
-            .filter_map(|x| async move { x.ok().flatten() })
-            .filter_map(|w| async move {
-                let mut am = w.clone().into_active_model();
-                am.random_number = Set(fastrand::i32(1..10000));
-                am.update(db).await.ok()
-            })
-            .collect()
-            .await;
-
-    conn.with_json(&vec_of_worlds)
+    match worlds {
+        Ok(worlds) => conn.with_json(&worlds),
+        Err(_) => conn.with_status(Status::InternalServerError),
+    }
 }

+ 2 - 2
frameworks/Rust/trillium/templates/fortunes.html

@@ -4,9 +4,9 @@
   <body>
     <table>
       <tr><th>id</th><th>message</th></tr>
-      {% for fortune in fortunes %}
+      {%- for fortune in fortunes -%}
       <tr><td>{{fortune.id}}</td><td>{{fortune.message}}</td></tr>
-      {% endfor %}
+      {%- endfor -%}
     </table>
   </body>
 </html>

+ 2 - 2
frameworks/Rust/trillium/trillium.dockerfile

@@ -1,4 +1,4 @@
-FROM rust:1.57
+FROM rust:1.74
 WORKDIR /trillium
 COPY src src
 COPY templates templates
@@ -12,5 +12,5 @@ ENV PORT=8080
 ENV HOST=0.0.0.0
 ENV DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world
 
-RUN cargo build --release
+RUN cargo build --release --features jemallocator
 CMD ["./target/release/trillium-techempower"]

Some files were not shown because too many files changed in this diff