Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rich indexer partial query by args performance issue #4484

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions util/indexer-sync/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ impl IndexerSyncService {
block.number(),
block.hash()
);
indexer.append(&block).expect("append block should be OK");
if let Err(e) = indexer.append(&block) {
error!("Failed to append block: {}. Will attempt to retry.", e);
}
} else {
info!(
"{} rollback {}, {}",
Expand All @@ -178,7 +180,11 @@ impl IndexerSyncService {
}
}
Ok(None) => match self.get_block_by_number(0) {
Some(block) => indexer.append(&block).expect("append block should be OK"),
Some(block) => {
if let Err(e) = indexer.append(&block) {
error!("Failed to append block: {}. Will attempt to retry.", e);
}
}
None => {
error!("CKB node returns an empty genesis block");
break;
Expand Down
3 changes: 2 additions & 1 deletion util/rich-indexer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ In order to run a CKB node with the Rich-Indexer enabled, it is recommended to a

- Processor: 4 core
- RAM: 8 GB
- Disk: Solid State Drive (SSD) to ensure performance

## Quick Start

Expand Down Expand Up @@ -57,7 +58,7 @@ Note that CKB starting options `--indexer` and `--rich-indexer` can only be used

## Enabling Rich Indexer with PostgreSQL

To enable PostgreSQL, you must first set up a functional PostgreSQL service on your own. Please refer to [Server Administration](https://www.postgresql.org/docs/16/admin.html) for guidance. It is recommended to install version 12 or above.
To enable PostgreSQL, you must first set up a functional PostgreSQL service on your own. Please refer to [Server Administration](https://www.postgresql.org/docs/16/admin.html) for guidance. It is recommended to install version 16 or above.

For hardware with 4 cores and 8 GB of RAM, it is recommended to make the following two configuration parameter adjustments in PostgreSQL to achieve optimal query performance.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,18 @@ impl AsyncRichIndexerHandle {
.bind(search_key.script.args.as_bytes())
.bind(get_binary_upper_boundary(search_key.script.args.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(search_key.script.args.as_bytes());
}
Some(IndexerSearchMode::Partial) => match self.store.db_driver {
DBDriver::Postgres => {
let new_args = escape_and_wrap_for_postgres_like(&search_key.script.args);
query = query.bind(new_args);
}
DBDriver::Sqlite => {
query = query.bind(search_key.script.args.as_bytes());
}
},
}
if let Some(filter) = search_key.filter.as_ref() {
if let Some(script) = filter.script.as_ref() {
Expand All @@ -190,9 +199,18 @@ impl AsyncRichIndexerHandle {
.bind(data.as_bytes())
.bind(get_binary_upper_boundary(data.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(data.as_bytes());
}
Some(IndexerSearchMode::Partial) => match self.store.db_driver {
DBDriver::Postgres => {
let new_data = escape_and_wrap_for_postgres_like(data);
query = query.bind(new_data);
}
DBDriver::Sqlite => {
query = query.bind(data.as_bytes());
}
},
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,18 @@ impl AsyncRichIndexerHandle {
.bind(search_key.script.args.as_bytes())
.bind(get_binary_upper_boundary(search_key.script.args.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(search_key.script.args.as_bytes());
}
Some(IndexerSearchMode::Partial) => match self.store.db_driver {
DBDriver::Postgres => {
let new_args = escape_and_wrap_for_postgres_like(&search_key.script.args);
query = query.bind(new_args);
}
DBDriver::Sqlite => {
query = query.bind(search_key.script.args.as_bytes());
}
},
}
if let Some(filter) = search_key.filter.as_ref() {
if let Some(script) = filter.script.as_ref() {
Expand All @@ -150,9 +159,18 @@ impl AsyncRichIndexerHandle {
.bind(data.as_bytes())
.bind(get_binary_upper_boundary(data.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(data.as_bytes());
}
Some(IndexerSearchMode::Partial) => match self.store.db_driver {
DBDriver::Postgres => {
let new_data = escape_and_wrap_for_postgres_like(data);
query = query.bind(new_data);
}
DBDriver::Sqlite => {
query = query.bind(data.as_bytes());
}
},
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,18 @@ pub async fn get_tx_with_cell(
.bind(search_key.script.args.as_bytes())
.bind(get_binary_upper_boundary(search_key.script.args.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(search_key.script.args.as_bytes());
}
Some(IndexerSearchMode::Partial) => match db_driver {
DBDriver::Postgres => {
let new_args = escape_and_wrap_for_postgres_like(&search_key.script.args);
query = query.bind(new_args);
}
DBDriver::Sqlite => {
query = query.bind(search_key.script.args.as_bytes());
}
},
}
if let Some(filter) = search_key.filter.as_ref() {
if let Some(script) = filter.script.as_ref() {
Expand All @@ -215,9 +224,18 @@ pub async fn get_tx_with_cell(
.bind(data.as_bytes())
.bind(get_binary_upper_boundary(data.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(data.as_bytes());
}
Some(IndexerSearchMode::Partial) => match db_driver {
DBDriver::Postgres => {
let new_data = escape_and_wrap_for_postgres_like(data);
query = query.bind(new_data);
}
DBDriver::Sqlite => {
query = query.bind(data.as_bytes());
}
},
}
}
}
Expand Down Expand Up @@ -326,9 +344,18 @@ pub async fn get_tx_with_cells(
.bind(search_key.script.args.as_bytes())
.bind(get_binary_upper_boundary(search_key.script.args.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(search_key.script.args.as_bytes());
}
Some(IndexerSearchMode::Partial) => match db_driver {
DBDriver::Postgres => {
let new_args = escape_and_wrap_for_postgres_like(&search_key.script.args);
query = query.bind(new_args);
}
DBDriver::Sqlite => {
query = query.bind(search_key.script.args.as_bytes());
}
},
}
if let Some(filter) = search_key.filter.as_ref() {
if let Some(script) = filter.script.as_ref() {
Expand All @@ -347,9 +374,18 @@ pub async fn get_tx_with_cells(
.bind(data.as_bytes())
.bind(get_binary_upper_boundary(data.as_bytes()));
}
Some(IndexerSearchMode::Exact) | Some(IndexerSearchMode::Partial) => {
Some(IndexerSearchMode::Exact) => {
query = query.bind(data.as_bytes());
}
Some(IndexerSearchMode::Partial) => match db_driver {
DBDriver::Postgres => {
let new_data = escape_and_wrap_for_postgres_like(data);
query = query.bind(new_data);
}
DBDriver::Sqlite => {
query = query.bind(data.as_bytes());
}
},
}
}
}
Expand Down Expand Up @@ -566,10 +602,7 @@ fn build_filter(
Some(IndexerSearchMode::Partial) => {
match db_driver {
DBDriver::Postgres => {
query_builder.and_where(format!(
"position(${} in output.data) > 0",
param_index
));
query_builder.and_where(format!("output.data LIKE ${}", param_index));
}
DBDriver::Sqlite => {
query_builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use ckb_app_config::DBDriver;
use ckb_indexer_sync::{Error, Pool};
use ckb_jsonrpc_types::{
IndexerRange, IndexerScriptType, IndexerSearchKey, IndexerSearchKeyFilter, IndexerSearchMode,
IndexerTip,
IndexerTip, JsonBytes,
};
use ckb_types::H256;
use num_bigint::BigUint;
Expand Down Expand Up @@ -99,7 +99,7 @@ fn build_query_script_sql(
Some(IndexerSearchMode::Partial) => {
match db_driver {
DBDriver::Postgres => {
query_builder.and_where(format!("position(${} in args) > 0", param_index));
query_builder.and_where(format!("args LIKE ${}", param_index));
}
DBDriver::Sqlite => {
query_builder.and_where(format!("instr(args, ${}) > 0", param_index));
Expand Down Expand Up @@ -140,7 +140,7 @@ fn build_query_script_id_sql(
Some(IndexerSearchMode::Partial) => {
match db_driver {
DBDriver::Postgres => {
query_builder.and_where(format!("position(${} in args) > 0", param_index));
query_builder.and_where(format!("args LIKE ${}", param_index));
}
DBDriver::Sqlite => {
query_builder.and_where(format!("instr(args, ${}) > 0", param_index));
Expand Down Expand Up @@ -229,10 +229,7 @@ fn build_cell_filter(
Some(IndexerSearchMode::Partial) => {
match db_driver {
DBDriver::Postgres => {
query_builder.and_where(format!(
"position(${} in output.data) > 0",
param_index
));
query_builder.and_where(format!("output.data LIKE ${}", param_index));
}
DBDriver::Sqlite => {
query_builder
Expand Down Expand Up @@ -316,6 +313,35 @@ pub(crate) fn convert_max_values_in_search_filter(
})
}

/// Escapes special characters and wraps data with '%' for PostgreSQL LIKE queries.
///
/// This function escapes the characters '%', '\' and '_' in the input `JsonBytes` by prefixing them with '\'.
/// It then wraps the processed data with '%' at both the start and end for use in PostgreSQL LIKE queries.
/// Note: This function is not suitable for SQLite queries if the data contains NUL characters (0x00),
/// as SQLite treats NUL as the end of the string.
fn escape_and_wrap_for_postgres_like(data: &JsonBytes) -> Vec<u8> {
// 0x5c is the default escape character '\'
// 0x25 is the '%' wildcard
// 0x5f is the '_' wildcard

let mut new_data: Vec<u8> = data
.as_bytes()
.iter()
.flat_map(|&b| {
if b == 0x25 || b == 0x5c || b == 0x5f {
vec![0x5c, b]
} else {
vec![b]
}
})
.collect();

new_data.insert(0, 0x25); // Start with %
new_data.push(0x25); // End with %

new_data
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion util/rich-indexer/src/tests/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,7 @@ async fn output_data_filter_mode_rpc() {
.unwrap();
assert_eq!(1, cells.objects.len(),);

// test get_cells_capacity rpc with output_data Partial search mode
// test get_cells_capacity rpc with output_data Prefix search mode
let cells = rpc
.get_cells_capacity(IndexerSearchKey {
script: lock_script11.clone().into(),
Expand Down
Loading