Skip to content

Repository files navigation

liquid_cache_logo

Crates.io Version docs.rs

Rust CI codecov Codacy Badge ClickBench TPC-H TPC-DS

LiquidCache understands both data and queries.

  • It caches data in an optimized, cache-only data format, so you can keep using existing storage formats without sacrificing performance.
  • It keeps query-relevant data in memory and efficiently spills the rest to SSDs. For example, if a query groups by year, LiquidCache stores only the year in memory and keeps the full timestamp on disk.

Quick start

This quick start uses the core cache API from src/core. Add liquid-cache, arrow, and datafusion to your project dependencies. The example below demonstrates insertion, retrieval, selection pushdown, and predicate pushdown.

use arrow::array::{BooleanArray, UInt64Array};
use arrow::buffer::BooleanBuffer;
use datafusion::logical_expr::Operator;
use datafusion::physical_plan::PhysicalExpr;
use datafusion::physical_plan::expressions::{BinaryExpr, Column, Literal};
use datafusion::scalar::ScalarValue;
use liquid_cache::cache::{EntryID, LiquidCacheBuilder};
use std::sync::Arc;

tokio_test::block_on(async {
    let cache = LiquidCacheBuilder::new().build().await;
    let entry_id = EntryID::from(1);
    let values = Arc::new(UInt64Array::from(vec![10, 11, 12, 13, 14, 15]));

    // 1) insert
    cache.insert(entry_id, values.clone()).await;

    // 2) get
    let all_rows = cache.get(&entry_id).await.expect("entry should exist");

    // 3) get filtered (selection pushdown): keep rows 0, 2, 4
    let selection = BooleanBuffer::from(vec![true, false, true, false, true, false]);
    let selected_rows = cache
        .get(&entry_id)
        .with_selection(&selection)
        .await
        .expect("entry should exist");

    // 4) get with predicate pushdown: col > 12
    let predicate: Arc<dyn PhysicalExpr> = Arc::new(BinaryExpr::new(
        Arc::new(Column::new("col", 0)),
        Operator::Gt,
        Arc::new(Literal::new(ScalarValue::UInt64(Some(12)))),
    ));
    let predicate_mask = cache
        .eval_predicate(&entry_id, &predicate)
        .await
        .expect("entry should exist")
        .expect("predicate should be evaluated in cache");

    // Conceptual expectations:
    assert_eq!(all_rows.as_ref(), values.as_ref()); // [10, 11, 12, 13, 14, 15]
    assert_eq!(selected_rows.as_ref(), &UInt64Array::from(vec![10, 12, 14]));
    assert_eq!(
        predicate_mask,
        BooleanArray::from(vec![
        Some(false),
        Some(false),
        Some(false),
        Some(true),
        Some(true),
        Some(true),
        ]),
    );
});

Performance troubleshooting

LiquidCache uses direct I/O

By default, LiquidCache bypasses the OS page cache using O_DIRECT on Linux and F_NOCACHE on macOS. This avoids double-caching and bounds memory usage.

This also means LiquidCache can appear slower than other caches when most of the data fits in the OS page cache, not a realistic scenario in production.

Using LiquidCache with DataFusion

LiquidCache requires a few non-default DataFusion configurations:

With ListingTable:

let (ctx, _) = LiquidCacheLocalBuilder::new().build(config).await?;

let listing_options = ParquetReadOptions::default()
    .to_listing_options(&ctx.copied_config(), ctx.copied_table_options());
ctx.register_listing_table("default", &table_path, listing_options, None, None)
    .await?;

Or register Parquet directly:

let (ctx, _) = LiquidCacheLocalBuilder::new().build(config).await?;
ctx.register_parquet("default", "examples/nano_hits.parquet", Default::default())
    .await?;

Disable background transcoding

For performance testing, disable background transcoding:

let (ctx, _) = LiquidCacheLocalBuilder::new()
    .with_eviction_policy(Box::new(Evict))
    .build(config)
    .await?;

x86-64 optimization

LiquidCache includes specific x86-64 optimizations. On ARM platforms such as Apple Silicon, it uses fallback implementations. Contributions are welcome.

Development

See dev/README.md.

Benchmark

See benchmark/README.md.

FAQ

Is LiquidCache production-ready?

Almost. LiquidCache began as a research project exploring new approaches to cost-effective caching. Like most research projects, it needs time to mature, and we welcome your help.

How can I contribute?

  • We use LLMs to help write code. Please have an LLM review your code before submitting it.
  • Be accountable for the code you propose, and help maintainers be accountable for the code they merge.
  • A PR should take no more than 10 minutes to review, as measured by cognitive burden rather than lines of code.

Who is behind LiquidCache?

LiquidCache grew out of Xiangpeng Hao's PhD dissertation, which was generously supported by SpiralDB, InfluxData, Bauplan, and taxpayer funding from the state of Wisconsin and the federal government.

LiquidCache is and will remain open source and free to use.

License

Apache License 2.0

Releases

Used by

Contributors

Languages