|
1 | | -use jni::objects::{JClass, JObjectArray, JString}; |
2 | | -use jni::sys::jlong; |
| 1 | +use jni::objects::{JByteArray, JClass, JObjectArray, JString}; |
| 2 | +use jni::sys::{jbyteArray, jint, jlong}; |
3 | 3 | use jni::{JNIEnv}; |
4 | 4 | use crate::custom_cache_manager::CustomCacheManager; |
5 | 5 | use crate::util::{parse_string_arr}; |
6 | 6 | use crate::cache; |
7 | 7 | use crate::DataFusionRuntime; |
8 | 8 | use datafusion::execution::cache::cache_unit::DefaultFilesMetadataCache; |
9 | 9 | use std::sync::Arc; |
| 10 | +use bytes::Bytes; |
10 | 11 | use vectorized_exec_spi::{log_info, log_error, log_debug}; |
11 | 12 |
|
| 13 | +// Default page cache budget — overridden by Java settings via createCache() |
| 14 | +const DEFAULT_PAGE_CACHE_DISK_BYTES: usize = 10 * 1024 * 1024 * 1024; // 10 GB disk |
| 15 | +const DEFAULT_PAGE_CACHE_DIR: &str = "/tmp/foyer-page-cache"; |
| 16 | + |
| 17 | +/// Parse the eviction_type string for PAGES cache type. |
| 18 | +/// Expected format: "<disk_capacity_bytes>|<disk_dir>" |
| 19 | +/// Falls back to defaults if the string is malformed (e.g. plain "LRU" from old Java code). |
| 20 | +fn parse_page_cache_params(eviction_str: &str) -> (usize, String) { |
| 21 | + if let Some(sep) = eviction_str.find('|') { |
| 22 | + let disk_bytes_str = &eviction_str[..sep]; |
| 23 | + let disk_dir = eviction_str[sep + 1..].to_string(); |
| 24 | + if let Ok(disk_bytes) = disk_bytes_str.parse::<usize>() { |
| 25 | + let dir = if disk_dir.is_empty() { DEFAULT_PAGE_CACHE_DIR.to_string() } else { disk_dir }; |
| 26 | + return (disk_bytes, dir); |
| 27 | + } |
| 28 | + } |
| 29 | + // Fallback: plain eviction type like "LRU" from legacy config |
| 30 | + log_info!( |
| 31 | + "[FOYER-PAGE-CACHE] eviction_type '{}' is not in '<disk_bytes>|<dir>' format; \ |
| 32 | + using defaults: disk={}B, dir={}", |
| 33 | + eviction_str, DEFAULT_PAGE_CACHE_DISK_BYTES, DEFAULT_PAGE_CACHE_DIR |
| 34 | + ); |
| 35 | + (DEFAULT_PAGE_CACHE_DISK_BYTES, DEFAULT_PAGE_CACHE_DIR.to_string()) |
| 36 | +} |
| 37 | + |
12 | 38 | /// Create a CustomCacheManager instance |
13 | 39 | #[no_mangle] |
14 | 40 | pub extern "system" fn Java_org_opensearch_datafusion_jni_NativeBridge_createCustomCacheManager( |
@@ -89,6 +115,25 @@ pub extern "system" fn Java_org_opensearch_datafusion_jni_NativeBridge_createCac |
89 | 115 | manager.set_statistics_cache(stats_cache); |
90 | 116 | log_info!("[CACHE INFO] Successfully created {} cache in CustomCacheManager", cache_type_str); |
91 | 117 | } |
| 118 | + cache::CACHE_TYPE_PAGES => { |
| 119 | + // Create Foyer disk-only page cache — Cache Layer 3. |
| 120 | + // `size_limit` is ignored for PAGES (disk-only, no memory tier). |
| 121 | + // The disk budget and disk directory are passed via eviction_type as |
| 122 | + // "<disk_bytes>|<disk_dir>". |
| 123 | + let (disk_bytes, disk_dir) = parse_page_cache_params(&eviction_type_str); |
| 124 | + log_info!( |
| 125 | + "[FOYER-PAGE-CACHE] creating disk-only page cache: disk={}B, dir={}", |
| 126 | + disk_bytes, disk_dir |
| 127 | + ); |
| 128 | + let page_cache = Arc::new(crate::tiered::foyer_cache::FoyerDiskPageCache::new( |
| 129 | + disk_bytes, |
| 130 | + disk_dir, |
| 131 | + )); |
| 132 | + manager.set_page_cache(page_cache); |
| 133 | + log_info!( |
| 134 | + "[FOYER-PAGE-CACHE] successfully created Foyer disk-only page cache in CustomCacheManager" |
| 135 | + ); |
| 136 | + } |
92 | 137 | _ => { |
93 | 138 | let msg = format!("Invalid cache type: {}", cache_type_str); |
94 | 139 | log_error!("[CACHE ERROR] {}", msg); |
@@ -444,3 +489,128 @@ pub extern "system" fn Java_org_opensearch_datafusion_jni_NativeBridge_cacheMana |
444 | 489 | } |
445 | 490 | } |
446 | 491 | } |
| 492 | + |
| 493 | +// ============================================================================ |
| 494 | +// Foyer page cache JNI operations (Layer 3: Parquet byte range cache) |
| 495 | +// Called by DataFusionPlugin.FoyerCacheProvider implementation to serve |
| 496 | +// PassthroughCacheStrategy → FoyerParquetCacheStrategy in the tiered-storage module. |
| 497 | +// ============================================================================ |
| 498 | + |
| 499 | +/// Look up a cached byte range for a Parquet file. |
| 500 | +/// Returns the cached bytes as a Java byte[], or null on cache miss. |
| 501 | +#[no_mangle] |
| 502 | +pub extern "system" fn Java_org_opensearch_datafusion_jni_NativeBridge_foyerPageCacheGet( |
| 503 | + mut env: JNIEnv, |
| 504 | + _class: JClass, |
| 505 | + runtime_ptr: jlong, |
| 506 | + path: JString, |
| 507 | + start: jint, |
| 508 | + end: jint, |
| 509 | +) -> jbyteArray { |
| 510 | + if runtime_ptr == 0 { |
| 511 | + return std::ptr::null_mut(); |
| 512 | + } |
| 513 | + |
| 514 | + let runtime = unsafe { &*(runtime_ptr as *const DataFusionRuntime) }; |
| 515 | + let path_str: String = match env.get_string(&path) { |
| 516 | + Ok(s) => s.into(), |
| 517 | + Err(_) => return std::ptr::null_mut(), |
| 518 | + }; |
| 519 | + |
| 520 | + let page_cache = match runtime.custom_cache_manager.as_ref().and_then(|m| m.get_page_cache()) { |
| 521 | + Some(c) => c, |
| 522 | + None => return std::ptr::null_mut(), |
| 523 | + }; |
| 524 | + |
| 525 | + // FoyerDiskPageCache.get() is async (disk I/O). Use get_blocking() since JNI is synchronous. |
| 526 | + match page_cache.get_blocking(&path_str, start as usize, end as usize) { |
| 527 | + Some(bytes) => { |
| 528 | + log_debug!( |
| 529 | + "[FOYER-PAGE-CACHE] JNI get HIT: path={}, range={}..{}, size={}B", |
| 530 | + path_str, start, end, bytes.len() |
| 531 | + ); |
| 532 | + match env.byte_array_from_slice(&bytes) { |
| 533 | + Ok(arr) => arr.into_raw(), |
| 534 | + Err(e) => { |
| 535 | + log_debug!("[FOYER-PAGE-CACHE] JNI get: failed to create Java byte[]: {}", e); |
| 536 | + std::ptr::null_mut() |
| 537 | + } |
| 538 | + } |
| 539 | + } |
| 540 | + None => { |
| 541 | + log_debug!( |
| 542 | + "[FOYER-PAGE-CACHE] JNI get MISS: path={}, range={}..{}", |
| 543 | + path_str, start, end |
| 544 | + ); |
| 545 | + std::ptr::null_mut() |
| 546 | + } |
| 547 | + } |
| 548 | +} |
| 549 | + |
| 550 | +/// Store a byte range for a Parquet file in the Foyer page cache. |
| 551 | +#[no_mangle] |
| 552 | +pub extern "system" fn Java_org_opensearch_datafusion_jni_NativeBridge_foyerPageCachePut( |
| 553 | + mut env: JNIEnv, |
| 554 | + _class: JClass, |
| 555 | + runtime_ptr: jlong, |
| 556 | + path: JString, |
| 557 | + start: jint, |
| 558 | + end: jint, |
| 559 | + data: JByteArray, |
| 560 | +) { |
| 561 | + if runtime_ptr == 0 { |
| 562 | + return; |
| 563 | + } |
| 564 | + |
| 565 | + let runtime = unsafe { &*(runtime_ptr as *const DataFusionRuntime) }; |
| 566 | + let path_str: String = match env.get_string(&path) { |
| 567 | + Ok(s) => s.into(), |
| 568 | + Err(e) => { |
| 569 | + log_debug!("[FoyerCache] foyerPageCachePut: failed to convert path: {}", e); |
| 570 | + return; |
| 571 | + } |
| 572 | + }; |
| 573 | + |
| 574 | + let page_cache = match runtime.custom_cache_manager.as_ref().and_then(|m| m.get_page_cache()) { |
| 575 | + Some(c) => c, |
| 576 | + None => return, |
| 577 | + }; |
| 578 | + |
| 579 | + let bytes_vec: Vec<u8> = match env.convert_byte_array(data) { |
| 580 | + Ok(v) => v, |
| 581 | + Err(e) => { |
| 582 | + log_debug!("[FoyerCache] foyerPageCachePut: failed to convert byte array: {}", e); |
| 583 | + return; |
| 584 | + } |
| 585 | + }; |
| 586 | + |
| 587 | + page_cache.put(path_str, start as usize, end as usize, Bytes::from(bytes_vec)); |
| 588 | +} |
| 589 | + |
| 590 | +/// Evict all cached byte ranges for a given Parquet file. |
| 591 | +/// Called when a file is deleted (merged/compacted/tiered out). |
| 592 | +#[no_mangle] |
| 593 | +pub extern "system" fn Java_org_opensearch_datafusion_jni_NativeBridge_foyerPageCacheEvictFile( |
| 594 | + mut env: JNIEnv, |
| 595 | + _class: JClass, |
| 596 | + runtime_ptr: jlong, |
| 597 | + path: JString, |
| 598 | +) { |
| 599 | + if runtime_ptr == 0 { |
| 600 | + return; |
| 601 | + } |
| 602 | + |
| 603 | + let runtime = unsafe { &*(runtime_ptr as *const DataFusionRuntime) }; |
| 604 | + let path_str: String = match env.get_string(&path) { |
| 605 | + Ok(s) => s.into(), |
| 606 | + Err(e) => { |
| 607 | + log_debug!("[FoyerCache] foyerPageCacheEvictFile: failed to convert path: {}", e); |
| 608 | + return; |
| 609 | + } |
| 610 | + }; |
| 611 | + |
| 612 | + if let Some(page_cache) = runtime.custom_cache_manager.as_ref().and_then(|m| m.get_page_cache()) { |
| 613 | + page_cache.evict_file(&path_str); |
| 614 | + log_debug!("[FoyerCache] evicted file from page cache: {}", path_str); |
| 615 | + } |
| 616 | +} |
0 commit comments