feat: add caches

This commit is contained in:
Ondrej Vlach 2023-12-11 22:05:39 +01:00
parent 64a1d88479
commit b7b3957db8
Signed by: ovlach
GPG Key ID: 4FF1A23B4914DE70
7 changed files with 296 additions and 12 deletions

185
Cargo.lock generated
View File

@ -47,6 +47,15 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener",
]
[[package]] [[package]]
name = "async-mutex" name = "async-mutex"
version = "1.4.0" version = "1.4.0"
@ -233,6 +242,12 @@ version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "bytecount"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.14.0" version = "1.14.0"
@ -251,6 +266,37 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "camino"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -593,6 +639,15 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "error-chain"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "2.5.3" version = "2.5.3"
@ -1325,6 +1380,15 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "mach2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "matchers" name = "matchers"
version = "0.1.0" version = "0.1.0"
@ -1382,6 +1446,31 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "moka"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8017ec3548ffe7d4cef7ac0e12b044c01164a74c0f3119420faeaf13490ad8b"
dependencies = [
"async-lock",
"async-trait",
"crossbeam-channel",
"crossbeam-epoch",
"crossbeam-utils",
"futures-util",
"log",
"once_cell",
"parking_lot",
"quanta",
"rustc_version",
"skeptic",
"smallvec",
"tagptr",
"thiserror",
"triomphe",
"uuid",
]
[[package]] [[package]]
name = "multer" name = "multer"
version = "2.1.0" version = "2.1.0"
@ -1531,9 +1620,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.18.0" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "openssl" name = "openssl"
@ -1724,8 +1813,10 @@ dependencies = [
"fern", "fern",
"headless_chrome", "headless_chrome",
"log", "log",
"moka",
"nanobyte_opentelemetry", "nanobyte_opentelemetry",
"nanobyte_tera", "nanobyte_tera",
"once_cell",
"ovlach_data", "ovlach_data",
"ovlach_tera", "ovlach_tera",
"phf", "phf",
@ -2020,6 +2111,33 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "pulldown-cmark"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
dependencies = [
"bitflags 1.3.2",
"memchr",
"unicase",
]
[[package]]
name = "quanta"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab"
dependencies = [
"crossbeam-utils",
"libc",
"mach2",
"once_cell",
"raw-cpuid",
"wasi",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.33"
@ -2059,6 +2177,15 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "raw-cpuid"
version = "10.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332"
dependencies = [
"bitflags 1.3.2",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.3.5" version = "0.3.5"
@ -2310,6 +2437,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.26" version = "0.38.26"
@ -2435,6 +2571,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6"
[[package]]
name = "semver"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.193" version = "1.0.193"
@ -2559,6 +2704,21 @@ version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "skeptic"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8"
dependencies = [
"bytecount",
"cargo_metadata",
"error-chain",
"glob",
"pulldown-cmark",
"tempfile",
"walkdir",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -2700,6 +2860,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "tagptr"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.8.1" version = "3.8.1"
@ -3090,6 +3256,12 @@ dependencies = [
"tracing-serde", "tracing-serde",
] ]
[[package]]
name = "triomphe"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.4" version = "0.2.4"
@ -3223,6 +3395,15 @@ dependencies = [
"unic-common", "unic-common",
] ]
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.13" version = "0.3.13"

View File

@ -28,6 +28,8 @@ phf = { version = "0.11.2", features = ["macros"] }
nanobyte_tera = { version = "0.2.0", registry = "gitea_nanobyte" } nanobyte_tera = { version = "0.2.0", registry = "gitea_nanobyte" }
ovlach_tera = { version="0.2.0", registry="gitea_ovlach" } ovlach_tera = { version="0.2.0", registry="gitea_ovlach" }
ovlach_data = { version = "0.1.3", registry = "gitea_ovlach"} ovlach_data = { version = "0.1.3", registry = "gitea_ovlach"}
moka = { version = "0.12.1", features = ["future", 'log'] }
once_cell = "1.19.0"
[dev-dependencies] [dev-dependencies]
serde_json = "1.0.108" serde_json = "1.0.108"

View File

@ -3,6 +3,7 @@ use nanobyte_opentelemetry::rocket::TracingFairing;
use rocket::{Rocket, Build, routes, fairing::AdHoc}; use rocket::{Rocket, Build, routes, fairing::AdHoc};
use rocket_prometheus::PrometheusMetrics; use rocket_prometheus::PrometheusMetrics;
use serde::Deserialize; use serde::Deserialize;
use tools::cache::{build_cache, cache_prometheus_metrics};
pub mod routes; pub mod routes;
mod chromium; mod chromium;
mod tools; mod tools;
@ -26,6 +27,7 @@ pub struct DefaultPerson {
pub fn rocket_builder() -> Rocket<Build> { pub fn rocket_builder() -> Rocket<Build> {
let prometheus = PrometheusMetrics::new(); let prometheus = PrometheusMetrics::new();
cache_prometheus_metrics(&prometheus).expect("Failed to register prometheus cache metrics");
rocket::build() rocket::build()
.attach(TracingFairing::ignite()) .attach(TracingFairing::ignite())
@ -37,6 +39,7 @@ pub fn rocket_builder() -> Rocket<Build> {
AdHoc::config::<DefaultPerson>() AdHoc::config::<DefaultPerson>()
) )
.attach(prometheus.clone()) .attach(prometheus.clone())
.manage( build_cache())
.mount("/", routes![ .mount("/", routes![
routes::pdf::render_pdf_cv, routes::pdf::render_pdf_cv,
routes::pdf::render_html_cv routes::pdf::render_html_cv

View File

@ -4,11 +4,13 @@ use nanobyte_opentelemetry::rocket::{TracingSpan, OtelReqwestClient};
use nanobyte_tera::l18n::LanguageDescription; use nanobyte_tera::l18n::LanguageDescription;
use ovlach_data::cv::data::CV; use ovlach_data::cv::data::CV;
use rocket::fs::NamedFile; use rocket::fs::NamedFile;
use rocket::serde::json::json;
use ::rocket::{State, http::Status}; use ::rocket::{State, http::Status};
use ::rocket::get; use ::rocket::get;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use tera::Context; use tera::Context;
use tracing::{info_span, error, debug, Instrument}; use tracing::{info_span, error, debug, Instrument};
use crate::tools::cache::NanoCache;
use crate::{chromium::rocket::BrowserHolder, tools::{tera::NanoTera, pdf::PdfStream, rocket::RequestLanguage}, services::cv::fetch_cv_data_from_backend, CVBackendConfig}; use crate::{chromium::rocket::BrowserHolder, tools::{tera::NanoTera, pdf::PdfStream, rocket::RequestLanguage}, services::cv::fetch_cv_data_from_backend, CVBackendConfig};
// TODO: request-id // TODO: request-id
@ -56,19 +58,32 @@ fn render_template(template_name: &str, file: &NamedTempFile, tera: NanoTera, cv
} }
} }
#[get("/cv/<username>/<language>/output.pdf")] #[allow(clippy::too_many_arguments)]
#[get("/cv/<username>/<language>/output.pdf?<force_rebuild>")]
pub async fn render_pdf_cv(username: &str, tera: NanoTera, tracing: TracingSpan, request_client: OtelReqwestClient, pub async fn render_pdf_cv(username: &str, tera: NanoTera, tracing: TracingSpan, request_client: OtelReqwestClient,
cv_config: &State<CVBackendConfig>, language: RequestLanguage, browser: BrowserHolder) -> Result<PdfStream, Status> { cv_config: &State<CVBackendConfig>, language: RequestLanguage, browser: BrowserHolder, cache: &State<NanoCache<String, PdfStream>>,
force_rebuild: Option<bool>) -> Result<PdfStream, Status> {
async move { async move {
match fetch_cv_data_from_backend(&cv_config.cv_backend_path, &username.to_string(), &request_client.0).await { match fetch_cv_data_from_backend(&cv_config.cv_backend_path, &username.to_string(), &request_client.0).await {
Ok(cv_data) => { Ok(cv_data) => {
// TODO: CV hasher
let data = json!(cv_data).to_string();
match cache.get(&data, force_rebuild).await {
Some(data) => {
Ok(data.clone())
}
None => {
let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap(); let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
render_template("two_column", &file, tera, cv_data, language.language); render_template("two_column", &file, tera, cv_data, language.language);
let span = info_span!("render_pdf", username = username); let span = info_span!("render_pdf", username = username);
let pdf = span.in_scope(||{ let pdf = span.in_scope(||{
generate_pdf(browser.browser, &file) generate_pdf(browser.browser, &file)
}); });
Ok(PdfStream::new(pdf)) let result = PdfStream::new(pdf);
cache.insert(data, result.clone()).await;
Ok(result)
}
}
}, },
Err(e) => { Err(e) => {
error!("Error fetching cv data: {:?}", e); error!("Error fetching cv data: {:?}", e);

81
src/tools/cache.rs Normal file
View File

@ -0,0 +1,81 @@
use std::time::Duration;
use moka::{future::Cache, notification::RemovalCause};
use once_cell::sync::Lazy;
use rocket_prometheus::{prometheus::{IntCounterVec, opts, Error}, PrometheusMetrics};
use std::hash::Hash;
use super::pdf::PdfStream;
static CACHE_EVICTION_COUNT: Lazy<IntCounterVec> = Lazy::new(||
IntCounterVec::new(opts!(format!("{}_{}", std::env::var("ROCKET_PROMETHEUS_NAMESPACE").unwrap_or("app".to_string()), "cache_eviction"), "Count of cache eviction"), &["cause"])
.expect("Could not create NAME_COUNTER")
);
static CACHE_HITS: Lazy<IntCounterVec> = Lazy::new(||
IntCounterVec::new(opts!(format!("{}_{}", std::env::var("ROCKET_PROMETHEUS_NAMESPACE").unwrap_or("app".to_string()), "cache_hists"), "Count of cache eviction"), &[]).expect("Could not create NAME_COUNTER")
);
static CACHE_MISSES: Lazy<IntCounterVec> = Lazy::new(||
IntCounterVec::new(opts!(format!("{}_{}", std::env::var("ROCKET_PROMETHEUS_NAMESPACE").unwrap_or("app".to_string()), "cache_misses"), "Count of cache eviction"), &[]).expect("Could not create NAME_COUNTER")
);
pub fn cache_prometheus_metrics(metrics: &PrometheusMetrics) -> Result<(), Error> {
metrics.registry().register(Box::new(CACHE_EVICTION_COUNT.clone()))?;
metrics.registry().register(Box::new(CACHE_HITS.clone()))?;
metrics.registry().register(Box::new(CACHE_MISSES.clone()))?;
Ok(())
}
pub fn build_cache() -> NanoCache<String, PdfStream> {
let cache = Cache::builder()
.time_to_live(Duration::from_secs(60 * 60))
.eviction_listener(|_k, _v, cause| {
let cause_string = match cause {
RemovalCause::Expired => "expired",
RemovalCause::Explicit => "explicit",
RemovalCause::Replaced => "replaced",
RemovalCause::Size => "size",
};
CACHE_EVICTION_COUNT.with_label_values(&[cause_string]).inc();
})
.build();
NanoCache::new(cache)
}
pub struct NanoCache<K, V> {
inner: Cache<K, V>,
}
impl<K, V> NanoCache<K, V>
where
K: Hash + Eq + Send + Sync + 'static,
V: Clone + Send + Sync + 'static
{
pub fn new(cache: Cache<K, V>) -> Self {
Self {
inner: cache
}
}
pub async fn get(&self, key: &K, force_invalidate: Option<bool>) -> Option<V> {
if force_invalidate == Some(true) {
self.inner.invalidate(key).await;
CACHE_MISSES.with_label_values(&[]).inc();
return None;
}
let result = self.inner.get(key).await;
match result {
Some(_) => CACHE_HITS.with_label_values(&[]).inc(),
None => CACHE_MISSES.with_label_values(&[]).inc(),
}
result
}
pub async fn insert(&self, key: K, value: V) {
self.inner.insert(key, value).await
}
}

View File

@ -1,3 +1,4 @@
pub mod tera; pub mod tera;
pub mod pdf; pub mod pdf;
pub(crate) mod rocket; pub(crate) mod rocket;
pub(crate) mod cache;

View File

@ -3,6 +3,7 @@ use std::io::Cursor;
#[derive(Clone)]
pub struct PdfStream { pub struct PdfStream {
data: Vec<u8>, data: Vec<u8>,
} }