Skip to content

Commit

Permalink
feat: configure http cache size through upstream setting (#1988)
Browse files Browse the repository at this point in the history
Co-authored-by: Tushar Mathur <[email protected]>
  • Loading branch information
laststylebender14 and tusharmath authored May 22, 2024
1 parent 677ecad commit b048f7c
Show file tree
Hide file tree
Showing 71 changed files with 190 additions and 99 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ The below file is a standard `.graphQL` file, with a few additions such as `@ser
```graphql
schema
@server(port: 8000, hostname: "0.0.0.0")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) {
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42) {
query: Query
}

Expand Down
2 changes: 1 addition & 1 deletion benches/http_execute_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub fn benchmark_http_execute_method(c: &mut Criterion) {
let tokio_runtime = tokio::runtime::Runtime::new().unwrap();

let mut blueprint = Blueprint::default();
blueprint.upstream.http_cache = true; // allow http caching for bench test.
blueprint.upstream.http_cache = 42; // allow http caching for bench test.
let native_http = NativeHttp::init(&blueprint.upstream, &blueprint.telemetry);
let request_url = String::from("http://jsonplaceholder.typicode.com/users");

Expand Down
4 changes: 2 additions & 2 deletions benches/impl_path_string_for_evaluation_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ impl Http {

let mut client = ClientBuilder::new(builder.build().expect("Failed to build client"));

if upstream.http_cache {
if upstream.http_cache > 0 {
client = client.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: HttpCacheManager::default(),
manager: HttpCacheManager::new(upstream.http_cache),
options: HttpCacheOptions::default(),
}))
}
Expand Down
2 changes: 1 addition & 1 deletion examples/auth.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42)
@link(id: "auth-basic", type: Htpasswd, src: ".htpasswd")
@link(id: "auth-jwt", type: Jwks, src: ".jwks") {
query: Query
Expand Down
2 changes: 1 addition & 1 deletion examples/graphql-composition.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

schema
@server(port: 8001, queryValidation: false, hostname: "0.0.0.0")
@upstream(baseURL: "http://localhost:8000/graphql", httpCache: true, batch: {delay: 1}) {
@upstream(baseURL: "http://localhost:8000/graphql", httpCache: 42, batch: {delay: 1}) {
query: Query
}

Expand Down
2 changes: 1 addition & 1 deletion examples/grpc-reflection.graphql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# for test upstream server see [repo](https://github.com/tailcallhq/tailcall/tree/main/tailcall-upstream-grpc)
schema
@server(port: 8000)
@upstream(baseURL: "http://localhost:50051", httpCache: true, batch: {delay: 10})
@upstream(baseURL: "http://localhost:50051", httpCache: 42, batch: {delay: 10})
@link(src: "http://localhost:50051", type: Grpc) {
query: Query
}
Expand Down
2 changes: 1 addition & 1 deletion examples/grpc.graphql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# for test upstream server see [repo](https://github.com/tailcallhq/rust-grpc)
schema
@server(port: 8000)
@upstream(baseURL: "http://localhost:50051", httpCache: true, batch: {delay: 10})
@upstream(baseURL: "http://localhost:50051", httpCache: 42, batch: {delay: 10})
@link(id: "news", src: "../tailcall-fixtures/fixtures/protobuf/news.proto", type: Protobuf) {
query: Query
}
Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000, headers: {cors: {allowOrigins: ["*"], allowHeaders: ["*"], allowMethods: [POST, GET, OPTIONS]}})
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true, batch: {delay: 100}) {
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) {
query: Query
}

Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"upstream": {
"baseURL": "http://jsonplaceholder.typicode.com",
"httpCache": true
"httpCache": 42
},
"schema": {
"query": "Query"
Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ server:
port: 8000
upstream:
baseURL: http://jsonplaceholder.typicode.com
httpCache: true
httpCache: 42
schema:
query: Query
types:
Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder_batch.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true, batch: {delay: 1, maxSize: 1000}) {
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 1, maxSize: 1000}) {
query: Query
}

Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder_batch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
},
"upstream": {
"baseURL": "http://jsonplaceholder.typicode.com",
"httpCache": true,
"httpCache": 42,
"batch": {
"maxSize": 1000,
"delay": 1,
Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder_batch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ server:
port: 8000
upstream:
baseURL: http://jsonplaceholder.typicode.com
httpCache: true
httpCache: 42
batch:
maxSize: 1000
delay: 1
Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder_http_2.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ schema
@server(port: 3000, queryValidation: false, hostname: "0.0.0.0", version: HTTP2)
@link(type: Cert, src: "./example.crt")
@link(type: Key, src: "./example.key")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) {
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42) {
query: Query
}

Expand Down
2 changes: 1 addition & 1 deletion examples/jsonplaceholder_script.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000, hostname: "0.0.0.0")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42)
@link(type: Script, src: "scripts/echo.js") {
query: Query
}
Expand Down
2 changes: 1 addition & 1 deletion examples/rest-api.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000, hostname: "0.0.0.0")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42)
@link(type: Operation, src: "operations/routes.graphql") {
query: Query
}
Expand Down
2 changes: 1 addition & 1 deletion examples/telemetry-otlp.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000, hostname: "0.0.0.0")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42)
@link(type: Operation, src: "operations/routes.graphql")
@link(id: "news", src: "../tailcall-fixtures/fixtures/protobuf/news.proto", type: Protobuf)
@telemetry(
Expand Down
2 changes: 1 addition & 1 deletion examples/telemetry-prometheus.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000, hostname: "0.0.0.0")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42)
@telemetry(export: {prometheus: {path: "/metrics"}}) {
query: Query
}
Expand Down
2 changes: 1 addition & 1 deletion examples/telemetry-stdout.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000, hostname: "0.0.0.0")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42)
@telemetry(export: {stdout: {pretty: true}}) {
query: Query
}
Expand Down
8 changes: 4 additions & 4 deletions generated/.tailcallrc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,11 @@ directive @upstream(
"""
http2Only: Boolean
"""
Activating this enables Tailcall's HTTP caching, adhering to the [HTTP Caching RFC](https://tools.ietf.org/html/rfc7234),
to enhance performance by minimizing redundant data fetches. Defaults to `false`
if unspecified.
Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching
RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant
data fetches. Defaults to `0` if unspecified.
"""
httpCache: Boolean
httpCache: Int
"""
The time in seconds between each keep-alive message sent to maintain the connection.
"""
Expand Down
8 changes: 5 additions & 3 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1366,11 +1366,13 @@
]
},
"httpCache": {
"description": "Activating this enables Tailcall's HTTP caching, adhering to the [HTTP Caching RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant data fetches. Defaults to `false` if unspecified.",
"description": "Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant data fetches. Defaults to `0` if unspecified.",
"type": [
"boolean",
"integer",
"null"
]
],
"format": "uint64",
"minimum": 0.0
},
"keepAliveInterval": {
"description": "The time in seconds between each keep-alive message sent to maintain the connection.",
Expand Down
78 changes: 69 additions & 9 deletions src/cli/runtime/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ impl NativeHttp {

let mut client = ClientBuilder::new(builder.build().expect("Failed to build client"));

if upstream.http_cache {
if upstream.http_cache > 0 {
client = client.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: HttpCacheManager::default(),
manager: HttpCacheManager::new(upstream.http_cache),
options: HttpCacheOptions::default(),
}))
}
Expand Down Expand Up @@ -190,13 +190,20 @@ mod tests {
use tokio;

use super::*;
use crate::core::http::Response;

fn start_mock_server() -> httpmock::MockServer {
httpmock::MockServer::start()
}

async fn make_request(request_url: &str, native_http: &NativeHttp) -> Response<Bytes> {
let request = reqwest::Request::new(Method::GET, request_url.parse().unwrap());
let result = native_http.execute(request).await;
result.unwrap()
}

#[tokio::test]
async fn test_native_http_get_request() {
async fn test_native_http_get_request_without_cache() {
let server = start_mock_server();

let header_serv = server.mock(|when, then| {
Expand All @@ -208,17 +215,70 @@ mod tests {
let port = server.port();
// Build a GET request to the mock server
let request_url = format!("http://localhost:{}/test", port);
let request = reqwest::Request::new(Method::GET, request_url.parse().unwrap());
let response = make_request(&request_url, &native_http).await;

// Execute the request
let result = native_http.execute(request).await;
// Assert the response is as expected
assert_eq!(response.status, reqwest::StatusCode::OK);
assert_eq!(response.body, Bytes::from("Hello"));
assert!(response.headers.get("x-cache-lookup").is_none());

let request_url = format!("http://localhost:{}/test", port);
let response = make_request(&request_url, &native_http).await;

// Assert the response is as expected
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.status, reqwest::StatusCode::OK);
assert_eq!(response.body, Bytes::from("Hello"));
assert!(response.headers.get("x-cache-lookup").is_none());

header_serv.assert_hits(2);
}

#[tokio::test]
async fn test_native_http_get_request_with_cache() {
let server = start_mock_server();

server.mock(|when, then| {
when.method(httpmock::Method::GET).path("/test-1");
then.status(200).body("Hello");
});

server.mock(|when, then| {
when.method(httpmock::Method::GET).path("/test-2");
then.status(200).body("Hello");
});

server.mock(|when, then| {
when.method(httpmock::Method::GET).path("/test-3");
then.status(200).body("Hello");
});

let upstream = Upstream { http_cache: 2, ..Default::default() };
let native_http = NativeHttp::init(&upstream, &Default::default());
let port = server.port();

let url1 = format!("http://localhost:{}/test-1", port);
let resp = make_request(&url1, &native_http).await;
assert_eq!(resp.headers.get("x-cache-lookup").unwrap(), "MISS");

let resp = make_request(&url1, &native_http).await;
assert_eq!(resp.headers.get("x-cache-lookup").unwrap(), "HIT");

let url2 = format!("http://localhost:{}/test-2", port);
let resp = make_request(&url2, &native_http).await;
assert_eq!(resp.headers.get("x-cache-lookup").unwrap(), "MISS");

let resp = make_request(&url2, &native_http).await;
assert_eq!(resp.headers.get("x-cache-lookup").unwrap(), "HIT");

// now cache is full, let's make 3rd request and cache it and evict url1.
let url3 = format!("http://localhost:{}/test-3", port);
let resp = make_request(&url3, &native_http).await;
assert_eq!(resp.headers.get("x-cache-lookup").unwrap(), "MISS");

let resp = make_request(&url3, &native_http).await;
assert_eq!(resp.headers.get("x-cache-lookup").unwrap(), "HIT");

header_serv.assert();
let resp = make_request(&url1, &native_http).await;
assert_eq!(resp.headers.get("x-cache-lookup").unwrap(), "MISS");
}
}
4 changes: 2 additions & 2 deletions src/core/blueprint/upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct Upstream {
pub user_agent: String,
pub allowed_headers: BTreeSet<String>,
pub base_url: Option<String>,
pub http_cache: bool,
pub http_cache: u64,
pub batch: Option<Batch>,
pub http2_only: bool,
pub dedupe: bool,
Expand Down Expand Up @@ -78,7 +78,7 @@ impl TryFrom<&ConfigModule> for Upstream {
user_agent: (config_upstream).get_user_agent(),
allowed_headers,
base_url,
http_cache: (config_upstream).get_enable_http_cache(),
http_cache: (config_upstream).get_http_cache_size(),
batch,
http2_only: (config_upstream).get_http_2_only(),
dedupe: (config_upstream).get_dedupe(),
Expand Down
8 changes: 4 additions & 4 deletions src/core/config/upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ pub struct Upstream {
pub connect_timeout: Option<u64>,

#[serde(default, skip_serializing_if = "is_default")]
/// Activating this enables Tailcall's HTTP caching, adhering to the [HTTP Caching RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant data fetches. Defaults to `false` if unspecified.
pub http_cache: Option<bool>,
/// Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant data fetches. Defaults to `0` if unspecified.
pub http_cache: Option<u64>,

#[setters(strip_option)]
#[serde(rename = "http2Only", default, skip_serializing_if = "is_default")]
Expand Down Expand Up @@ -172,8 +172,8 @@ impl Upstream {
.clone()
.unwrap_or("Tailcall/1.0".to_string())
}
pub fn get_enable_http_cache(&self) -> bool {
self.http_cache.unwrap_or(false)
pub fn get_http_cache_size(&self) -> u64 {
self.http_cache.unwrap_or(0)
}
pub fn get_allowed_headers(&self) -> BTreeSet<String> {
self.allowed_headers.clone().unwrap_or_default()
Expand Down
4 changes: 2 additions & 2 deletions src/core/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ pub mod test {

let mut client = ClientBuilder::new(builder.build().expect("Failed to build client"));

if upstream.http_cache {
if upstream.http_cache > 0 {
client = client.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: HttpCacheManager::default(),
manager: HttpCacheManager::new(upstream.http_cache),
options: HttpCacheOptions::default(),
}))
}
Expand Down
2 changes: 1 addition & 1 deletion tailcall-fixtures/fixtures/configs/user-posts.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema
@server(port: 8000, hostname: "0.0.0.0")
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) {
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42) {
query: Query
}

Expand Down
3 changes: 3 additions & 0 deletions tailcall-http-cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ publish = false

[dependencies]
http-cache-reqwest = { version = "0.13.0", default-features = false, features = ["manager-moka"] }
moka = { version = "0.12.7", default-features = false, features = [
"future",
]}
http-cache-semantics = { version = "1.0.1", default-features = false, features = ["with_serde", "reqwest"]}
serde = "1.0.202"
async-trait = "0.1.80"
Expand Down
Loading

1 comment on commit b048f7c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running 30s test @ http://localhost:8000/graphql

4 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev
Latency 6.75ms 3.23ms 120.83ms 75.30%
Req/Sec 3.76k 148.53 4.32k 90.00%

448698 requests in 30.01s, 2.25GB read

Requests/sec: 14950.32

Transfer/sec: 76.74MB

Please sign in to comment.