diff --git a/pkg/ui/workspaces/cluster-ui/src/api/databaseDetailsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/databaseDetailsApi.ts index 0e7251254eb8..0d863aa651c6 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/databaseDetailsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/databaseDetailsApi.ts @@ -393,6 +393,7 @@ type DatabaseIndexUsageStatsResponse = { const getDatabaseIndexUsageStats: DatabaseDetailsQuery = { createStmt: (dbName: string, csIndexUnusedDuration: string) => { + csIndexUnusedDuration = csIndexUnusedDuration ?? "168h"; return { sql: Format( `SELECT * FROM (SELECT diff --git a/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts index 6b688b53fea8..cdbe34b1ee99 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts @@ -51,12 +51,16 @@ type CreateIndexRecommendationsResponse = { index_recommendations: string[]; }; +export type SchemaInsightReqParams = { + csIndexUnusedDuration: string; +}; + type SchemaInsightResponse = | ClusterIndexUsageStatistic | CreateIndexRecommendationsResponse; type SchemaInsightQuery = { name: InsightType; - query: string; + query: string | ((csIndexUnusedDuration: string) => string); toSchemaInsight: (response: SqlTxnResult) => InsightRecommendation[]; }; @@ -142,12 +146,9 @@ function createIndexRecommendationsToSchemaInsight( // and want to return the most used ones as a priority. const dropUnusedIndexQuery: SchemaInsightQuery = { name: "DropIndex", - query: `WITH cs AS ( - SELECT value - FROM crdb_internal.cluster_settings - WHERE variable = 'sql.index_recommendation.drop_unused_duration' - ) - SELECT * FROM (SELECT us.table_id, + query: (csIndexUnusedDuration: string) => { + csIndexUnusedDuration = csIndexUnusedDuration ?? "168h"; + return `SELECT * FROM (SELECT us.table_id, us.index_id, us.last_read, us.total_reads, @@ -157,18 +158,18 @@ const dropUnusedIndexQuery: SchemaInsightQuery = { t.parent_id as database_id, t.database_name, t.schema_name, - cs.value as unused_threshold, - cs.value::interval as interval_threshold, + '${csIndexUnusedDuration}' as unused_threshold, + '${csIndexUnusedDuration}'::interval as interval_threshold, now() - COALESCE(us.last_read AT TIME ZONE 'UTC', COALESCE(ti.created_at, '0001-01-01')) as unused_interval FROM "".crdb_internal.index_usage_statistics AS us JOIN "".crdb_internal.table_indexes as ti ON us.index_id = ti.index_id AND us.table_id = ti.descriptor_id JOIN "".crdb_internal.tables as t ON t.table_id = ti.descriptor_id and t.name = ti.descriptor_name - CROSS JOIN cs WHERE t.database_name != 'system' AND ti.is_unique IS false) WHERE unused_interval > interval_threshold - ORDER BY total_reads DESC;`, + ORDER BY total_reads DESC;`; + }, toSchemaInsight: clusterIndexUsageStatsToSchemaInsight, }; @@ -211,14 +212,24 @@ const schemaInsightQueries: SchemaInsightQuery[] = [ createIndexRecommendationsQuery, ]; +function getQuery( + csIndexUnusedDuration: string, + query: string | ((csIndexUnusedDuration: string) => string), +): string { + if (typeof query == "string") { + return query; + } + return query(csIndexUnusedDuration); +} + // getSchemaInsights makes requests over the SQL API and transforms the corresponding // SQL responses into schema insights. -export async function getSchemaInsights(): Promise< - SqlApiResponse -> { +export async function getSchemaInsights( + params: SchemaInsightReqParams, +): Promise> { const request: SqlExecutionRequest = { statements: schemaInsightQueries.map(insightQuery => ({ - sql: insightQuery.query, + sql: getQuery(params.csIndexUnusedDuration, insightQuery.query), })), execute: true, max_result_size: LARGE_RESULT_SIZE, diff --git a/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts index 9116685f3d3d..7ac5ce6f95b1 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts @@ -481,6 +481,7 @@ const getTableIndexUsageStats: TableDetailsQuery = { [new Identifier(dbName), new SQL(tableName)], new SQL("."), ); + csIndexUnusedDuration = csIndexUnusedDuration ?? "168h"; return { sql: Format( `WITH tableId AS (SELECT $1::regclass::int as table_id) diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsights.fixture.ts b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsights.fixture.ts index a774d897b26a..535fc139b697 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsights.fixture.ts +++ b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsights.fixture.ts @@ -71,6 +71,7 @@ export const SchemaInsightsPropsFixture: SchemaInsightsViewProps = { schemaInsightType: "", }, hasAdminRole: true, + csIndexUnusedDuration: "168h", refreshSchemaInsights: () => {}, onSortChange: () => {}, onFiltersChange: () => {}, diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsPageConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsPageConnected.tsx index 8458a00475fb..49ebf928d9c5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsPageConnected.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsPageConnected.tsx @@ -32,6 +32,7 @@ import { actions as localStorageActions } from "../../store/localStorage"; import { Dispatch } from "redux"; import { selectHasAdminRole } from "../../store/uiConfig"; import { actions as analyticsActions } from "../../store/analytics"; +import { selectDropUnusedIndexDuration } from "src/store/clusterSettings/clusterSettings.selectors"; const mapStateToProps = ( state: AppState, @@ -45,6 +46,7 @@ const mapStateToProps = ( sortSetting: selectSortSetting(state), hasAdminRole: selectHasAdminRole(state), maxSizeApiReached: selectSchemaInsightsMaxApiSizeReached(state), + csIndexUnusedDuration: selectDropUnusedIndexDuration(state), }); const mapDispatchToProps = ( @@ -82,8 +84,8 @@ const mapDispatchToProps = ( }), ); }, - refreshSchemaInsights: () => { - dispatch(actions.refresh()); + refreshSchemaInsights: (csIndexUnusedDuration: string) => { + dispatch(actions.refresh({ csIndexUnusedDuration })); }, refreshUserSQLRoles: () => dispatch(uiConfigActions.refreshUserSQLRoles()), }); diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx index 4b49858e46e0..3ac96dd838ed 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx @@ -55,13 +55,14 @@ export type SchemaInsightsViewStateProps = { filters: SchemaInsightEventFilters; sortSetting: SortSetting; hasAdminRole: boolean; + csIndexUnusedDuration: string; maxSizeApiReached?: boolean; }; export type SchemaInsightsViewDispatchProps = { onFiltersChange: (filters: SchemaInsightEventFilters) => void; onSortChange: (ss: SortSetting) => void; - refreshSchemaInsights: () => void; + refreshSchemaInsights: (csIndexUnusedDuration: string) => void; refreshUserSQLRoles: () => void; }; @@ -83,6 +84,7 @@ export const SchemaInsightsView: React.FC = ({ onFiltersChange, onSortChange, maxSizeApiReached, + csIndexUnusedDuration, }: SchemaInsightsViewProps) => { const isCockroachCloud = useContext(CockroachCloudContext); const [pagination, setPagination] = useState({ @@ -95,13 +97,17 @@ export const SchemaInsightsView: React.FC = ({ ); useEffect(() => { + const refreshSchema = (): void => { + refreshSchemaInsights(csIndexUnusedDuration); + }; + // Refresh every 1 minute. - refreshSchemaInsights(); - const interval = setInterval(refreshSchemaInsights, 60 * 1000); + refreshSchema(); + const interval = setInterval(refreshSchema, 60 * 1000); return () => { clearInterval(interval); }; - }, [refreshSchemaInsights]); + }, [refreshSchemaInsights, csIndexUnusedDuration]); useEffect(() => { // Refresh every 5 minutes. diff --git a/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.reducer.ts b/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.reducer.ts index 3aa0cb1ffe2a..63dd27f4ab4a 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.reducer.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.reducer.ts @@ -9,10 +9,10 @@ // licenses/APL.txt. import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { DOMAIN_NAME, noopReducer } from "../utils"; +import { DOMAIN_NAME } from "../utils"; import moment, { Moment } from "moment-timezone"; import { InsightRecommendation } from "../../insights"; -import { SqlApiResponse } from "src/api"; +import { SchemaInsightReqParams, SqlApiResponse } from "src/api"; export type SchemaInsightsState = { data: SqlApiResponse; @@ -50,9 +50,8 @@ const schemaInsightsSlice = createSlice({ state.valid = false; state.lastUpdated = moment.utc(); }, - // Define actions that don't change state. - refresh: noopReducer, - request: noopReducer, + refresh: (_, _action: PayloadAction) => {}, + request: (_, _action: PayloadAction) => {}, }, }); diff --git a/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.sagas.ts b/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.sagas.ts index 60cb09693409..bd1548b40efe 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.sagas.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/schemaInsights/schemaInsights.sagas.ts @@ -11,15 +11,20 @@ import { all, call, put, takeLatest } from "redux-saga/effects"; import { actions } from "./schemaInsights.reducer"; -import { getSchemaInsights } from "../../api"; +import { SchemaInsightReqParams, getSchemaInsights } from "../../api"; +import { PayloadAction } from "@reduxjs/toolkit"; -export function* refreshSchemaInsightsSaga() { - yield put(actions.request()); +export function* refreshSchemaInsightsSaga( + action: PayloadAction, +) { + yield put(actions.request(action.payload)); } -export function* requestSchemaInsightsSaga(): any { +export function* requestSchemaInsightsSaga( + action: PayloadAction, +): any { try { - const result = yield call(getSchemaInsights); + const result = yield call(getSchemaInsights, action.payload); yield put(actions.received(result)); } catch (e) { yield put(actions.failed(e)); diff --git a/pkg/ui/workspaces/db-console/src/views/insights/schemaInsightsPage.tsx b/pkg/ui/workspaces/db-console/src/views/insights/schemaInsightsPage.tsx index 588e02730169..6394b14ba3dc 100644 --- a/pkg/ui/workspaces/db-console/src/views/insights/schemaInsightsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/insights/schemaInsightsPage.tsx @@ -31,6 +31,7 @@ import { selectSchemaInsightsTypes, } from "src/views/insights/insightsSelectors"; import { selectHasAdminRole } from "src/redux/user"; +import { selectDropUnusedIndexDuration } from "src/redux/clusterSettings"; const mapStateToProps = ( state: AdminUIState, @@ -44,13 +45,16 @@ const mapStateToProps = ( sortSetting: schemaInsightsSortLocalSetting.selector(state), hasAdminRole: selectHasAdminRole(state), maxSizeApiReached: selectSchemaInsightsMaxApiReached(state), + csIndexUnusedDuration: selectDropUnusedIndexDuration(state), }); const mapDispatchToProps = { onFiltersChange: (filters: SchemaInsightEventFilters) => schemaInsightsFiltersLocalSetting.set(filters), onSortChange: (ss: SortSetting) => schemaInsightsSortLocalSetting.set(ss), - refreshSchemaInsights: refreshSchemaInsights, + refreshSchemaInsights: (csIndexUnusedDuration: string) => { + return refreshSchemaInsights({ csIndexUnusedDuration }); + }, refreshUserSQLRoles: refreshUserSQLRoles, };