Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
74 changes: 72 additions & 2 deletions tests/unit/utils/S3UtilsMongoClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const assert = require('assert');
const werelogs = require('werelogs');
const { BucketInfo, ObjectMD, ObjectMDArchive } = require('arsenal').models;
const { MongoMemoryReplSet } = require('mongodb-memory-server');
const { Long } = require('mongodb');
const { constants, errors } = require('arsenal');
const S3UtilsMongoClient = require('../../../utils/S3UtilsMongoClient');
const {
Expand All @@ -18,6 +19,7 @@ const {

const logger = new werelogs.Logger('S3UtilsMongoClient', 'debug', 'debug');
const USERSBUCKET = '__usersbucket';
const INFOSTORE = '__infostore';

const mongoTestClient = new S3UtilsMongoClient({});

Expand Down Expand Up @@ -3127,13 +3129,13 @@ describe('S3UtilsMongoClient, update inflight deltas', () => {
{
_id: 'bucket_bucket1_1715849127256',
usedCapacity: {
_inflight: 3000n,
_inflight: 3000,
},
},
{
_id: 'bucket_bucket2_1715849127257',
usedCapacity: {
_inflight: 5000n,
_inflight: 5000,
},
},
],
Expand All @@ -3151,6 +3153,74 @@ describe('S3UtilsMongoClient, update inflight deltas', () => {
// as they belong to this account: 2000 + 2900 + 3500
assert.strictEqual(output[2].usedCapacity.current, 8400n);
});

// `updateInflightDeltas` mutates the entries it is given, so build a fresh set
// rather than sharing `metrics` with the tests above.
const longMetrics = () => [
{
_id: 'bucket_bucket1_1715849127256',
accountOwnerID: '1234',
usedCapacity: { current: 1000n, _inflightsPreScan: 100n },
},
{
_id: 'bucket_bucket2_1715849127257',
accountOwnerID: '1234',
usedCapacity: { current: 1000n, _inflightsPreScan: 1500n },
},
{
_id: 'account_1234',
usedCapacity: { current: 2000n },
},
].map(entry => S3UtilsMongoClient.convertNumberToLong(entry));

it('should compute the inflights deltas with the types mongodb returns', async () => {
// `__infostore` stores BSON Longs and the driver promotes them to Numbers on
// read, while the metrics have already been through `convertNumberToLong` by
// the time this runs: neither side of the arithmetic is a BigInt in production.
const collection = await client.getCollection(INFOSTORE);
await collection.deleteMany({});
await collection.insertMany([
{
_id: 'bucket_bucket1_1715849127256',
usedCapacity: { _inflight: Long.fromNumber(3000) },
},
{
_id: 'bucket_bucket2_1715849127257',
usedCapacity: { _inflight: Long.fromNumber(5000) },
},
]);

const output = await client.updateInflightDeltas(longMetrics(), logger);

// first bucket: 1000 current + (3000 post scan - 100 pre scan) = 3900
assert.strictEqual(BigInt(output[0].usedCapacity.current), 3900n);
// second bucket: 1000 current + (5000 post scan - 1500 pre scan) = 4500
assert.strictEqual(BigInt(output[1].usedCapacity.current), 4500n);
// account: 2000 current + 2900 + 3500 = 8400
assert.strictEqual(BigInt(output[2].usedCapacity.current), 8400n);
});

it('should compute the inflights delta when there was no pre scan value', async () => {
// `_inflightsPreScan` is only stored when non-zero, so a bucket which only
// gained inflights during the scan carries no pre scan value at all.
const collection = await client.getCollection(INFOSTORE);
await collection.deleteMany({});
await collection.insertOne({
_id: 'bucket_bucket1_1715849127256',
usedCapacity: { _inflight: Long.fromNumber(3000) },
});

const output = await client.updateInflightDeltas([
S3UtilsMongoClient.convertNumberToLong({
_id: 'bucket_bucket1_1715849127256',
accountOwnerID: '1234',
usedCapacity: { current: 1000n },
}),
], logger);

// 1000 current + (3000 post scan - 0 pre scan) = 4000
assert.strictEqual(BigInt(output[0].usedCapacity.current), 4000n);
});
});

describe('S3UtilsMongoClient, cold object helpers', () => {
Expand Down
14 changes: 11 additions & 3 deletions utils/S3UtilsMongoClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const __COUNT_ITEMS = 'countitems';
const BigIntMax = (...args) => args.reduce((max, current) => {
const maxAsBigInt = BigInt(max);
const currentAsBigInt = BigInt(current);
return maxAsBigInt > currentAsBigInt ? max : current;
return maxAsBigInt > currentAsBigInt ? maxAsBigInt : currentAsBigInt;
});

const baseMetricsObject = {
Expand Down Expand Up @@ -74,7 +74,10 @@ class S3UtilsMongoClient extends MongoClientInterface {
// convert inflights to a map with _id: usedCapacity._inflight
const inflightsMap = {};
for (const inflight of inflights) {
const inflightValue = inflight.usedCapacity?._inflight || 0n;
// `_inflight` is stored as a BSON Long and the driver promotes it to a
// Number on read, so it has to be converted before being mixed with
// the BigInt metrics below.
const inflightValue = BigInt(inflight.usedCapacity?._inflight ?? 0);
inflightsMap[inflight._id] = inflightValue;
}

Expand All @@ -83,7 +86,12 @@ class S3UtilsMongoClient extends MongoClientInterface {
const id = entry._id;
if (id.startsWith('bucket_')) {
const inflightDocument = inflightsMap[id];
const inflight = inflightDocument ? BigIntMax(0n, inflightDocument - entry.usedCapacity._inflightsPreScan) : 0n;
// The metrics have been through `convertNumberToLong`, and the
// field is only stored when non-zero.
const preScan = BigInt(entry.usedCapacity._inflightsPreScan ?? 0);
const inflight = inflightDocument
? BigIntMax(0n, inflightDocument - preScan)
: 0n;
if (inflight) {
// Inflights remaining after the scan are part of the "current" bytes,
// and stored in _inflightsDelta
Expand Down
Loading