-
Notifications
You must be signed in to change notification settings - Fork 80
feat(shell-api): add shardedDataDistribution to sh.status() MONGOSH-1326 #2214
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
Changes from all commits
d082248
fc93cbd
6fc82eb
fa4f17f
e7f0eef
7ae0ac7
9fb806d
4842194
f71faa0
789ba83
80ac653
05f6b33
2ef07db
631c92f
0083086
7c606db
46adbb2
118574c
c6f5f86
f7bc2b5
9bf0251
f12bf83
f25c329
c4f06b9
178b616
9f26347
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ import type { | |
bson, | ||
} from '@mongosh/service-provider-core'; | ||
import type { ClientSideFieldLevelEncryptionOptions } from './field-level-encryption'; | ||
import { type AutoEncryptionOptions } from 'mongodb'; | ||
import type { AutoEncryptionOptions, Long, ObjectId, Timestamp } from 'mongodb'; | ||
import { shellApiType } from './enums'; | ||
import type { AbstractCursor } from './abstract-cursor'; | ||
import type ChangeStreamCursor from './change-stream-cursor'; | ||
|
@@ -226,8 +226,8 @@ export function processDigestPassword( | |
export async function getPrintableShardStatus( | ||
configDB: Database, | ||
verbose: boolean | ||
): Promise<Document> { | ||
const result = {} as any; | ||
): Promise<ShardingStatusResult> { | ||
const result = {} as ShardingStatusResult; | ||
|
||
// configDB is a DB object that contains the sharding metadata of interest. | ||
const mongosColl = configDB.getCollection('mongos'); | ||
|
@@ -259,9 +259,12 @@ export async function getPrintableShardStatus( | |
); | ||
} | ||
|
||
result.shardingVersion = version; | ||
result.shardingVersion = version as { | ||
_id: number; | ||
clusterId: ObjectId; | ||
}; | ||
|
||
result.shards = shards; | ||
result.shards = shards as ShardingStatusResult['shards']; | ||
|
||
// (most recently) active mongoses | ||
const mongosActiveThresholdMs = 60000; | ||
|
@@ -280,9 +283,8 @@ export async function getPrintableShardStatus( | |
} | ||
} | ||
|
||
mongosAdjective = `${mongosAdjective} mongoses`; | ||
if (mostRecentMongosTime === null) { | ||
result[mongosAdjective] = 'none'; | ||
result[`${mongosAdjective} mongoses`] = 'none'; | ||
} else { | ||
const recentMongosQuery = { | ||
ping: { | ||
|
@@ -295,25 +297,27 @@ export async function getPrintableShardStatus( | |
}; | ||
|
||
if (verbose) { | ||
result[mongosAdjective] = await (await mongosColl.find(recentMongosQuery)) | ||
result[`${mongosAdjective} mongoses`] = await ( | ||
await mongosColl.find(recentMongosQuery) | ||
) | ||
.sort({ ping: -1 }) | ||
.toArray(); | ||
} else { | ||
result[mongosAdjective] = ( | ||
result[`${mongosAdjective} mongoses`] = ( | ||
(await ( | ||
await mongosColl.aggregate([ | ||
{ $match: recentMongosQuery }, | ||
{ $group: { _id: '$mongoVersion', num: { $sum: 1 } } }, | ||
{ $sort: { num: -1 } }, | ||
]) | ||
).toArray()) as any[] | ||
).toArray()) as { _id: string; num: number }[] | ||
).map((z: { _id: string; num: number }) => { | ||
return { [z._id]: z.num }; | ||
}); | ||
} | ||
} | ||
|
||
const balancerRes: Record<string, any> = {}; | ||
const balancerRes = {} as ShardingStatusResult['balancer']; | ||
await Promise.all([ | ||
(async (): Promise<void> => { | ||
// Is autosplit currently enabled | ||
|
@@ -331,13 +335,13 @@ export async function getPrintableShardStatus( | |
})(), | ||
(async (): Promise<void> => { | ||
// Is the balancer currently active | ||
let balancerRunning = 'unknown'; | ||
let balancerRunning: 'yes' | 'no' | 'unknown' = 'unknown'; | ||
try { | ||
const balancerStatus = await configDB.adminCommand({ | ||
balancerStatus: 1, | ||
}); | ||
balancerRunning = balancerStatus.inBalancerRound ? 'yes' : 'no'; | ||
} catch (err: any) { | ||
} catch { | ||
// pass, ignore all error messages | ||
} | ||
balancerRes['Currently running'] = balancerRunning; | ||
|
@@ -364,7 +368,7 @@ export async function getPrintableShardStatus( | |
if (activeLocks?.length > 0) { | ||
balancerRes['Collections with active migrations'] = activeLocks.map( | ||
(lock) => { | ||
return `${lock._id} started at ${lock.when}`; | ||
return `${lock._id} started at ${lock.when}` as const; | ||
} | ||
); | ||
} | ||
|
@@ -418,8 +422,23 @@ export async function getPrintableShardStatus( | |
const yesterday = new Date(); | ||
yesterday.setDate(yesterday.getDate() - 1); | ||
|
||
type MigrationResult = | ||
| { | ||
_id: 'Success'; | ||
count: number; | ||
from: never; | ||
to: never; | ||
} | ||
// Failed migration | ||
| { | ||
_id: string; | ||
count: number; | ||
from: string; | ||
to: string; | ||
}; | ||
|
||
// Successful migrations. | ||
let migrations = await ( | ||
let migrations = (await ( | ||
await changelogColl.aggregate([ | ||
{ | ||
$match: { | ||
|
@@ -437,11 +456,11 @@ export async function getPrintableShardStatus( | |
}, | ||
}, | ||
]) | ||
).toArray(); | ||
).toArray()) as MigrationResult[]; | ||
|
||
// Failed migrations. | ||
migrations = migrations.concat( | ||
await ( | ||
(await ( | ||
await changelogColl.aggregate([ | ||
{ | ||
$match: { | ||
|
@@ -472,11 +491,12 @@ export async function getPrintableShardStatus( | |
}, | ||
}, | ||
]) | ||
).toArray() | ||
).toArray()) as MigrationResult[] | ||
); | ||
|
||
const migrationsRes: Record<number, string> = {}; | ||
migrations.forEach((x: any) => { | ||
const migrationsRes: ShardingStatusResult['balancer']['Migration Results for the last 24 hours'] = | ||
{}; | ||
migrations.forEach((x) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note for the future, but if you run into existing |
||
if (x._id === 'Success') { | ||
migrationsRes[x.count] = x._id; | ||
} else { | ||
|
@@ -500,7 +520,7 @@ export async function getPrintableShardStatus( | |
// All databases in config.databases + those implicitly referenced | ||
// by a sharded collection in config.collections | ||
// (could become a single pipeline using $unionWith when we drop 4.2 server support) | ||
const [databases, collections] = await Promise.all([ | ||
const [databases, collections, shardedDataDistribution] = await Promise.all([ | ||
(async () => | ||
await (await configDB.getCollection('databases').find()) | ||
.sort({ _id: 1 }) | ||
|
@@ -513,7 +533,22 @@ export async function getPrintableShardStatus( | |
) | ||
.sort({ _id: 1 }) | ||
.toArray())(), | ||
(async () => { | ||
try { | ||
// $shardedDataDistribution is available since >= 6.0.3 | ||
const adminDB = configDB.getSiblingDB('admin'); | ||
return (await ( | ||
await adminDB.aggregate([{ $shardedDataDistribution: {} }]) | ||
).toArray()) as ShardedDataDistribution; | ||
} catch { | ||
// Pass, most likely an older version. | ||
return undefined; | ||
} | ||
})(), | ||
]); | ||
|
||
result.shardedDataDistribution = shardedDataDistribution; | ||
|
||
// Special case the config db, since it doesn't have a record in config.databases. | ||
databases.push({ _id: 'config', primary: 'config', partitioned: true }); | ||
|
||
|
@@ -648,6 +683,65 @@ export async function getPrintableShardStatus( | |
return result; | ||
} | ||
|
||
export type ShardingStatusResult = { | ||
shardingVersion: { | ||
_id: number; | ||
clusterId: ObjectId; | ||
/** This gets deleted when it is returned from getPrintableShardStatus */ | ||
currentVersion?: number; | ||
}; | ||
shards: { | ||
_id: string; | ||
host: string; | ||
state: number; | ||
tags: string[]; | ||
topologyTime: Timestamp; | ||
replSetConfigVersion: Long; | ||
}[]; | ||
[mongoses: `${string} mongoses`]: | ||
| 'none' | ||
| { | ||
[version: string]: | ||
| number | ||
| { | ||
up: number; | ||
waiting: boolean; | ||
}; | ||
}[]; | ||
autosplit: { | ||
'Currently enabled': 'yes' | 'no'; | ||
}; | ||
balancer: { | ||
'Currently enabled': 'yes' | 'no'; | ||
'Currently running': 'yes' | 'no' | 'unknown'; | ||
'Failed balancer rounds in last 5 attempts': number; | ||
'Migration Results for the last 24 hours': | ||
| 'No recent migrations' | ||
| { | ||
[count: number]: | ||
| 'Success' | ||
| `Failed with error '${string}', from ${string} to ${string}`; | ||
}; | ||
'Balancer active window is set between'?: `${string} and ${string} server local time`; | ||
'Last reported error'?: string; | ||
'Time of Reported error'?: string; | ||
'Collections with active migrations'?: `${string} started at ${string}`[]; | ||
}; | ||
shardedDataDistribution?: ShardedDataDistribution; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am adding it as a separate field at the moment since that's just a simple solution, but we could make it cleaner/nicer for the user. Open to suggestions |
||
databases: { database: Document; collections: Document }[]; | ||
}; | ||
|
||
export type ShardedDataDistribution = { | ||
gagik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ns: string; | ||
shards: { | ||
shardName: string; | ||
numOrphanedDocs: number; | ||
numOwnedDocuments: number; | ||
orphanedSizeBytes: number; | ||
ownedSizeBytes: number; | ||
}[]; | ||
}[]; | ||
|
||
export async function getConfigDB(db: Database): Promise<Database> { | ||
const helloResult = await db._maybeCachedHello(); | ||
if (helloResult.msg !== 'isdbgrid') { | ||
|
Uh oh!
There was an error while loading. Please reload this page.