Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shell-api): Account for orphan documents count in getShardDistribution() helper MONGOSH-1838 #2203

Merged
merged 24 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 18 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
64 changes: 64 additions & 0 deletions packages/shell-api/src/collection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2276,6 +2276,70 @@ describe('Collection', function () {
ShellApiErrors.NotConnectedToShardedCluster
);
});

describe('with orphan documents', function () {
const mockedNumChunks = 2;
const mockedCollectionConfigInfo = {};
const mockedShardStats = {
shard: 'test-shard',
storageStats: {
size: 1000,
numOrphanDocs: 10,
avgObjSize: 7,
count: 15,
},
};
const mockedShardInfo = {
host: 'dummy-host',
};

beforeEach(function () {
const serviceProviderCursor = stubInterface<ServiceProviderCursor>();

// Make find and limit have no effect so the value of findOne is determined by tryNext.
serviceProviderCursor.limit.returns(serviceProviderCursor);
serviceProvider.find.returns(serviceProviderCursor);

// Mock according to the order of findOne calls getShardDistribution uses.
serviceProviderCursor.tryNext
.onCall(0)
.resolves(mockedCollectionConfigInfo);
serviceProviderCursor.tryNext.onCall(1).resolves(mockedShardInfo);
serviceProvider.countDocuments.returns(
Promise.resolve(mockedNumChunks)
);

const aggregateTryNext = sinon.stub();
aggregateTryNext.onCall(0).resolves(mockedShardStats);
aggregateTryNext.onCall(1).resolves(null);

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
serviceProvider.aggregate.returns({
tryNext: aggregateTryNext,
} as any);
});

it('should account for numOrphanDocs when calculating size', async function () {
const shardDistribution = await collection.getShardDistribution();

const { storageStats } = mockedShardStats;
expect(shardDistribution.type).equals('StatsResult');
const adjustedSize =
storageStats.size -
storageStats.numOrphanDocs * storageStats.avgObjSize;
expect(shardDistribution.value.Totals.data).equals(
`${adjustedSize}B`
);
const shardField = Object.keys(shardDistribution.value).find(
(field) => field !== 'Totals'
) as string;

expect(shardField).not.undefined;
expect(
shardDistribution.value[shardField]['estimated data per chunk']
).equals(`${adjustedSize / mockedNumChunks}B`);
});
});
});

describe('analyzeShardKey', function () {
Expand Down
26 changes: 20 additions & 6 deletions packages/shell-api/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import { HIDDEN_COMMANDS } from '@mongosh/history';
import PlanCache from './plan-cache';
import ChangeStreamCursor from './change-stream-cursor';
import { ShellApiErrors } from './error-codes';
import type { GetShardDistributionResult } from './result';

@shellApiClassDefault
@addSourceToResults
Expand Down Expand Up @@ -2135,12 +2136,14 @@ export default class Collection extends ShellApiWithMongoClass {
@returnsPromise
@topologies([Topologies.Sharded])
@apiVersions([])
async getShardDistribution(): Promise<CommandResult> {
async getShardDistribution(): Promise<
CommandResult<GetShardDistributionResult>
> {
this._emitCollectionApiCall('getShardDistribution', {});

await getConfigDB(this._database); // Warns if not connected to mongos

const result = {} as Document;
const result = {} as GetShardDistributionResult;
const config = this._mongo.getDB('config');

const collStats = await (
Expand Down Expand Up @@ -2179,10 +2182,21 @@ export default class Collection extends ShellApiWithMongoClass {
.findOne({ _id: extractedShardStats.shard }),
config.getCollection('chunks').countDocuments(countChunksQuery),
]);

// Since 6.0, there can be orphan documents indicated by numOrphanDocs.
// These orphan documents need to be accounted for in the size calculation.
const orphanDocumentsSize = !extractedShardStats.storageStats
.numOrphanDocs
? 0
: extractedShardStats.storageStats.numOrphanDocs *
extractedShardStats.storageStats.avgObjSize;
const adjustedSize =
extractedShardStats.storageStats.size - orphanDocumentsSize;

const shardStats = {
shardId: shard,
host: host !== null ? host.host : null,
size: extractedShardStats.storageStats.size,
size: adjustedSize,
count: extractedShardStats.storageStats.count,
numChunks: numChunks,
avgObjSize: extractedShardStats.storageStats.avgObjSize,
Expand Down Expand Up @@ -2211,7 +2225,7 @@ export default class Collection extends ShellApiWithMongoClass {
'estimated docs per chunk': estimatedDocsPerChunk,
};

totals.size += coerceToJSNumber(shardStats.size);
totals.size += coerceToJSNumber(adjustedSize);
totals.count += coerceToJSNumber(shardStatsCount);
totals.numChunks += coerceToJSNumber(shardStats.numChunks);

Expand All @@ -2224,7 +2238,7 @@ export default class Collection extends ShellApiWithMongoClass {
data: dataFormat(totals.size),
docs: totals.count,
chunks: totals.numChunks,
} as Document;
} as GetShardDistributionResult['Totals'];

for (const shardStats of conciseShardsStats) {
const estDataPercent =
Expand All @@ -2243,7 +2257,7 @@ export default class Collection extends ShellApiWithMongoClass {
];
}
result.Totals = totalValue;
return new CommandResult('StatsResult', result);
return new CommandResult<GetShardDistributionResult>('StatsResult', result);
}

@serverVersions(['3.1.0', ServerVersions.latest])
Expand Down
18 changes: 18 additions & 0 deletions packages/shell-api/src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,21 @@ export class CursorIterationResult extends ShellApiValueClass {
this.documents = [];
}
}

export type GetShardDistributionResult = {
gagik marked this conversation as resolved.
Show resolved Hide resolved
Totals: {
data: string;
docs: number;
chunks: number;
} & {
[individualShardDistribution: string]: string[];
};
} & {
[individualShardResult: string]: {
gagik marked this conversation as resolved.
Show resolved Hide resolved
data: string;
docs: number;
chunks: number;
'estimated data per chunk': string;
'estimated docs per chunk': number;
};
};
Loading