Skip to content

Commit 2eeb0eb

Browse files
Explicit byte size accounting (#2689)
1 parent 346af4c commit 2eeb0eb

File tree

2 files changed

+78
-21
lines changed

2 files changed

+78
-21
lines changed

packages/firestore/src/model/proto_values.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import * as api from '../protos/firestore_proto_api';
1919

2020
import { TypeOrder } from './field_value';
2121
import { assert, fail } from '../util/assert';
22-
import { keys, size } from '../util/obj';
22+
import { forEach, keys, size } from '../util/obj';
2323
import { ByteString } from '../util/byte_string';
2424
import {
2525
numericComparator,
@@ -417,6 +417,62 @@ function canonifyArray(arrayValue: api.ArrayValue): string {
417417
return result + ']';
418418
}
419419

420+
/**
421+
* Returns an approximate (and wildly inaccurate) in-memory size for the field
422+
* value.
423+
*
424+
* The memory size takes into account only the actual user data as it resides
425+
* in memory and ignores object overhead.
426+
*/
427+
export function estimateByteSize(value: api.Value): number {
428+
if ('nullValue' in value) {
429+
return 4;
430+
} else if ('booleanValue' in value) {
431+
return 4;
432+
} else if ('integerValue' in value) {
433+
return 8;
434+
} else if ('doubleValue' in value) {
435+
return 8;
436+
} else if ('timestampValue' in value) {
437+
// TODO(mrschmidt: Add ServerTimestamp support
438+
// Timestamps are made up of two distinct numbers (seconds + nanoseconds)
439+
return 16;
440+
} else if ('stringValue' in value) {
441+
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures:
442+
// "JavaScript's String type is [...] a set of elements of 16-bit unsigned
443+
// integer values"
444+
return value.stringValue!.length * 2;
445+
} else if ('bytesValue' in value) {
446+
return normalizeByteString(value.bytesValue!).approximateByteSize();
447+
} else if ('referenceValue' in value) {
448+
return value.referenceValue!.length;
449+
} else if ('geoPointValue' in value) {
450+
// GeoPoints are made up of two distinct numbers (latitude + longitude)
451+
return 16;
452+
} else if ('arrayValue' in value) {
453+
return estimateArrayByteSize(value.arrayValue!);
454+
} else if ('mapValue' in value) {
455+
return estimateMapByteSize(value.mapValue!);
456+
} else {
457+
return fail('Invalid value type: ' + JSON.stringify(value));
458+
}
459+
}
460+
461+
function estimateMapByteSize(mapValue: api.MapValue): number {
462+
let size = 0;
463+
forEach(mapValue.fields || {}, (key, val) => {
464+
size += key.length + estimateByteSize(val);
465+
});
466+
return size;
467+
}
468+
469+
function estimateArrayByteSize(arrayValue: api.ArrayValue): number {
470+
return (arrayValue.values || []).reduce(
471+
(previousSize, value) => previousSize + estimateByteSize(value),
472+
0
473+
);
474+
}
475+
420476
/**
421477
* Converts the possible Proto values for a timestamp value into a "seconds and
422478
* nanos" representation.

packages/firestore/test/unit/model/field_value.test.ts

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
ObjectValue,
3030
PrimitiveValue
3131
} from '../../../src/model/proto_field_value';
32-
import { canonicalId } from '../../../src/model/proto_values';
32+
import { canonicalId, estimateByteSize } from '../../../src/model/proto_values';
3333
import { ByteString } from '../../../src/util/byte_string';
3434
import { primitiveComparator } from '../../../src/util/misc';
3535
import * as typeUtils from '../../../src/util/types';
@@ -630,9 +630,7 @@ describe('FieldValue', () => {
630630
);
631631
});
632632

633-
// TODO(mrschmidt): Fix size accounting
634-
// eslint-disable-next-line no-restricted-properties
635-
it.skip('estimates size correctly for fixed sized values', () => {
633+
it('estimates size correctly for fixed sized values', () => {
636634
// This test verifies that each member of a group takes up the same amount
637635
// of space in memory (based on its estimated in-memory size).
638636
const equalityGroups = [
@@ -653,22 +651,23 @@ describe('FieldValue', () => {
653651
expectedByteSize: 16,
654652
elements: [wrap(Timestamp.fromMillis(100)), wrap(Timestamp.now())]
655653
},
654+
// TODO(mrschmidt): Support server timestamps
655+
// {
656+
// expectedByteSize: 16,
657+
// elements: [
658+
// new ServerTimestampValue(Timestamp.fromMillis(100), null),
659+
// new ServerTimestampValue(Timestamp.now(), null)
660+
// ]
661+
// },
662+
// {
663+
// expectedByteSize: 20,
664+
// elements: [
665+
// new ServerTimestampValue(Timestamp.fromMillis(100), wrap(true)),
666+
// new ServerTimestampValue(Timestamp.now(), wrap(false))
667+
// ]
668+
// },
656669
{
657-
expectedByteSize: 16,
658-
elements: [
659-
new ServerTimestampValue(Timestamp.fromMillis(100), null),
660-
new ServerTimestampValue(Timestamp.now(), null)
661-
]
662-
},
663-
{
664-
expectedByteSize: 20,
665-
elements: [
666-
new ServerTimestampValue(Timestamp.fromMillis(100), wrap(true)),
667-
new ServerTimestampValue(Timestamp.now(), wrap(false))
668-
]
669-
},
670-
{
671-
expectedByteSize: 11,
670+
expectedByteSize: 42,
672671
elements: [
673672
wrapRef(dbId('p1', 'd1'), key('c1/doc1')),
674673
wrapRef(dbId('p2', 'd2'), key('c2/doc2'))
@@ -684,7 +683,9 @@ describe('FieldValue', () => {
684683

685684
for (const group of equalityGroups) {
686685
for (const element of group.elements) {
687-
expect(element.approximateByteSize()).to.equal(group.expectedByteSize);
686+
expect(estimateByteSize(element.proto)).to.equal(
687+
group.expectedByteSize
688+
);
688689
}
689690
}
690691
});

0 commit comments

Comments
 (0)