global: serialization optimizations for faster responses

This commit is contained in:
nym21
2025-09-20 18:42:15 +02:00
parent 23f6397a97
commit 2c5b502da9
90 changed files with 915 additions and 460 deletions
+2
View File
@@ -26,7 +26,9 @@ vecdb = { workspace = true }
jiff = { workspace = true }
log = { workspace = true }
quick_cache = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sonic-rs = { workspace = true }
tokio = { workspace = true }
tower-http = { version = "0.6.6", features = ["compression-full", "trace"] }
tracing = "0.1.41"
+3 -3
View File
@@ -12,7 +12,7 @@ This crate provides a high-performance HTTP server built on `axum` that exposes
**Key Features:**
- RESTful API for blockchain data queries with flexible filtering
- Multiple output formats: JSON, CSV, TSV, Markdown
- Multiple output formats: JSON, CSV
- Intelligent caching system with ETags and conditional requests
- HTTP compression (Gzip, Brotli, Deflate, Zstd) for bandwidth efficiency
- Static file serving for web interfaces and documentation
@@ -81,7 +81,7 @@ server.serve(true).await?;
**Supported Parameters:**
- `from` / `to`: Range filtering (height, timestamp, date-based)
- `format`: Output format (json, csv, tsv, md)
- `format`: Output format (json, csv)
- Pagination parameters for large datasets
### Address API Response Format
@@ -193,7 +193,7 @@ The server implements intelligent caching:
2. **Parameter Validation**: Query parameter parsing and validation
3. **Data Retrieval**: Interface calls to indexer/computer components
4. **Caching Logic**: ETag generation and cache lookup
5. **Format Conversion**: JSON/CSV/TSV/MD output formatting
5. **Format Conversion**: JSON/CSV output formatting
6. **Compression**: Response compression based on Accept-Encoding
7. **Response**: HTTP response with appropriate headers
+20 -21
View File
@@ -11,13 +11,14 @@ use axum::{
response::{IntoResponse, Redirect, Response},
routing::get,
};
use bitcoin::{Address, Network, Transaction, absolute::LockTime, consensus::Decodable};
use bitcoin::{Address, Network, Transaction, consensus::Decodable};
use bitcoincore_rpc::bitcoin;
use brk_interface::{IdParam, Index, PaginatedIndexParam, PaginationParam, Params, ParamsOpt};
use brk_parser::XORIndex;
use brk_structs::{
AddressBytesHash, AnyAddressDataIndexEnum, Bitcoin, OutputType, Txid, TxidPrefix,
AddressBytesHash, AnyAddressDataIndexEnum, Bitcoin, OutputType, TxIndex, Txid, TxidPrefix,
};
use serde::Serialize;
use serde_json::Number;
use vecdb::{AnyIterableVec, VecIterator};
@@ -32,6 +33,13 @@ pub trait ApiRoutes {
const TO_SEPARATOR: &str = "_to_";
#[derive(Serialize)]
struct TxResponse {
txid: Txid,
index: TxIndex,
tx: Transaction,
}
impl ApiRoutes for Router<AppState> {
fn add_api_routes(self) -> Self {
self.route(
@@ -151,6 +159,7 @@ impl ApiRoutes for Router<AppState> {
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
return "Invalid txid".into_response()
};
let txid = Txid::from(txid);
let prefix = TxidPrefix::from(&txid);
let interface = state.interface;
@@ -162,22 +171,12 @@ impl ApiRoutes for Router<AppState> {
.map(|opt| opt.map(|cow| cow.into_owned())) else {
return "Unknown transaction".into_response();
};
let txid = indexer
.vecs
.txindex_to_txid
.iter()
.unwrap_get_inner(txindex);
let version = indexer
.vecs
.txindex_to_txversion
.iter()
.unwrap_get_inner(txindex);
let rawlocktime = indexer
.vecs
.txindex_to_rawlocktime
.iter()
.unwrap_get_inner(txindex);
let locktime = LockTime::from(rawlocktime);
let parser = interface.parser();
let computer = interface.computer();
@@ -213,14 +212,14 @@ impl ApiRoutes for Router<AppState> {
return "Error decoding transaction".into_response();
};
Json(serde_json::json!({
"txid": txid,
"index": txindex,
"version": version,
"locktime": locktime,
"tx": tx,
}))
.into_response()
let response = TxResponse { txid, index: txindex, tx };
let bytes = sonic_rs::to_vec(&response).unwrap();
Response::builder()
.header("content-type", "application/json")
.body(bytes.into())
.unwrap()
},
),
)
+6 -11
View File
@@ -89,7 +89,7 @@ fn req_to_response_res(
Response::new(Body::from(v))
} else {
match interface.format(vecs, &params.rest)? {
Output::CSV(s) | Output::TSV(s) | Output::MD(s) => {
Output::CSV(s) => {
if let GuardResult::Guard(g) = guard_res {
g.insert(s.clone().into())
.map_err(|_| Error::QuickCacheError)?;
@@ -97,7 +97,7 @@ fn req_to_response_res(
s.into_response()
}
Output::Json(v) => {
let json = serde_json::to_vec(&v)?;
let json = v.to_vec();
if let GuardResult::Guard(g) = guard_res {
g.insert(json.clone().into())
.map_err(|_| Error::QuickCacheError)?;
@@ -115,17 +115,12 @@ fn req_to_response_res(
headers.insert_cache_control_must_revalidate();
match format {
Some(format) => {
Format::CSV => {
headers.insert_content_disposition_attachment();
match format {
Format::CSV => headers.insert_content_type_text_csv(),
Format::MD => headers.insert_content_type_text_plain(),
Format::TSV => headers.insert_content_type_text_tsv(),
Format::JSON => headers.insert_content_type_application_json(),
}
headers.insert_content_type_text_csv()
}
None => headers.insert_content_type_application_json(),
};
Format::JSON => headers.insert_content_type_application_json(),
}
Ok(response)
}
@@ -44,7 +44,6 @@ pub trait HeaderMapExtended {
fn insert_content_type_application_pdf(&mut self);
fn insert_content_type_text_css(&mut self);
fn insert_content_type_text_csv(&mut self);
fn insert_content_type_text_tsv(&mut self);
fn insert_content_type_text_html(&mut self);
fn insert_content_type_text_plain(&mut self);
fn insert_content_type_font_woff2(&mut self);
@@ -192,13 +191,6 @@ impl HeaderMapExtended for HeaderMap {
self.insert(header::CONTENT_TYPE, "text/csv".parse().unwrap());
}
fn insert_content_type_text_tsv(&mut self) {
self.insert(
header::CONTENT_TYPE,
"text/tab-separated-values".parse().unwrap(),
);
}
fn insert_content_type_text_html(&mut self) {
self.insert(header::CONTENT_TYPE, "text/html".parse().unwrap());
}