Initial version of Hot Partition Key Rate Limiting Policy

This commit is contained in:
Chuck Skelton
2026-06-16 18:12:04 -07:00
parent d19c7e0cb7
commit f4c8b71c66
18 changed files with 325 additions and 14 deletions
+5
View File
@@ -17,12 +17,17 @@
"test/out/**": true, "test/out/**": true,
"workers/libs/**": true "workers/libs/**": true
}, },
"js/ts.tsdk.path": "node_modules/typescript/lib",
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit" "source.organizeImports": "explicit"
}, },
"js/ts.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}
} }
+1
View File
@@ -88,6 +88,7 @@ export class CapabilityNames {
public static readonly EnableDynamicDataMasking: string = "EnableDynamicDataMasking"; public static readonly EnableDynamicDataMasking: string = "EnableDynamicDataMasking";
public static readonly EnableNoSQLFullTextSearchPreviewFeatures: string = "EnableNoSQLFullTextSearchPreviewFeatures"; public static readonly EnableNoSQLFullTextSearchPreviewFeatures: string = "EnableNoSQLFullTextSearchPreviewFeatures";
public static readonly EnableOnlineCopyFeature: string = "EnableOnlineContainerCopy"; public static readonly EnableOnlineCopyFeature: string = "EnableOnlineContainerCopy";
public static readonly EnableHotPartitionKeyThrottling: string = "EnableHotPartitionKeyThrottling";
} }
export enum CapacityMode { export enum CapacityMode {
@@ -112,6 +112,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
: resource.softAllowedMaximumThroughput; : resource.softAllowedMaximumThroughput;
const throughputBuckets = resource?.throughputBuckets; const throughputBuckets = resource?.throughputBuckets;
const hotPartitionKeyRateLimitingPolicy = resource?.hotPartitionKeyRateLimitingPolicy;
if (autoscaleSettings) { if (autoscaleSettings) {
return { return {
@@ -123,6 +124,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
instantMaximumThroughput, instantMaximumThroughput,
softAllowedMaximumThroughput, softAllowedMaximumThroughput,
throughputBuckets, throughputBuckets,
hotPartitionKeyRateLimitingPolicy,
}; };
} }
@@ -135,6 +137,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
instantMaximumThroughput, instantMaximumThroughput,
softAllowedMaximumThroughput, softAllowedMaximumThroughput,
throughputBuckets, throughputBuckets,
hotPartitionKeyRateLimitingPolicy,
}; };
} }
+8
View File
@@ -367,6 +367,10 @@ const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpd
body.properties.resource.throughputBuckets = throughputBuckets; body.properties.resource.throughputBuckets = throughputBuckets;
} }
if (params.hotPartitionKeyRateLimitingPolicy !== undefined) {
body.properties.resource.hotPartitionKeyRateLimitingPolicy = params.hotPartitionKeyRateLimitingPolicy;
}
return body; return body;
}; };
@@ -409,6 +413,10 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 }; newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
} }
if (params.hotPartitionKeyRateLimitingPolicy !== undefined) {
newOffer.content.hotPartitionKeyRateLimitingPolicy = params.hotPartitionKeyRateLimitingPolicy;
}
const sdkResponse = await client() const sdkResponse = await client()
.offer(params.currentOffer.id) .offer(params.currentOffer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660) // TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
+7
View File
@@ -343,6 +343,11 @@ export interface Offer {
instantMaximumThroughput?: number; instantMaximumThroughput?: number;
softAllowedMaximumThroughput?: number; softAllowedMaximumThroughput?: number;
throughputBuckets?: ThroughputBucket[]; throughputBuckets?: ThroughputBucket[];
hotPartitionKeyRateLimitingPolicy?: HotPartitionKeyRateLimitingPolicy;
}
export interface HotPartitionKeyRateLimitingPolicy {
maximumPerPartitionKeyThroughputUtilizationPercent: number;
} }
export interface ThroughputBucket { export interface ThroughputBucket {
@@ -359,6 +364,7 @@ export interface SDKOfferDefinition extends Resource {
offerIsRUPerMinuteThroughputEnabled?: boolean; offerIsRUPerMinuteThroughputEnabled?: boolean;
collectionThroughputInfo?: OfferThroughputInfo; collectionThroughputInfo?: OfferThroughputInfo;
offerAutopilotSettings?: AutoPilotOfferSettings; offerAutopilotSettings?: AutoPilotOfferSettings;
hotPartitionKeyRateLimitingPolicy?: HotPartitionKeyRateLimitingPolicy;
}; };
resource?: string; resource?: string;
offerResourceId?: string; offerResourceId?: string;
@@ -492,6 +498,7 @@ export interface UpdateOfferParams {
migrateToAutoPilot?: boolean; migrateToAutoPilot?: boolean;
migrateToManual?: boolean; migrateToManual?: boolean;
throughputBuckets?: ThroughputBucket[]; throughputBuckets?: ThroughputBucket[];
hotPartitionKeyRateLimitingPolicy?: HotPartitionKeyRateLimitingPolicy | null;
} }
export interface Notification { export interface Notification {
@@ -17,25 +17,25 @@ import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { Keys, t } from "Localization"; import { Keys, t } from "Localization";
import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import * as React from "react"; import * as React from "react";
import { isHotPartitionKeyThrottlingEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import DiscardIcon from "../../../../images/discard.svg"; import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg"; import SaveIcon from "../../../../images/save-cosmos.svg";
import { AuthType } from "../../../AuthType"; import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress"; import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection"; import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateCollection } from "../../../Common/dataAccess/updateCollection";
import { updateOffer } from "../../../Common/dataAccess/updateOffer"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor"; import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { import {
PartitionKeyComponent, PartitionKeyComponent,
@@ -65,16 +65,16 @@ import {
AddMongoIndexProps, AddMongoIndexProps,
ChangeFeedPolicyState, ChangeFeedPolicyState,
GeospatialConfigType, GeospatialConfigType,
MongoIndexTypes,
SettingsV2TabTypes,
TtlType,
getMongoNotification, getMongoNotification,
getTabTitle, getTabTitle,
hasDatabaseSharedThroughput, hasDatabaseSharedThroughput,
isDataMaskingEnabled, isDataMaskingEnabled,
isDirty, isDirty,
MongoIndexTypes,
parseConflictResolutionMode, parseConflictResolutionMode,
parseConflictResolutionProcedure, parseConflictResolutionProcedure,
SettingsV2TabTypes,
TtlType,
} from "./SettingsUtils"; } from "./SettingsUtils";
interface SettingsV2TabInfo { interface SettingsV2TabInfo {
tab: SettingsV2TabTypes; tab: SettingsV2TabTypes;
@@ -103,6 +103,8 @@ export interface SettingsComponentState {
throughputBuckets: DataModels.ThroughputBucket[]; throughputBuckets: DataModels.ThroughputBucket[];
throughputBucketsBaseline: DataModels.ThroughputBucket[]; throughputBucketsBaseline: DataModels.ThroughputBucket[];
throughputError: string; throughputError: string;
hotPartitionKeyRateLimitingPolicy: DataModels.HotPartitionKeyRateLimitingPolicy;
hotPartitionKeyRateLimitingPolicyBaseline: DataModels.HotPartitionKeyRateLimitingPolicy;
timeToLive: TtlType; timeToLive: TtlType;
timeToLiveBaseline: TtlType; timeToLiveBaseline: TtlType;
@@ -225,6 +227,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
throughputBuckets: undefined, throughputBuckets: undefined,
throughputBucketsBaseline: undefined, throughputBucketsBaseline: undefined,
throughputError: undefined, throughputError: undefined,
hotPartitionKeyRateLimitingPolicy: null,
hotPartitionKeyRateLimitingPolicyBaseline: null,
timeToLive: undefined, timeToLive: undefined,
timeToLiveBaseline: undefined, timeToLiveBaseline: undefined,
@@ -495,6 +499,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
throughput: this.state.throughputBaseline, throughput: this.state.throughputBaseline,
throughputBuckets: this.state.throughputBucketsBaseline, throughputBuckets: this.state.throughputBucketsBaseline,
throughputBucketsBaseline: this.state.throughputBucketsBaseline, throughputBucketsBaseline: this.state.throughputBucketsBaseline,
hotPartitionKeyRateLimitingPolicy: this.state.hotPartitionKeyRateLimitingPolicyBaseline,
hotPartitionKeyRateLimitingPolicyBaseline: this.state.hotPartitionKeyRateLimitingPolicyBaseline,
timeToLive: this.state.timeToLiveBaseline, timeToLive: this.state.timeToLiveBaseline,
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline, timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
displayedTtlSeconds: this.state.displayedTtlSecondsBaseline, displayedTtlSeconds: this.state.displayedTtlSecondsBaseline,
@@ -877,12 +883,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
] as DataModels.ComputedProperties; ] as DataModels.ComputedProperties;
} }
const throughputBuckets = this.offer?.throughputBuckets; const throughputBuckets = this.offer?.throughputBuckets;
const hotPartitionKeyRateLimitingPolicy = this.offer?.hotPartitionKeyRateLimitingPolicy ?? null;
return { return {
throughput: offerThroughput, throughput: offerThroughput,
throughputBaseline: offerThroughput, throughputBaseline: offerThroughput,
throughputBuckets, throughputBuckets,
throughputBucketsBaseline: throughputBuckets, throughputBucketsBaseline: throughputBuckets,
hotPartitionKeyRateLimitingPolicy,
hotPartitionKeyRateLimitingPolicyBaseline: hotPartitionKeyRateLimitingPolicy,
changeFeedPolicy: changeFeedPolicy, changeFeedPolicy: changeFeedPolicy,
changeFeedPolicyBaseline: changeFeedPolicy, changeFeedPolicyBaseline: changeFeedPolicy,
timeToLive: timeToLive, timeToLive: timeToLive,
@@ -984,6 +993,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.setState({ throughputBuckets }); this.setState({ throughputBuckets });
}; };
private onHotPartitionKeyRateLimitingPolicyChange = (
hotPartitionKeyRateLimitingPolicy: DataModels.HotPartitionKeyRateLimitingPolicy,
): void => {
this.setState({ hotPartitionKeyRateLimitingPolicy });
};
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void => private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
this.setState({ isAutoPilotSelected: isAutoPilotSelected }); this.setState({ isAutoPilotSelected: isAutoPilotSelected });
@@ -999,6 +1014,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
currentOffer: this.database.offer(), currentOffer: this.database.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined, autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput, manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
hotPartitionKeyRateLimitingPolicy: isHotPartitionKeyThrottlingEnabled()
? this.state.hotPartitionKeyRateLimitingPolicy ?? null
: undefined,
}; };
if (this.hasProvisioningTypeChanged()) { if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) { if (this.state.isAutoPilotSelected) {
@@ -1232,6 +1250,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined, autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput, manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
throughputBuckets: this.throughputBucketsEnabled ? this.state.throughputBuckets : undefined, throughputBuckets: this.throughputBucketsEnabled ? this.state.throughputBuckets : undefined,
hotPartitionKeyRateLimitingPolicy: isHotPartitionKeyThrottlingEnabled()
? this.state.hotPartitionKeyRateLimitingPolicy ?? null
: undefined,
}; };
if (this.hasProvisioningTypeChanged()) { if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) { if (this.state.isAutoPilotSelected) {
@@ -1300,6 +1321,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onScaleSaveableChange: this.onScaleSaveableChange, onScaleSaveableChange: this.onScaleSaveableChange,
onScaleDiscardableChange: this.onScaleDiscardableChange, onScaleDiscardableChange: this.onScaleDiscardableChange,
throughputError: this.state.throughputError, throughputError: this.state.throughputError,
hotPartitionKeyRateLimitingPolicy: this.state.hotPartitionKeyRateLimitingPolicy,
hotPartitionKeyRateLimitingPolicyBaseline: this.state.hotPartitionKeyRateLimitingPolicyBaseline,
onHotPartitionKeyRateLimitingPolicyChange: this.onHotPartitionKeyRateLimitingPolicyChange,
}; };
if (!this.isCollectionSettingsTab) { if (!this.isCollectionSettingsTab) {
return ( return (
@@ -29,6 +29,9 @@ describe("ScaleComponent", () => {
onScaleDiscardableChange: () => { onScaleDiscardableChange: () => {
return; return;
}, },
onHotPartitionKeyRateLimitingPolicyChange: () => {
return;
},
}; };
it("autoScale disabled", () => { it("autoScale disabled", () => {
@@ -2,7 +2,7 @@ import { Link, MessageBar, MessageBarType, Stack, Text, TextField } from "@fluen
import { Keys, t } from "Localization"; import { Keys, t } from "Localization";
import * as React from "react"; import * as React from "react";
import * as Constants from "../../../../Common/Constants"; import * as Constants from "../../../../Common/Constants";
import { Platform, configContext } from "../../../../ConfigContext"; import { configContext, Platform } from "../../../../ConfigContext";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import * as ViewModels from "../../../../Contracts/ViewModels"; import * as ViewModels from "../../../../Contracts/ViewModels";
import * as SharedConstants from "../../../../Shared/Constants"; import * as SharedConstants from "../../../../Shared/Constants";
@@ -36,6 +36,9 @@ export interface ScaleComponentProps {
onScaleSaveableChange: (isScaleSaveable: boolean) => void; onScaleSaveableChange: (isScaleSaveable: boolean) => void;
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void; onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
throughputError?: string; throughputError?: string;
hotPartitionKeyRateLimitingPolicy?: DataModels.HotPartitionKeyRateLimitingPolicy;
hotPartitionKeyRateLimitingPolicyBaseline?: DataModels.HotPartitionKeyRateLimitingPolicy;
onHotPartitionKeyRateLimitingPolicyChange: (newPolicy: DataModels.HotPartitionKeyRateLimitingPolicy) => void;
} }
export class ScaleComponent extends React.Component<ScaleComponentProps> { export class ScaleComponent extends React.Component<ScaleComponentProps> {
@@ -148,6 +151,9 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
instantMaximumThroughput={this.offer?.instantMaximumThroughput} instantMaximumThroughput={this.offer?.instantMaximumThroughput}
softAllowedMaximumThroughput={this.offer?.softAllowedMaximumThroughput} softAllowedMaximumThroughput={this.offer?.softAllowedMaximumThroughput}
isGlobalSecondaryIndex={this.props.isGlobalSecondaryIndex} isGlobalSecondaryIndex={this.props.isGlobalSecondaryIndex}
hotPartitionKeyRateLimitingPolicy={this.props.hotPartitionKeyRateLimitingPolicy}
hotPartitionKeyRateLimitingPolicyBaseline={this.props.hotPartitionKeyRateLimitingPolicyBaseline}
onHotPartitionKeyRateLimitingPolicyChange={this.props.onHotPartitionKeyRateLimitingPolicyChange}
/> />
); );
@@ -1,12 +1,24 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import * as Constants from "../../../../../Common/Constants";
import * as DataModels from "../../../../../Contracts/DataModels"; import * as DataModels from "../../../../../Contracts/DataModels";
import { updateUserContext } from "../../../../../UserContext";
import { import {
ThroughputInputAutoPilotV3Component, ThroughputInputAutoPilotV3Component,
ThroughputInputAutoPilotV3Props, ThroughputInputAutoPilotV3Props,
} from "./ThroughputInputAutoPilotV3Component"; } from "./ThroughputInputAutoPilotV3Component";
describe("ThroughputInputAutoPilotV3Component", () => { describe("ThroughputInputAutoPilotV3Component", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: Constants.CapabilityNames.EnableHotPartitionKeyThrottling }],
},
} as DataModels.DatabaseAccount,
});
});
const baseProps: ThroughputInputAutoPilotV3Props = { const baseProps: ThroughputInputAutoPilotV3Props = {
databaseAccount: {} as DataModels.DatabaseAccount, databaseAccount: {} as DataModels.DatabaseAccount,
databaseName: "test", databaseName: "test",
@@ -45,6 +57,9 @@ describe("ThroughputInputAutoPilotV3Component", () => {
instantMaximumThroughput: 5000, instantMaximumThroughput: 5000,
softAllowedMaximumThroughput: 1000000, softAllowedMaximumThroughput: 1000000,
isGlobalSecondaryIndex: false, isGlobalSecondaryIndex: false,
onHotPartitionKeyRateLimitingPolicyChange: () => {
return;
},
}; };
it("throughput input visible", () => { it("throughput input visible", () => {
@@ -12,9 +12,11 @@ import {
MessageBarType, MessageBarType,
ProgressIndicator, ProgressIndicator,
Separator, Separator,
Slider,
Stack, Stack,
Text, Text,
TextField, TextField,
Toggle,
} from "@fluentui/react"; } from "@fluentui/react";
import { Keys, t } from "Localization"; import { Keys, t } from "Localization";
import React from "react"; import React from "react";
@@ -25,10 +27,10 @@ import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryPr
import { userContext } from "../../../../../UserContext"; import { userContext } from "../../../../../UserContext";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import { autoPilotThroughput1K } from "../../../../../Utils/AutoPilotUtils"; import { autoPilotThroughput1K } from "../../../../../Utils/AutoPilotUtils";
import { isHotPartitionKeyThrottlingEnabled } from "../../../../../Utils/CapabilityUtils";
import { calculateEstimateNumber } from "../../../../../Utils/PricingUtils"; import { calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import { import {
PriceBreakdown,
checkBoxAndInputStackProps, checkBoxAndInputStackProps,
getChoiceGroupStyles, getChoiceGroupStyles,
getEstimatedSpendingElement, getEstimatedSpendingElement,
@@ -40,11 +42,12 @@ import {
getUpdateThroughputBeyondSupportLimitMessage, getUpdateThroughputBeyondSupportLimitMessage,
manualToAutoscaleDisclaimerElement, manualToAutoscaleDisclaimerElement,
noLeftPaddingCheckBoxStyle, noLeftPaddingCheckBoxStyle,
PriceBreakdown,
relaxedSpacingStackProps, relaxedSpacingStackProps,
saveThroughputWarningMessage, saveThroughputWarningMessage,
titleAndInputStackProps, titleAndInputStackProps,
} from "../../SettingsRenderUtils"; } from "../../SettingsRenderUtils";
import { IsComponentDirtyResult, getSanitizedInputValue, isDirty } from "../../SettingsUtils"; import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent"; import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
export interface ThroughputInputAutoPilotV3Props { export interface ThroughputInputAutoPilotV3Props {
@@ -82,6 +85,9 @@ export interface ThroughputInputAutoPilotV3Props {
instantMaximumThroughput: number; instantMaximumThroughput: number;
softAllowedMaximumThroughput: number; softAllowedMaximumThroughput: number;
isGlobalSecondaryIndex: boolean; isGlobalSecondaryIndex: boolean;
hotPartitionKeyRateLimitingPolicy?: DataModels.HotPartitionKeyRateLimitingPolicy;
hotPartitionKeyRateLimitingPolicyBaseline?: DataModels.HotPartitionKeyRateLimitingPolicy;
onHotPartitionKeyRateLimitingPolicyChange: (newPolicy: DataModels.HotPartitionKeyRateLimitingPolicy) => void;
} }
interface ThroughputInputAutoPilotV3State { interface ThroughputInputAutoPilotV3State {
@@ -137,6 +143,12 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
if (this.hasProvisioningTypeChanged()) { if (this.hasProvisioningTypeChanged()) {
isSaveable = true; isSaveable = true;
isDiscardable = true; isDiscardable = true;
} else if (
isHotPartitionKeyThrottlingEnabled() &&
isDirty(this.props.hotPartitionKeyRateLimitingPolicy, this.props.hotPartitionKeyRateLimitingPolicyBaseline)
) {
isSaveable = true;
isDiscardable = true;
} else if (this.props.isAutoPilotSelected) { } else if (this.props.isAutoPilotSelected) {
if (isDirty(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)) { if (isDirty(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)) {
isDiscardable = true; isDiscardable = true;
@@ -865,6 +877,50 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
); );
}; };
private renderPartitionKeyRateLimitingPolicy = (): JSX.Element => {
return (
<Stack {...titleAndInputStackProps} style={{ maxWidth: "700px" }}>
<Stack horizontal>
<ToolTipLabelComponent
label={t(Keys.controls.settings.scale.rateLimitingPolicyTitle)}
toolTipElement={null}
/>
<Toggle
onText={t(Keys.common.on)}
offText={t(Keys.common.off)}
checked={!!this.props.hotPartitionKeyRateLimitingPolicy}
onChange={(_ev, checked) => {
if (checked) {
this.props.onHotPartitionKeyRateLimitingPolicyChange({
maximumPerPartitionKeyThroughputUtilizationPercent:
this.props.hotPartitionKeyRateLimitingPolicyBaseline
?.maximumPerPartitionKeyThroughputUtilizationPercent ?? 75,
});
} else {
this.props.onHotPartitionKeyRateLimitingPolicyChange(null);
}
}}
/>
</Stack>
<Slider
disabled={!this.props.hotPartitionKeyRateLimitingPolicy}
label={t(Keys.controls.settings.scale.rateLimitPolicyMaxThroughputUtilizationLabel)}
min={51}
max={100}
ariaValueText={(value: number) => `${value} percent`}
valueFormat={(value: number) => `${value}%`}
showValue
value={this.props.hotPartitionKeyRateLimitingPolicy?.maximumPerPartitionKeyThroughputUtilizationPercent ?? 75}
onChange={(value: number) =>
this.props.onHotPartitionKeyRateLimitingPolicyChange({
maximumPerPartitionKeyThroughputUtilizationPercent: value,
})
}
/>
</Stack>
);
};
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<Stack {...checkBoxAndInputStackProps}> <Stack {...checkBoxAndInputStackProps}>
@@ -872,6 +928,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
{this.renderThroughputModeChoices()} {this.renderThroughputModeChoices()}
{this.renderThroughputComponent()} {this.renderThroughputComponent()}
{isHotPartitionKeyThrottlingEnabled() && this.renderPartitionKeyRateLimitingPolicy()}
</Stack> </Stack>
); );
} }
@@ -797,6 +797,44 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
</Stack> </Stack>
</StackItem> </StackItem>
</Stack> </Stack>
<Stack
style={
{
"maxWidth": "700px",
}
}
tokens={
{
"childrenGap": 5,
}
}
>
<Stack
horizontal={true}
>
<ToolTipLabelComponent
label="Rate limiting policy"
toolTipElement={null}
/>
<StyledToggleBase
checked={false}
offText="Off"
onChange={[Function]}
onText="On"
/>
</Stack>
<StyledSliderBase
ariaValueText={[Function]}
disabled={true}
label="Max per partition key Throughput utilization"
max={100}
min={51}
onChange={[Function]}
showValue={true}
value={75}
valueFormat={[Function]}
/>
</Stack>
</Stack> </Stack>
`; `;
@@ -1372,6 +1410,44 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
</Stack> </Stack>
</StackItem> </StackItem>
</Stack> </Stack>
<Stack
style={
{
"maxWidth": "700px",
}
}
tokens={
{
"childrenGap": 5,
}
}
>
<Stack
horizontal={true}
>
<ToolTipLabelComponent
label="Rate limiting policy"
toolTipElement={null}
/>
<StyledToggleBase
checked={false}
offText="Off"
onChange={[Function]}
onText="On"
/>
</Stack>
<StyledSliderBase
ariaValueText={[Function]}
disabled={true}
label="Max per partition key Throughput utilization"
max={100}
min={51}
onChange={[Function]}
showValue={true}
value={75}
valueFormat={[Function]}
/>
</Stack>
</Stack> </Stack>
`; `;
@@ -1930,5 +2006,43 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
</Stack> </Stack>
</StackItem> </StackItem>
</Stack> </Stack>
<Stack
style={
{
"maxWidth": "700px",
}
}
tokens={
{
"childrenGap": 5,
}
}
>
<Stack
horizontal={true}
>
<ToolTipLabelComponent
label="Rate limiting policy"
toolTipElement={null}
/>
<StyledToggleBase
checked={false}
offText="Off"
onChange={[Function]}
onText="On"
/>
</Stack>
<StyledSliderBase
ariaValueText={[Function]}
disabled={true}
label="Max per partition key Throughput utilization"
max={100}
min={51}
onChange={[Function]}
showValue={true}
value={75}
valueFormat={[Function]}
/>
</Stack>
</Stack> </Stack>
`; `;
@@ -3,8 +3,8 @@ import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
const zeroValue = 0; const zeroValue = 0;
export type isDirtyTypes = export type isDirtyTypes =
@@ -17,7 +17,8 @@ export type isDirtyTypes =
| DataModels.VectorIndex[] | DataModels.VectorIndex[]
| DataModels.FullTextPolicy | DataModels.FullTextPolicy
| DataModels.ThroughputBucket[] | DataModels.ThroughputBucket[]
| DataModels.DataMaskingPolicy; | DataModels.DataMaskingPolicy
| DataModels.HotPartitionKeyRateLimitingPolicy;
export const TtlOff = "off"; export const TtlOff = "off";
export const TtlOn = "on"; export const TtlOn = "on";
export const TtlOnNoDefault = "on-nodefault"; export const TtlOnNoDefault = "on-nodefault";
@@ -179,10 +179,13 @@ exports[`SettingsComponent renders 1`] = `
"vectorEmbeddingPolicy": [Function], "vectorEmbeddingPolicy": [Function],
} }
} }
hotPartitionKeyRateLimitingPolicy={null}
hotPartitionKeyRateLimitingPolicyBaseline={null}
isAutoPilotSelected={false} isAutoPilotSelected={false}
isFixedContainer={false} isFixedContainer={false}
isGlobalSecondaryIndex={true} isGlobalSecondaryIndex={true}
onAutoPilotSelected={[Function]} onAutoPilotSelected={[Function]}
onHotPartitionKeyRateLimitingPolicyChange={[Function]}
onMaxAutoPilotThroughputChange={[Function]} onMaxAutoPilotThroughputChange={[Function]}
onScaleDiscardableChange={[Function]} onScaleDiscardableChange={[Function]}
onScaleSaveableChange={[Function]} onScaleSaveableChange={[Function]}
+3 -1
View File
@@ -894,7 +894,9 @@
"autoScaleCustomSettings": "Your account has custom settings that prevents setting throughput at the container level. Please work with your Cosmos DB engineering team point of contact to make changes.", "autoScaleCustomSettings": "Your account has custom settings that prevents setting throughput at the container level. Please work with your Cosmos DB engineering team point of contact to make changes.",
"keyspaceSharedThroughput": "This table shared throughput is configured at the keyspace", "keyspaceSharedThroughput": "This table shared throughput is configured at the keyspace",
"throughputRangeLabel": "Throughput ({{min}} - {{max}} RU/s)", "throughputRangeLabel": "Throughput ({{min}} - {{max}} RU/s)",
"unlimited": "unlimited" "unlimited": "unlimited",
"rateLimitingPolicyTitle": "Rate limiting policy",
"rateLimitPolicyMaxThroughputUtilizationLabel": "Max per partition key Throughput utilization"
}, },
"partitionKeyEditor": { "partitionKeyEditor": {
"changePartitionKey": "Change {{partitionKeyName}}", "changePartitionKey": "Change {{partitionKeyName}}",
+47
View File
@@ -0,0 +1,47 @@
import * as Constants from "../Common/Constants";
import { DatabaseAccount } from "../Contracts/DataModels";
import { updateUserContext } from "../UserContext";
import { isHotPartitionKeyThrottlingEnabled } from "./CapabilityUtils";
describe("CapabilityUtils", () => {
describe("isHotPartitionKeyThrottlingEnabled", () => {
it("returns true for a SQL account with the EnableHotPartitionKeyThrottling capability", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: Constants.CapabilityNames.EnableHotPartitionKeyThrottling }],
},
} as DatabaseAccount,
});
expect(isHotPartitionKeyThrottlingEnabled()).toBe(true);
});
it("returns false for a SQL account without the capability", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: Constants.CapabilityNames.EnableAutoScale }],
},
} as DatabaseAccount,
});
expect(isHotPartitionKeyThrottlingEnabled()).toBe(false);
});
it("returns false for a non-SQL account even with the capability", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [
{ name: Constants.CapabilityNames.EnableCassandra },
{ name: Constants.CapabilityNames.EnableHotPartitionKeyThrottling },
],
},
} as DatabaseAccount,
});
expect(isHotPartitionKeyThrottlingEnabled()).toBe(false);
});
});
});
+7
View File
@@ -34,3 +34,10 @@ export const isFullTextSearchPreviewFeaturesEnabled = (targetAccountOverride?: A
isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLFullTextSearchPreviewFeatures, targetAccountOverride) isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLFullTextSearchPreviewFeatures, targetAccountOverride)
); );
}; };
export const isHotPartitionKeyThrottlingEnabled = (targetAccountOverride?: AccountOverride): boolean => {
return (
userContext.apiType === "SQL" &&
isCapabilityEnabled(Constants.CapabilityNames.EnableHotPartitionKeyThrottling, targetAccountOverride)
);
};
@@ -1096,6 +1096,11 @@ export interface CassandraViewCreateUpdateProperties {
options?: CreateUpdateOptions; options?: CreateUpdateOptions;
} }
export interface HotPartitionKeyRateLimitingPolicy {
/* Maximum throughput utilization for partition keys (in percent) */
maximumPerPartitionKeyThroughputUtilizationPercent: number;
}
/* Cosmos DB resource throughput object. Either throughput is required or autoscaleSettings is required, but not both. */ /* Cosmos DB resource throughput object. Either throughput is required or autoscaleSettings is required, but not both. */
export interface ThroughputSettingsResource { export interface ThroughputSettingsResource {
/* Value of the Cosmos DB resource throughput. Either throughput is required or autoscaleSettings is required, but not both. */ /* Value of the Cosmos DB resource throughput. Either throughput is required or autoscaleSettings is required, but not both. */
@@ -1113,6 +1118,8 @@ export interface ThroughputSettingsResource {
readonly softAllowedMaximumThroughput?: string; readonly softAllowedMaximumThroughput?: string;
/* Array of throughput bucket limits to be applied to the Cosmos DB container */ /* Array of throughput bucket limits to be applied to the Cosmos DB container */
throughputBuckets?: ThroughputBucketResource[]; throughputBuckets?: ThroughputBucketResource[];
/* Object describing the Rate Limiting policy for Hot Partition Keys */
hotPartitionKeyRateLimitingPolicy?: HotPartitionKeyRateLimitingPolicy | null;
} }
/* Cosmos DB provisioned throughput settings object */ /* Cosmos DB provisioned throughput settings object */
+1
View File
@@ -98,6 +98,7 @@
"./src/Utils/Base64Utils.ts", "./src/Utils/Base64Utils.ts",
"./src/Utils/BlobUtils.ts", "./src/Utils/BlobUtils.ts",
"./src/Utils/CapabilityUtils.ts", "./src/Utils/CapabilityUtils.ts",
"./src/Utils/CapabilityUtils.test.ts",
"./src/Utils/CloudUtils.ts", "./src/Utils/CloudUtils.ts",
"./src/Utils/EndpointUtils.ts", "./src/Utils/EndpointUtils.ts",
"./src/Utils/GitHubUtils.test.ts", "./src/Utils/GitHubUtils.test.ts",