Skip to content

Commit

Permalink
ui: update txn contention insights to use waiting txns as event
Browse files Browse the repository at this point in the history
This commit updates the transaction workload insights pages to use the waiting
contended transaction as the primary contention event, rather than the blocking
transaction.

Fixes #87284.

Release justification: bug fixes and low-risk updates to new functionality
Release note: None
  • Loading branch information
ericharmeling committed Sep 8, 2022
1 parent 01eb6ab commit 653f052
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 74 deletions.
121 changes: 61 additions & 60 deletions pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,47 +52,47 @@ export type TransactionContentionEventsResponse =

// txnContentionQuery selects all relevant transaction contention events.
const txnContentionQuery = `SELECT *
FROM (SELECT blocking_txn_id,
FROM (SELECT waiting_txn_id,
encode(
blocking_txn_fingerprint_id, 'hex'
) AS blocking_txn_fingerprint_id,
waiting_txn_fingerprint_id, 'hex'
) AS waiting_txn_fingerprint_id,
collection_ts,
contention_duration,
row_number() over (
PARTITION BY blocking_txn_fingerprint_id
PARTITION BY waiting_txn_fingerprint_id
ORDER BY
collection_ts DESC
) AS rank,
threshold
FROM (SELECT "sql.insights.latency_threshold" :: INTERVAL AS threshold
FROM
[SHOW CLUSTER SETTING sql.insights.latency_threshold]),
(SELECT DISTINCT ON (blocking_txn_id) *
(SELECT DISTINCT ON (waiting_txn_id) *
FROM crdb_internal.transaction_contention_events tce),
(SELECT txn_id FROM crdb_internal.cluster_execution_insights)
WHERE contention_duration > threshold
OR waiting_txn_id = txn_id)
WHERE rank = 1`;

type TransactionContentionResponseColumns = {
blocking_txn_id: string;
blocking_txn_fingerprint_id: string;
waiting_txn_id: string;
waiting_txn_fingerprint_id: string;
collection_ts: string;
contention_duration: string;
threshold: string;
};

function transactionContentionResultsToEventState(
response: SqlExecutionResponse<TransactionContentionResponseColumns>,
): TransactionContentionEventState[] {
): TransactionContentionEventsResponse {
if (!response.execution.txn_results[0].rows) {
// No transaction contention events.
return [];
}

return response.execution.txn_results[0].rows.map(row => ({
transactionID: row.blocking_txn_id,
fingerprintID: row.blocking_txn_fingerprint_id,
transactionID: row.waiting_txn_id,
fingerprintID: row.waiting_txn_fingerprint_id,
startTime: moment(row.collection_ts),
contentionDuration: moment.duration(row.contention_duration),
contentionThreshold: moment.duration(row.threshold).asMilliseconds(),
Expand Down Expand Up @@ -196,7 +196,7 @@ export function getTransactionInsightEventState(): Promise<TransactionInsightEve
if (!res || res.length < 1) {
return;
}
const txnFingerprintIDs = res.map(row => row.blocking_txn_fingerprint_id);
const txnFingerprintIDs = res.map(row => row.waiting_txn_fingerprint_id);
const txnFingerprintRequest: SqlExecutionRequest = {
statements: [
{
Expand All @@ -209,10 +209,12 @@ export function getTransactionInsightEventState(): Promise<TransactionInsightEve
return executeInternalSql<TxnStmtFingerprintsResponseColumns>(
txnFingerprintRequest,
).then(txnStmtFingerprintResults => {
const stmtFingerprintIDs =
txnStmtFingerprintResults.execution.txn_results[0].rows?.map(
row => row.query_ids,
);
const txnStmtRes =
txnStmtFingerprintResults.execution.txn_results[0].rows;
if (!txnStmtRes || txnStmtRes.length < 1) {
return;
}
const stmtFingerprintIDs = txnStmtRes.map(row => row.query_ids);
const fingerprintStmtsRequest: SqlExecutionRequest = {
statements: [
{
Expand All @@ -238,7 +240,7 @@ export function getTransactionInsightEventState(): Promise<TransactionInsightEve
}

export function combineTransactionInsightEventState(
txnContentionState: TransactionContentionEventState[],
txnContentionState: TransactionContentionEventsResponse,
txnFingerprintState: TxnStmtFingerprintEventsResponse,
fingerprintStmtState: FingerprintStmtsEventsResponse,
): TransactionInsightEventState[] {
Expand Down Expand Up @@ -294,7 +296,7 @@ export type TransactionInsightEventDetailsRequest = { id: string };
// Query 1 types, functions.
export type TransactionContentionEventDetailsState = Omit<
TransactionInsightEventDetailsState,
"application" | "queries" | "waitingQueries"
"application" | "queries" | "blockingQueries"
>;

export type TransactionContentionEventDetailsResponse =
Expand Down Expand Up @@ -324,17 +326,17 @@ const txnContentionDetailsQuery = (id: string) => `SELECT collection_ts,
LEFT OUTER JOIN crdb_internal.ranges AS ranges
ON tce.contending_key BETWEEN ranges.start_key
AND ranges.end_key
WHERE blocking_txn_id = '${id}'
WHERE waiting_txn_id = '${id}'
`;

type TxnContentionDetailsResponseColumns = {
blocking_txn_id: string;
waiting_txn_id: string;
waiting_txn_fingerprint_id: string;
collection_ts: string;
contention_duration: string;
threshold: string;
blocking_txn_id: string;
blocking_txn_fingerprint_id: string;
waiting_txn_id: string;
waiting_txn_fingerprint_id: string;
schema_name: string;
database_name: string;
table_name: string;
Expand All @@ -351,13 +353,13 @@ function transactionContentionDetailsResultsToEventState(
}
const row = response.execution.txn_results[0].rows[0];
return {
executionID: row.blocking_txn_id,
executionID: row.waiting_txn_id,
fingerprintID: row.waiting_txn_fingerprint_id,
startTime: moment(row.collection_ts),
elapsedTime: moment.duration(row.contention_duration).asMilliseconds(),
contentionThreshold: moment.duration(row.threshold).asMilliseconds(),
fingerprintID: row.blocking_txn_fingerprint_id,
waitingExecutionID: row.waiting_txn_id,
waitingFingerprintID: row.waiting_txn_fingerprint_id,
blockingExecutionID: row.blocking_txn_id,
blockingFingerprintID: row.blocking_txn_fingerprint_id,
schemaName: row.schema_name,
databaseName: row.database_name,
tableName: row.table_name,
Expand Down Expand Up @@ -388,64 +390,63 @@ export function getTransactionInsightEventDetailsState(
if (!res || res.length < 1) {
return;
}
const blockingTxnFingerprintId = res[0].blocking_txn_fingerprint_id;
const blockingTxnFingerprintRequest: SqlExecutionRequest = {
const waitingTxnFingerprintId = res[0].waiting_txn_fingerprint_id;
const waitingTxnFingerprintRequest: SqlExecutionRequest = {
statements: [
{
sql: `${txnStmtFingerprintsQuery(blockingTxnFingerprintId)}`,
sql: `${txnStmtFingerprintsQuery(waitingTxnFingerprintId)}`,
},
],
execute: true,
max_result_size: 50000, // 50 kib
};
return executeInternalSql<TxnStmtFingerprintsResponseColumns>(
blockingTxnFingerprintRequest,
).then(blockingTxnStmtFingerprintIDs => {
const blockingStmtFingerprintIDs =
blockingTxnStmtFingerprintIDs.execution.txn_results[0].rows[0]
.query_ids;
const blockingFingerprintStmtsRequest: SqlExecutionRequest = {
waitingTxnFingerprintRequest,
).then(waitingTxnStmtFingerprintIDs => {
const waitingStmtFingerprintIDs =
waitingTxnStmtFingerprintIDs.execution.txn_results[0].rows[0].query_ids;
const waitingFingerprintStmtsRequest: SqlExecutionRequest = {
statements: [
{
sql: `${fingerprintStmtsQuery(blockingStmtFingerprintIDs)}`,
sql: `${fingerprintStmtsQuery(waitingStmtFingerprintIDs)}`,
},
],
execute: true,
max_result_size: 50000, // 50 kib
};
return executeInternalSql<FingerprintStmtsResponseColumns>(
blockingFingerprintStmtsRequest,
).then(blockingTxnStmtQueries => {
const waitingTxnFingerprintId =
waitingFingerprintStmtsRequest,
).then(waitingTxnStmtQueries => {
const blockingTxnFingerprintId =
contentionResults.execution.txn_results[0].rows[0]
.waiting_txn_fingerprint_id;
const waitingTxnFingerprintRequest: SqlExecutionRequest = {
.blocking_txn_fingerprint_id;
const blockingTxnFingerprintRequest: SqlExecutionRequest = {
statements: [
{
sql: `${txnStmtFingerprintsQuery(waitingTxnFingerprintId)}`,
sql: `${txnStmtFingerprintsQuery(blockingTxnFingerprintId)}`,
},
],
execute: true,
max_result_size: 50000, // 50 kib
};
return executeInternalSql<TxnStmtFingerprintsResponseColumns>(
waitingTxnFingerprintRequest,
).then(waitingTxnStmtFingerprintIDs => {
const waitingStmtFingerprintIDs =
waitingTxnStmtFingerprintIDs.execution.txn_results[0].rows[0]
blockingTxnFingerprintRequest,
).then(blockingTxnStmtFingerprintIDs => {
const blockingStmtFingerprintIDs =
blockingTxnStmtFingerprintIDs.execution.txn_results[0].rows[0]
.query_ids;
const waitingFingerprintStmtsRequest: SqlExecutionRequest = {
const blockingFingerprintStmtsRequest: SqlExecutionRequest = {
statements: [
{
sql: `${fingerprintStmtsQuery(waitingStmtFingerprintIDs)}`,
sql: `${fingerprintStmtsQuery(blockingStmtFingerprintIDs)}`,
},
],
execute: true,
max_result_size: 50000, // 50 kib
};
return executeInternalSql<FingerprintStmtsResponseColumns>(
waitingFingerprintStmtsRequest,
).then(waitingTxnStmtQueries => {
blockingFingerprintStmtsRequest,
).then(blockingTxnStmtQueries => {
return combineTransactionInsightEventDetailsState(
transactionContentionDetailsResultsToEventState(
contentionResults,
Expand All @@ -454,10 +455,10 @@ export function getTransactionInsightEventDetailsState(
blockingTxnStmtFingerprintIDs,
),
txnStmtFingerprintsResultsToEventState(
waitingTxnStmtFingerprintIDs,
blockingTxnStmtFingerprintIDs,
),
fingerprintStmtsResultsToEventState(blockingTxnStmtQueries),
fingerprintStmtsResultsToEventState(waitingTxnStmtQueries),
fingerprintStmtsResultsToEventState(blockingTxnStmtQueries),
);
});
});
Expand All @@ -468,31 +469,31 @@ export function getTransactionInsightEventDetailsState(

export function combineTransactionInsightEventDetailsState(
txnContentionDetailsState: TransactionContentionEventDetailsResponse,
blockingTxnFingerprintState: TxnStmtFingerprintEventsResponse,
waitingTxnFingerprintState: TxnStmtFingerprintEventsResponse,
blockingFingerprintStmtState: FingerprintStmtsEventsResponse,
blockingTxnFingerprintState: TxnStmtFingerprintEventsResponse,
waitingFingerprintStmtState: FingerprintStmtsEventsResponse,
blockingFingerprintStmtState: FingerprintStmtsEventsResponse,
): TransactionInsightEventDetailsState {
let res: TransactionInsightEventDetailsState;
if (
txnContentionDetailsState &&
blockingTxnFingerprintState &&
waitingTxnFingerprintState &&
blockingFingerprintStmtState &&
waitingFingerprintStmtState
blockingTxnFingerprintState &&
waitingFingerprintStmtState &&
blockingFingerprintStmtState
) {
res = {
...txnContentionDetailsState,
application: blockingTxnFingerprintState[0].application,
queries: blockingTxnFingerprintState[0].queryIDs.map(
queries: waitingTxnFingerprintState[0].queryIDs.map(
id =>
blockingFingerprintStmtState.find(
waitingFingerprintStmtState.find(
stmt => stmt.stmtFingerprintID === id,
)?.query,
),
waitingQueries: waitingTxnFingerprintState[0].queryIDs.map(
blockingQueries: blockingTxnFingerprintState[0].queryIDs.map(
id =>
waitingFingerprintStmtState.find(
blockingFingerprintStmtState.find(
stmt => stmt.stmtFingerprintID === id,
)?.query,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ export const WaitTimeInsightsLabels = {
`${capitalize(execType)} ID: ${id} waiting on`,
WAITING_TXNS_TABLE_TITLE: (id: string, execType: ExecutionType): string =>
`${capitalize(execType)}s waiting for ID: ${id}`,
BLOCKED_TXNS_TABLE_TITLE: (id: string, execType: ExecutionType): string =>
`${capitalize(execType)} with ID ${id} waited on`,
WAITED_TXNS_TABLE_TITLE: (id: string, execType: ExecutionType): string =>
`${capitalize(execType)}s that waited for ID: ${id}`,
`${capitalize(execType)}s that waited for ${capitalize(
execType,
)}s with ID ${id}`,
};

type WaitTimeInsightsPanelProps = {
Expand Down
6 changes: 3 additions & 3 deletions pkg/ui/workspaces/cluster-ui/src/insights/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export type TransactionInsightEventDetails = {
contentionThreshold: number;
application: string;
fingerprintID: string;
waitingExecutionID: string;
waitingFingerprintID: string;
waitingQueries: string[];
blockingExecutionID: string;
blockingFingerprintID: string;
blockingQueries: string[];
contendedKey: string;
schemaName: string;
databaseName: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ export class TransactionInsightDetails extends React.Component<TransactionInsigh
}

const tableData = insightsTableData();
const waitingExecutions: EventExecution[] = [
const blockingExecutions: EventExecution[] = [
{
executionID: insightDetails.waitingExecutionID,
fingerprintID: insightDetails.waitingFingerprintID,
queries: insightDetails.waitingQueries,
executionID: insightDetails.blockingExecutionID,
fingerprintID: insightDetails.blockingFingerprintID,
queries: insightDetails.blockingQueries,
startTime: insightDetails.startTime,
elapsedTime: insightDetails.elapsedTime,
execType: insightDetails.execType,
Expand Down Expand Up @@ -176,14 +176,14 @@ export class TransactionInsightDetails extends React.Component<TransactionInsigh
<Col>
<Row>
<Heading type="h5">
{WaitTimeInsightsLabels.WAITED_TXNS_TABLE_TITLE(
{WaitTimeInsightsLabels.BLOCKED_TXNS_TABLE_TITLE(
insightDetails.executionID,
insightDetails.execType,
)}
</Heading>
<div className={tableCx("margin-bottom-large")}>
<WaitTimeDetailsTable
data={waitingExecutions}
data={blockingExecutions}
execType={insightDetails.execType}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export const StatementInsightsView: React.FC<StatementInsightsViewProps> = (
</PageConfig>
<div className={cx("table-area")}>
<Loading
loading={statements == null}
loading={statements === null}
page="statement insights"
error={statementsError}
renderError={() => InsightsError()}
Expand Down Expand Up @@ -280,7 +280,7 @@ export const StatementInsightsView: React.FC<StatementInsightsViewProps> = (
renderNoResult={
<EmptyInsightsTablePlaceholder
isEmptySearchResults={
search?.length > 0 && filteredStatements?.length > 0
search?.length > 0 && filteredStatements?.length === 0
}
/>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export const TransactionInsightsView: React.FC<TransactionInsightsViewProps> = (
</PageConfig>
<div className={cx("table-area")}>
<Loading
loading={transactions == null}
loading={transactions === null}
page="transaction insights"
error={transactionsError}
renderError={() => InsightsError()}
Expand All @@ -235,7 +235,7 @@ export const TransactionInsightsView: React.FC<TransactionInsightsViewProps> = (
renderNoResult={
<EmptyInsightsTablePlaceholder
isEmptySearchResults={
search?.length > 0 && filteredTransactions?.length == 0
search?.length > 0 && filteredTransactions?.length === 0
}
/>
}
Expand Down

0 comments on commit 653f052

Please sign in to comment.