Skip to content

Commit 7c37580

Browse files
perf(NODE-6450): Lazy objectId hex string cache (#722)
Co-authored-by: Bailey Pearson <[email protected]>
1 parent 6e84925 commit 7c37580

File tree

2 files changed

+52
-26
lines changed

2 files changed

+52
-26
lines changed

src/objectid.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { NumberUtils } from './utils/number_utils';
77
// Unique sequence for the current process (initialized on first use)
88
let PROCESS_UNIQUE: Uint8Array | null = null;
99

10+
/** ObjectId hexString cache @internal */
11+
const __idCache = new WeakMap(); // TODO(NODE-6549): convert this to #__id private field when target updated to ES2022
12+
1013
/** @public */
1114
export interface ObjectIdLike {
1215
id: string | Uint8Array;
@@ -36,8 +39,6 @@ export class ObjectId extends BSONValue {
3639

3740
/** ObjectId Bytes @internal */
3841
private buffer!: Uint8Array;
39-
/** ObjectId hexString cache @internal */
40-
private __id?: string;
4142

4243
/**
4344
* Create ObjectId from a number.
@@ -111,6 +112,10 @@ export class ObjectId extends BSONValue {
111112
} else if (typeof workingId === 'string') {
112113
if (ObjectId.validateHexString(workingId)) {
113114
this.buffer = ByteUtils.fromHex(workingId);
115+
// If we are caching the hex string
116+
if (ObjectId.cacheHexString) {
117+
__idCache.set(this, workingId);
118+
}
114119
} else {
115120
throw new BSONError(
116121
'input must be a 24 character hex string, 12 byte Uint8Array, or an integer'
@@ -119,10 +124,6 @@ export class ObjectId extends BSONValue {
119124
} else {
120125
throw new BSONError('Argument passed in does not match the accepted types');
121126
}
122-
// If we are caching the hex string
123-
if (ObjectId.cacheHexString) {
124-
this.__id = ByteUtils.toHex(this.id);
125-
}
126127
}
127128

128129
/**
@@ -136,7 +137,7 @@ export class ObjectId extends BSONValue {
136137
set id(value: Uint8Array) {
137138
this.buffer = value;
138139
if (ObjectId.cacheHexString) {
139-
this.__id = ByteUtils.toHex(value);
140+
__idCache.set(this, ByteUtils.toHex(value));
140141
}
141142
}
142143

@@ -165,14 +166,15 @@ export class ObjectId extends BSONValue {
165166

166167
/** Returns the ObjectId id as a 24 lowercase character hex string representation */
167168
toHexString(): string {
168-
if (ObjectId.cacheHexString && this.__id) {
169-
return this.__id;
169+
if (ObjectId.cacheHexString) {
170+
const __id = __idCache.get(this);
171+
if (__id) return __id;
170172
}
171173

172174
const hexString = ByteUtils.toHex(this.id);
173175

174-
if (ObjectId.cacheHexString && !this.__id) {
175-
this.__id = hexString;
176+
if (ObjectId.cacheHexString) {
177+
__idCache.set(this, hexString);
176178
}
177179

178180
return hexString;
@@ -370,6 +372,11 @@ export class ObjectId extends BSONValue {
370372
return new ObjectId(doc.$oid);
371373
}
372374

375+
/** @internal */
376+
private isCached(): boolean {
377+
return ObjectId.cacheHexString && __idCache.has(this);
378+
}
379+
373380
/**
374381
* Converts to a string representation of this Id.
375382
*

test/node/bson_test.js

+34-15
Original file line numberDiff line numberDiff line change
@@ -1573,36 +1573,55 @@ describe('BSON', function () {
15731573
*/
15741574
it('ObjectId should have a correct cached representation of the hexString', function (done) {
15751575
ObjectId.cacheHexString = true;
1576+
// generated ObjectID uses lazy caching
15761577
var a = new ObjectId();
1577-
var __id = a.__id;
1578-
expect(__id).to.equal(a.toHexString());
1579-
1580-
// hexString
1581-
a = new ObjectId(__id);
1582-
expect(__id).to.equal(a.toHexString());
1578+
expect(a.isCached()).to.be.false;
1579+
a.toHexString();
1580+
expect(a.isCached()).to.be.true;
1581+
expect(a.toHexString()).to.equal(a.toHexString());
1582+
1583+
// hexString caches immediately
1584+
a = new ObjectId(a.toHexString());
1585+
expect(a.isCached()).to.be.true;
1586+
a.toHexString();
1587+
expect(a.isCached()).to.be.true;
1588+
expect(a.toHexString()).to.equal(a.toHexString());
15831589

15841590
// fromHexString
1585-
a = ObjectId.createFromHexString(__id);
1586-
expect(a.__id).to.equal(a.toHexString());
1587-
expect(__id).to.equal(a.toHexString());
1591+
a = ObjectId.createFromHexString(a.toHexString());
1592+
expect(a.isCached()).to.be.false;
1593+
a.toHexString();
1594+
expect(a.isCached()).to.be.true;
1595+
expect(a.toHexString()).to.equal(a.toHexString());
15881596

15891597
// number
15901598
var genTime = a.generationTime;
15911599
a = new ObjectId(genTime);
1592-
__id = a.__id;
1593-
expect(__id).to.equal(a.toHexString());
1600+
expect(a.isCached()).to.be.false;
1601+
a.toHexString();
1602+
expect(a.isCached()).to.be.true;
1603+
expect(a.toHexString()).to.equal(a.toHexString());
15941604

15951605
// generationTime
1596-
delete a.__id;
15971606
a.generationTime = genTime;
1598-
expect(__id).to.equal(a.toHexString());
1607+
expect(a.isCached()).to.be.true;
1608+
expect(a.toHexString()).to.equal(a.toHexString());
15991609

16001610
// createFromTime
16011611
a = ObjectId.createFromTime(genTime);
1602-
__id = a.__id;
1603-
expect(__id).to.equal(a.toHexString());
1612+
expect(a.isCached()).to.be.false;
1613+
a.toHexString();
1614+
expect(a.isCached()).to.be.true;
1615+
expect(a.toHexString()).to.equal(a.toHexString());
1616+
16041617
ObjectId.cacheHexString = false;
16051618

1619+
// No longer caches after cache is disabled
1620+
a = new ObjectId();
1621+
expect(a.isCached()).to.be.false;
1622+
a.toHexString();
1623+
expect(a.isCached()).to.be.false;
1624+
16061625
done();
16071626
});
16081627

0 commit comments

Comments
 (0)