Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Include named queries from rescore contexts in matched_queries array ([#18697](https://github.com/opensearch-project/OpenSearch/pull/18697))
- Add the configurable limit on rule cardinality ([#18663](https://github.com/opensearch-project/OpenSearch/pull/18663))
- [Experimental] Start in "clusterless" mode if a clusterless ClusterPlugin is loaded ([#18479](https://github.com/opensearch-project/OpenSearch/pull/18479))
- [Star-Tree] Add star-tree search related stats ([#18707](https://github.com/opensearch-project/OpenSearch/pull/18707))

### Changed
- Update Subject interface to use CheckedRunnable ([#18570](https://github.com/opensearch-project/OpenSearch/issues/18570))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,111 @@
"Help":
- skip:
version: " - 2.13.99"
reason: search idle reactivate count total is only added in 3.0.0
version: " - 3.1.99"
reason: star-tree search stats is only added in 3.2.0
features: node_selector
- do:
cat.shards:
help: true
node_selector:
version: "2.14.0 - "
version: "3.2.0 - "

- match:
$body: |
/^ index .+ \n
shard .+ \n
prirep .+ \n
state .+ \n
docs .+ \n
store .+ \n
ip .+ \n
id .+ \n
node .+ \n
sync_id .+ \n
unassigned.reason .+ \n
unassigned.at .+ \n
unassigned.for .+ \n
unassigned.details .+ \n
recoverysource.type .+ \n
completion.size .+ \n
fielddata.memory_size .+ \n
fielddata.evictions .+ \n
query_cache.memory_size .+ \n
query_cache.evictions .+ \n
flush.total .+ \n
flush.total_time .+ \n
get.current .+ \n
get.time .+ \n
get.total .+ \n
get.exists_time .+ \n
get.exists_total .+ \n
get.missing_time .+ \n
get.missing_total .+ \n
indexing.delete_current .+ \n
indexing.delete_time .+ \n
indexing.delete_total .+ \n
indexing.index_current .+ \n
indexing.index_time .+ \n
indexing.index_total .+ \n
indexing.index_failed .+ \n
merges.current .+ \n
merges.current_docs .+ \n
merges.current_size .+ \n
merges.total .+ \n
merges.total_docs .+ \n
merges.total_size .+ \n
merges.total_time .+ \n
refresh.total .+ \n
refresh.time .+ \n
refresh.external_total .+ \n
refresh.external_time .+ \n
refresh.listeners .+ \n
search.fetch_current .+ \n
search.fetch_time .+ \n
search.fetch_total .+ \n
search.open_contexts .+ \n
search.query_current .+ \n
search.query_time .+ \n
search.query_total .+ \n
search.concurrent_query_current .+ \n
search.concurrent_query_time .+ \n
search.concurrent_query_total .+ \n
search.concurrent_avg_slice_count .+ \n
search.startree_query_current .+ \n
search.startree_query_time .+ \n
search.startree_query_total .+ \n
search.scroll_current .+ \n
search.scroll_time .+ \n
search.scroll_total .+ \n
search.point_in_time_current .+ \n
search.point_in_time_time .+ \n
search.point_in_time_total .+ \n
search.search_idle_reactivate_count_total .+ \n
segments.count .+ \n
segments.memory .+ \n
segments.index_writer_memory .+ \n
segments.version_map_memory .+ \n
segments.fixed_bitset_memory .+ \n
seq_no.max .+ \n
seq_no.local_checkpoint .+ \n
seq_no.global_checkpoint .+ \n
warmer.current .+ \n
warmer.total .+ \n
warmer.total_time .+ \n
path.data .+ \n
path.state .+ \n
docs.deleted .+ \n
$/
---
"Help from 2.14.0 to 3.0.99":
- skip:
version: " - 2.13.99, 3.2.0 - "
reason: search idle reactivate count total is only added in 3.0.0
features: node_selector
- do:
cat.shards:
help: true
node_selector:
version: "2.14.0 - 3.1.99"

- match:
$body: |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.search.stats;

import org.opensearch.action.admin.indices.forcemerge.ForceMergeRequest;
import org.opensearch.action.admin.indices.stats.IndicesStatsResponse;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.index.search.stats.SearchStats;
import org.opensearch.search.aggregations.AggregationBuilders;
import org.opensearch.search.aggregations.bucket.terms.Terms;
import org.opensearch.search.aggregations.metrics.Sum;
import org.opensearch.test.OpenSearchIntegTestCase;

import static org.opensearch.index.query.QueryBuilders.matchAllQuery;
import static org.opensearch.index.query.QueryBuilders.termQuery;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;

/**
* Integration tests for search statistics related to star-tree.
*/
@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.SUITE)
public class StarTreeSearchStatsIT extends OpenSearchIntegTestCase {

private static final String STARTREE_INDEX_NAME = "test_startree";
private static final String REGULAR_INDEX_NAME = "test_regular";

/**
* This test validates that star-tree specific search stats are correctly reported.
* It creates two indices: one with a star-tree field and one without.
* It then runs queries that should and should not be offloaded to the star-tree
* and verifies the star-tree related stats.
*/
public void testStarTreeQueryStats() throws Exception {
// Create an index with a star-tree field mapping using the composite structure
createIndex(
STARTREE_INDEX_NAME,
Settings.builder()
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
.put("index.composite_index", true)
.put("index.append_only.enabled", true)
.put("index.search.star_tree_index.enabled", true)
.put("index.codec", "default")
.build(),
XContentFactory.jsonBuilder()
.startObject()
.startObject("composite")
.startObject("my_star_tree")
.field("type", "star_tree")
.startObject("config")
.startArray("ordered_dimensions")
.startObject()
.field("name", "product")
.endObject()
.startObject()
.field("name", "size")
.endObject()
.endArray()
.startArray("metrics")
.startObject()
.field("name", "sales")
.field("stats", new String[] { "sum" })
.endObject()
.endArray()
.endObject()
.endObject()
.endObject()
.startObject("properties")
.startObject("product")
.field("type", "keyword")
.endObject()
.startObject("size")
.field("type", "keyword")
.endObject()
.startObject("sales")
.field("type", "double")
.endObject()
.startObject("non_dimension_field")
.field("type", "text")
.endObject()
.endObject()
.endObject()
.toString()
);

// Create an index without a star-tree
createIndex(
REGULAR_INDEX_NAME,
Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0).build()
);
ensureGreen(STARTREE_INDEX_NAME, REGULAR_INDEX_NAME);

client().prepareIndex(STARTREE_INDEX_NAME)
.setSource("product", "laptop", "sales", 1200.0, "non_dimension_field", "some text", "size", "18")
.get();
client().prepareIndex(STARTREE_INDEX_NAME)
.setSource("product", "keyboard", "sales", 150.0, "non_dimension_field", "more text", "size", "12")
.get();
client().prepareIndex(REGULAR_INDEX_NAME).setSource("product", "monitor", "sales", 400.0).get();

// Force merge the star-tree index to ensure star-tree segments are created
client().admin().indices().forceMerge(new ForceMergeRequest(STARTREE_INDEX_NAME).maxNumSegments(1)).get();
refresh();

// Query 1: This query should be answered by the star-tree.
// It's an aggregation on a dimension with a metric sub-aggregation and size=0.
SearchResponse starTreeResponse = client().prepareSearch(STARTREE_INDEX_NAME)
.setQuery(matchAllQuery())
.addAggregation(
AggregationBuilders.terms("products").field("product").subAggregation(AggregationBuilders.sum("total_sales").field("sales"))
)
.setSize(0)
.execute()
.actionGet();
assertEquals(2, starTreeResponse.getHits().getTotalHits().value());
Terms terms = starTreeResponse.getAggregations().get("products");
assertEquals(2, terms.getBuckets().size());
Sum sum = terms.getBuckets().get(0).getAggregations().get("total_sales");
assertNotNull(sum);

// Query 2: This query should NOT be answered by the star-tree as it queries a non-dimension field.
client().prepareSearch(STARTREE_INDEX_NAME).setQuery(termQuery("non_dimension_field", "text")).execute().actionGet();

// Query 3: A standard query on the regular index.
client().prepareSearch(REGULAR_INDEX_NAME).setQuery(matchAllQuery()).get();

// Fetch index statistics
IndicesStatsResponse stats = client().admin().indices().prepareStats(STARTREE_INDEX_NAME, REGULAR_INDEX_NAME).setSearch(true).get();

// Assertions for the star-tree index
SearchStats.Stats starTreeIndexStats = stats.getIndex(STARTREE_INDEX_NAME).getTotal().getSearch().getTotal();

assertThat("Star-tree index should have 2 total queries", starTreeIndexStats.getQueryCount(), equalTo(2L));
assertThat("Star-tree index should have 1 star-tree query", starTreeIndexStats.getStarTreeQueryCount(), equalTo(1L));
assertThat(
"Star-tree query time should be non-negative",
starTreeIndexStats.getStarTreeQueryTimeInMillis(),
greaterThanOrEqualTo(0L)
);
assertThat(
"Star-tree query current should be non-negative",
starTreeIndexStats.getStarTreeQueryCurrent(),
greaterThanOrEqualTo(0L)
);

// Assertions for the regular index
SearchStats.Stats regularIndexStats = stats.getIndex(REGULAR_INDEX_NAME).getTotal().getSearch().getTotal();
assertThat("Regular index should have 1 total query", regularIndexStats.getQueryCount(), equalTo(1L));
assertThat("Regular index should have 0 star-tree queries", regularIndexStats.getStarTreeQueryCount(), equalTo(0L));
assertThat("Regular index should have 0 star-tree query time", regularIndexStats.getStarTreeQueryTimeInMillis(), equalTo(0L));

// Assertions for the total stats across both indices
SearchStats.Stats totalStats = stats.getTotal().getSearch().getTotal();
assertThat("Total query count should be 3", totalStats.getQueryCount(), equalTo(3L));
assertThat("Total star-tree query count should be 1", totalStats.getStarTreeQueryCount(), equalTo(1L));
assertThat(totalStats.getQueryTimeInMillis(), greaterThanOrEqualTo(totalStats.getStarTreeQueryTimeInMillis()));

// While not guaranteed, the time spent on the single star-tree query should be less than the total query time.
if (totalStats.getStarTreeQueryCount() > 0 && totalStats.getQueryCount() > totalStats.getStarTreeQueryCount()) {
assertThat(
"Star-tree query time should be less than total query time",
totalStats.getStarTreeQueryTimeInMillis(),
lessThan(totalStats.getQueryTimeInMillis())
);
}
}
}
Loading
Loading