Skip to content

Commit

Permalink
ui: include SERIALIZATION_CONFLICT events in txn contention details
Browse files Browse the repository at this point in the history
This commit introduces `SERIALIZATION_CONFLICT` contention event details to
the txn insights details page. If a transaction insight failed with a
40001 error code, we will query `crdb_internal.transaction_contention_events`
to see if there is any serialization conflict event containing the blocking
txn exec id, fingerprint id and conflict location information.

A section called `Failed Execution` will appear when this information is
available with the following info:
- blocking txn execution id
- blocking txn finggerprint id
- conflict location - db, table and index names

Epic: none
Closes: cockroachdb#111650

Release note (ui change): Txn insight details will show the following details
when we have information on a txn execution with a 40001 error code and we
have captured the conflicting txn meta (only available if the txn had not yet
committed at the time of execution).
A section called `Failed Execution` will appear when this information is
available with the following info:
- blocking txn execution id
- blocking txn finggerprint id
- conflict location - db, table and index names
  • Loading branch information
xinhaoz committed Oct 9, 2023
1 parent f840e7f commit 98aca73
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 9 deletions.
10 changes: 8 additions & 2 deletions pkg/ui/workspaces/cluster-ui/src/api/contentionApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from "./sqlApi";
import {
ContentionDetails,
ContentionTypeKey,
InsightExecEnum,
InsightNameEnum,
TxnContentionInsightDetails,
Expand Down Expand Up @@ -57,6 +58,7 @@ export type ContentionResponseColumns = {
table_name: string;
index_name: string;
key: string;
contention_type: ContentionTypeKey;
};

export async function getContentionDetailsApi(
Expand Down Expand Up @@ -95,7 +97,9 @@ export async function getContentionDetailsApi(
x.rows.forEach(row => {
contentionDetails.push({
blockingExecutionID: row.blocking_txn_id,
blockingTxnFingerprintID: row.blocking_txn_fingerprint_id,
blockingTxnFingerprintID: FixFingerprintHexValue(
row.blocking_txn_fingerprint_id,
),
blockingTxnQuery: null,
waitingTxnID: row.waiting_txn_id,
waitingTxnFingerprintID: row.waiting_txn_fingerprint_id,
Expand All @@ -113,6 +117,7 @@ export async function getContentionDetailsApi(
row.index_name && row.index_name !== ""
? row.index_name
: "index not found",
contentionType: row.contention_type,
});
});
});
Expand Down Expand Up @@ -190,7 +195,8 @@ function contentionDetailsQuery(filters?: ContentionFilters) {
database_name,
schema_name,
table_name,
index_name
index_name,
contention_type
FROM
crdb_internal.transaction_contention_events
${whereClause}
Expand Down
15 changes: 13 additions & 2 deletions pkg/ui/workspaces/cluster-ui/src/api/txnInsightDetailsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
sqlApiErrorMessage,
SqlApiResponse,
} from "./sqlApi";
import { InsightNameEnum, TxnInsightDetails } from "../insights";
import {
InsightNameEnum,
StmtFailureCodesStr,
TxnInsightDetails,
} from "../insights";
import {
formatStmtInsights,
stmtInsightsByTxnExecutionQuery,
Expand Down Expand Up @@ -137,8 +141,15 @@ export async function getTxnInsightDetailsApi(
insight => insight.name === InsightNameEnum.highContention,
);

const isRetrySerializableFailure =
txnInsightDetails.txnDetails?.errorCode ===
StmtFailureCodesStr.RETRY_SERIALIZABLE;

try {
if (!req.excludeContention && highContention) {
if (
!req.excludeContention &&
(highContention || isRetrySerializableFailure)
) {
const contentionInfo = await getTxnInsightsContentionDetailsApi(req);
txnInsightDetails.blockingContentionDetails =
contentionInfo?.blockingContentionDetails;
Expand Down
4 changes: 4 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/txnInsightsApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe("test txn insights api functions", () => {
blocking_txn_fingerprint_id: "4329ab5f4493f82d",
waiting_txn_id: waitingTxnID,
waiting_txn_fingerprint_id: "1831d909096f992c",
contention_type: "LOCK_WAIT",
};

afterEach(() => {
Expand Down Expand Up @@ -111,6 +112,7 @@ describe("test txn insights api functions", () => {
waitingTxnFingerprintID:
contentionDetailsMock.waiting_txn_fingerprint_id,
waitingTxnID: contentionDetailsMock.waiting_txn_id,
contentionType: "LOCK_WAIT",
},
],
execType: InsightExecEnum.TRANSACTION,
Expand Down Expand Up @@ -157,6 +159,7 @@ describe("test txn insights api functions", () => {
waitingTxnFingerprintID:
contentionDetailsMock.waiting_txn_fingerprint_id,
waitingTxnID: contentionDetailsMock.waiting_txn_id,
contentionType: "LOCK_WAIT",
},
],
execType: InsightExecEnum.TRANSACTION,
Expand Down Expand Up @@ -212,6 +215,7 @@ describe("test txn insights api functions", () => {
waitingTxnFingerprintID:
contentionDetailsMock.waiting_txn_fingerprint_id,
waitingTxnID: contentionDetailsMock.waiting_txn_id,
contentionType: "LOCK_WAIT",
},
],
execType: InsightExecEnum.TRANSACTION,
Expand Down
12 changes: 12 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/insights/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@

import moment, { Moment } from "moment-timezone";
import { Filters } from "../queryFilter";
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";

const ContentionTypeEnum = cockroach.sql.contentionpb.ContentionType;

export type ContentionTypeKey = {
[K in keyof typeof ContentionTypeEnum]: K;
}[keyof typeof ContentionTypeEnum];

// This enum corresponds to the string enum for `problems` in `cluster_execution_insights`
export enum InsightNameEnum {
Expand Down Expand Up @@ -83,6 +90,7 @@ export type ContentionDetails = {
tableName: string;
indexName: string;
contentionTimeMs: number;
contentionType: ContentionTypeKey;
};

// The return type of getTxnInsightsContentionDetailsApi.
Expand Down Expand Up @@ -354,3 +362,7 @@ export interface insightDetails {
duration?: number;
description: string;
}

export enum StmtFailureCodesStr {
RETRY_SERIALIZABLE = "40001",
}
1 change: 1 addition & 0 deletions pkg/ui/workspaces/cluster-ui/src/insights/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const blockedContentionMock: ContentionDetails = {
tableName: "table",
indexName: "index",
contentionTimeMs: 500,
contentionType: "LOCK_WAIT",
};

const statementInsightMock: StmtInsightEvent = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import React from "react";
import { Col, Row } from "antd";
import { ContentionDetails } from "../types";
import { SummaryCard, SummaryCardItem } from "../../summaryCard";
import { Heading } from "@cockroachlabs/ui-components";
import classNames from "classnames/bind";

// TODO (xinhaoz) we should organize these common page details styles into its own file.
import styles from "../../statementDetails/statementDetails.module.scss";

import "antd/lib/row/style";
import "antd/lib/col/style";
import { TransactionDetailsLink } from "../workloadInsights/util";

const cx = classNames.bind(styles);

type Props = {
conflictDetails: ContentionDetails;
};

const FailedInsightDetailsPanelLabels = {
SECTION_HEADER: "Failed Execution",
CONFLICTING_TRANSACTION_HEADER: "Conflicting Transaction",
CONFLICTING_TRANSACTION_EXEC_ID: "Transaction Execution",
CONFLICTING_TRANSACTION_FINGERPRINT: "Transaction Fingerprint",
CONFLICT_LOCATION_HEADER: "Conflict Location",
DATABASE_NAME: "Database",
TABLE_NAME: "Table",
INDEX_NAME: "Index",
};

export const FailedInsightDetailsPanel: React.FC<Props> = ({
conflictDetails,
}) => {
return (
<section
className={cx("section", "section--container", "margin-bottom-large")}
>
<Row gutter={24}>
<Col>
<Heading type="h5">
{FailedInsightDetailsPanelLabels.SECTION_HEADER}
</Heading>
<Row gutter={24}>
<Col className="gutter-row" span={12}>
<SummaryCard className={cx("summary-card")}>
<Heading type="h5">
{
FailedInsightDetailsPanelLabels.CONFLICTING_TRANSACTION_HEADER
}
</Heading>
<SummaryCardItem
label={
FailedInsightDetailsPanelLabels.CONFLICTING_TRANSACTION_EXEC_ID
}
value={conflictDetails.blockingExecutionID}
/>
<SummaryCardItem
label={
FailedInsightDetailsPanelLabels.CONFLICTING_TRANSACTION_FINGERPRINT
}
value={TransactionDetailsLink(
conflictDetails.blockingTxnFingerprintID,
)}
/>
</SummaryCard>
</Col>
<Col className="gutter-row" span={12}>
<SummaryCard className={cx("summary-card")}>
<Heading type="h5">
{FailedInsightDetailsPanelLabels.CONFLICT_LOCATION_HEADER}
</Heading>
<SummaryCardItem
label={FailedInsightDetailsPanelLabels.DATABASE_NAME}
value={conflictDetails.databaseName}
/>
<SummaryCardItem
label={FailedInsightDetailsPanelLabels.TABLE_NAME}
value={conflictDetails.tableName}
/>
<SummaryCardItem
label={FailedInsightDetailsPanelLabels.INDEX_NAME}
value={conflictDetails.indexName}
/>
</SummaryCard>
</Col>
</Row>
</Col>
</Row>
</section>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import "antd/lib/tabs/style";
import { Button } from "src/button";
import { getMatchParamByName } from "src/util/query";
import { TxnInsightDetailsRequest, TxnInsightDetailsReqErrs } from "src/api";
import { InsightNameEnum, TxnInsightDetails } from "../types";
import {
InsightNameEnum,
StmtFailureCodesStr,
TxnInsightDetails,
} from "../types";

import { commonStyles } from "src/common";
import { TimeScale } from "../../timeScaleDropdown";
Expand Down Expand Up @@ -94,7 +98,8 @@ export const TransactionInsightDetails: React.FC<
(txnDetails != null &&
txnDetails.insights.find(
i => i.name === InsightNameEnum.highContention,
) == null);
) == null &&
txnDetails.errorCode !== StmtFailureCodesStr.RETRY_SERIALIZABLE);

if (!stmtsComplete || !contentionComplete || txnDetails == null) {
// Only fetch if we are missing some information.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import insightTableStyles from "src/insightsTable/insightsTable.module.scss";
import insightsDetailsStyles from "src/insights/workloadInsightDetails/insightsDetails.module.scss";
import { InsightsError } from "../insightsErrorComponent";
import { Timestamp } from "../../timestamp";
import { FailedInsightDetailsPanel } from "./failedInsightDetailsPanel";

const cx = classNames.bind(insightsDetailsStyles);
const tableCx = classNames.bind(insightTableStyles);
Expand Down Expand Up @@ -93,8 +94,9 @@ the maximum number of statements was reached in the console.`;
true,
);

const blockingExecutions: ContentionEvent[] = contentionDetails?.map(
event => {
const blockingExecutions: ContentionEvent[] = contentionDetails
?.filter(e => e.contentionType === "LOCK_WAIT")
.map(event => {
const stmtInsight = statements.find(
stmt => stmt.statementExecutionID === event.waitingStmtID,
);
Expand All @@ -113,7 +115,12 @@ the maximum number of statements was reached in the console.`;
indexName: event.indexName,
stmtInsightEvent: stmtInsight,
};
},
});

// We only expect up to 1 serialization conflict since only 1 can be recorded
// per execution.
const serializationConflict = contentionDetails?.find(
e => e.contentionType === "SERIALIZATION_CONFLICT",
);

const insightRecs = getTxnInsightRecommendations(txnDetails);
Expand Down Expand Up @@ -233,6 +240,9 @@ the maximum number of statements was reached in the console.`;
)}
</Loading>
</section>
{serializationConflict && (
<FailedInsightDetailsPanel conflictDetails={serializationConflict} />
)}
{hasContentionInsights && (
<Loading
loading={!maxRequestsReached && contentionDetails == null}
Expand Down

0 comments on commit 98aca73

Please sign in to comment.