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

perf: reduce notifications on relationship remote state updates #9655

Merged
merged 4 commits into from
Feb 9, 2025
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
4 changes: 2 additions & 2 deletions packages/core-types/src/spec/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export interface ResourceMetaDocument {
links?: Links | PaginationLinks;
}

export interface SingleResourceDataDocument<T = StableExistingRecordIdentifier> {
export interface SingleResourceDataDocument<T = StableExistingRecordIdentifier, R = StableExistingRecordIdentifier> {
// the url or cache-key associated with the structured document
lid?: string;
links?: Links | PaginationLinks;
meta?: Meta;
data: T | null;
included?: T[];
included?: R[];
}

export interface CollectionResourceDataDocument<T = StableExistingRecordIdentifier> {
Expand Down
28 changes: 25 additions & 3 deletions packages/graph/src/-private/-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import replaceRelatedRecord from './operations/replace-related-record';
import replaceRelatedRecords from './operations/replace-related-records';

function _deprecatedCompare<T>(
priorLocalState: T[] | null,
newState: T[],
newMembers: Set<T>,
prevState: T[],
Expand All @@ -30,6 +31,7 @@ function _deprecatedCompare<T>(
const duplicates = new Map<T, number[]>();
const finalSet = new Set<T>();
const finalState: T[] = [];
const priorLocalLength = priorLocalState?.length ?? 0;

for (let i = 0, j = 0; i < iterationLength; i++) {
let adv = false;
Expand Down Expand Up @@ -69,6 +71,11 @@ function _deprecatedCompare<T>(
// j is always less than i and so if i < prevLength, j < prevLength
if (member !== prevState[j]) {
changed = true;
} else if (!changed && j < priorLocalLength) {
const priorLocalMember = priorLocalState![j];
if (priorLocalMember !== member) {
changed = true;
}
}

if (!newMembers.has(prevMember)) {
Expand Down Expand Up @@ -100,6 +107,7 @@ function _deprecatedCompare<T>(
}

function _compare<T>(
priorLocalState: T[] | null,
finalState: T[],
finalSet: Set<T>,
prevState: T[],
Expand All @@ -114,6 +122,7 @@ function _compare<T>(
let changed: boolean = finalSet.size !== prevSet.size;
const added = new Set<T>();
const removed = new Set<T>();
const priorLocalLength = priorLocalState?.length ?? 0;

for (let i = 0; i < iterationLength; i++) {
let member: T | undefined;
Expand All @@ -135,6 +144,11 @@ function _compare<T>(
// detect reordering
if (equalLength && member !== prevMember) {
changed = true;
} else if (equalLength && !changed && i < priorLocalLength) {
const priorLocalMember = priorLocalState![i];
if (priorLocalMember !== prevMember) {
changed = true;
}
}

if (!finalSet.has(prevMember)) {
Expand Down Expand Up @@ -169,11 +183,19 @@ export function diffCollection(
onDel: (v: StableRecordIdentifier) => void
): Diff<StableRecordIdentifier> {
const finalSet = new Set(finalState);
const { remoteState, remoteMembers } = relationship;
const { localState: priorLocalState, remoteState, remoteMembers } = relationship;

if (DEPRECATE_NON_UNIQUE_PAYLOADS) {
if (finalState.length !== finalSet.size) {
const { diff, duplicates } = _deprecatedCompare(finalState, finalSet, remoteState, remoteMembers, onAdd, onDel);
const { diff, duplicates } = _deprecatedCompare(
priorLocalState,
finalState,
finalSet,
remoteState,
remoteMembers,
onAdd,
onDel
);

if (DEBUG) {
deprecate(
Expand All @@ -199,7 +221,7 @@ export function diffCollection(
);
}

return _compare(finalState, finalSet, remoteState, remoteMembers, onAdd, onDel);
return _compare(priorLocalState, finalState, finalSet, remoteState, remoteMembers, onAdd, onDel);
}

export function computeLocalState(storage: CollectionEdge): StableRecordIdentifier[] {
Expand Down
43 changes: 28 additions & 15 deletions packages/graph/src/-private/operations/replace-related-records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function replaceRelatedRecordsLocal(graph: Graph, op: ReplaceRelatedRecordsOpera
const { inverseKey, type } = relationship.definition;
const { record } = op;
const wasDirty = relationship.isDirty;
relationship.isDirty = false;
let localBecameDirty = false;

const onAdd = (identifier: StableRecordIdentifier) => {
// Since we are diffing against the remote state, we check
Expand All @@ -105,7 +105,8 @@ function replaceRelatedRecordsLocal(graph: Graph, op: ReplaceRelatedRecordsOpera
graph.registerPolymorphicType(type, identifier.type);
}

relationship.isDirty = true;
// we've added a record locally that wasn't in the local state before
localBecameDirty = true;
addToInverse(graph, identifier, inverseKey, op.record, isRemote);

if (removalsHas) {
Expand All @@ -119,7 +120,8 @@ function replaceRelatedRecordsLocal(graph: Graph, op: ReplaceRelatedRecordsOpera
// if our previous local state had contained this identifier
const additionsHas = additions?.has(identifier);
if (additionsHas || !removals?.has(identifier)) {
relationship.isDirty = true;
// we've removed a record locally that was in the local state before
localBecameDirty = true;
removeFromInverse(graph, identifier, inverseKey, record, isRemote);

if (additionsHas) {
Expand All @@ -129,40 +131,39 @@ function replaceRelatedRecordsLocal(graph: Graph, op: ReplaceRelatedRecordsOpera
};

const diff = diffCollection(identifiers, relationship, onAdd, onRemove);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let becameDirty = relationship.isDirty || diff.changed;

// any additions no longer in the local state
// need to be removed from the inverse
// also need to be removed from the inverse
if (additions && additions.size > 0) {
additions.forEach((identifier) => {
if (!diff.add.has(identifier)) {
becameDirty = true;
localBecameDirty = true;
onRemove(identifier);
}
});
}

// any removals no longer in the local state
// need to be added back to the inverse
// also need to be added back to the inverse
if (removals && removals.size > 0) {
removals.forEach((identifier) => {
if (!diff.del.has(identifier)) {
becameDirty = true;
localBecameDirty = true;
onAdd(identifier);
}
});
}

const becameDirty = diff.changed || localBecameDirty;
relationship.additions = diff.add;
relationship.removals = diff.del;
relationship.localState = diff.finalState;
relationship.isDirty = wasDirty;

if (
isMaybeFirstUpdate ||
!wasDirty /*&& becameDirty // TODO to guard like this we need to detect reorder when diffing local */
) {
// we notify if this is the first update to the relationship
// because ?? may need to recalculate.
// otherwise we only notify if we are dirty and were not already dirty before
if (isMaybeFirstUpdate || (becameDirty && !wasDirty)) {
notifyChange(graph, op.record, op.field);
}
}
Expand All @@ -178,6 +179,9 @@ function replaceRelatedRecordsRemote(graph: Graph, op: ReplaceRelatedRecordsOper
if (isRemote) {
graph._addToTransaction(relationship);
}

// see note before flushCanonical
// const wasDirty = relationship.isDirty;
relationship.state.hasReceivedData = true;

// cache existing state
Expand Down Expand Up @@ -235,7 +239,13 @@ function replaceRelatedRecordsRemote(graph: Graph, op: ReplaceRelatedRecordsOper
if (DEPRECATE_RELATIONSHIP_REMOTE_UPDATE_CLEARING_LOCAL_STATE) {
// only do this for legacy hasMany, not collection
// and provide a way to incrementally migrate
if (relationship.definition.kind === 'hasMany' && relationship.definition.resetOnRemoteUpdate !== false) {
if (
// we do not guard by diff.changed here
// because we want to clear local changes even if
// no change has occurred to preserve the legacy behavior
relationship.definition.kind === 'hasMany' &&
relationship.definition.resetOnRemoteUpdate !== false
) {
const deprecationInfo: {
removals: StableRecordIdentifier[];
additions: StableRecordIdentifier[];
Expand Down Expand Up @@ -300,7 +310,10 @@ function replaceRelatedRecordsRemote(graph: Graph, op: ReplaceRelatedRecordsOper
}
}

if (relationship.isDirty) {
// we ought to only flush if we became dirty and were not before
// but this causes a fw test failures around unloadRecord and reference autotracking
// we should investigate this further
if (relationship.isDirty /*&& !wasDirty*/) {
flushCanonical(graph, relationship);
}
}
Expand Down
17 changes: 13 additions & 4 deletions packages/json-api/src/-private/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,16 +384,22 @@ export default class JSONAPICache implements Cache {
*/
mutate(mutation: LocalRelationshipOperation): void {
if (LOG_MUTATIONS) {
logGroup('cache', 'mutate', mutation.record.type, mutation.record.lid, mutation.field, mutation.op);
try {
const _data = JSON.parse(JSON.stringify(mutation)) as object;
// eslint-disable-next-line no-console
console.log(`EmberData | Mutation - update ${mutation.op}`, _data);
console.log(_data);
} catch {
// eslint-disable-next-line no-console
console.log(`EmberData | Mutation - update ${mutation.op}`, mutation);
console.log(mutation);
}
}
this.__graph.update(mutation, false);

if (LOG_MUTATIONS) {
// eslint-disable-next-line no-console
console.groupEnd();
}
}

/**
Expand Down Expand Up @@ -543,8 +549,6 @@ export default class JSONAPICache implements Cache {
// eslint-disable-next-line no-console
console.log(data);
}
// eslint-disable-next-line no-console
console.groupEnd();
}

if (cached.isNew) {
Expand Down Expand Up @@ -586,6 +590,11 @@ export default class JSONAPICache implements Cache {
notifyAttributes(this._capabilities, identifier, changedKeys);
}

if (LOG_OPERATIONS) {
// eslint-disable-next-line no-console
console.groupEnd();
}

return changedKeys?.size ? Array.from(changedKeys) : undefined;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TestContext } from '@ember/test-helpers';
import { settled } from '@ember/test-helpers';

import { module, test } from 'qunit';
Expand Down Expand Up @@ -201,7 +202,14 @@ function generateAppliedMutation(store: Store, record: User, mutation: Mutation)
};
}

async function applyMutation(assert: Assert, store: Store, record: User, mutation: Mutation, rc: ReactiveContext) {
async function applyMutation(
this: TestContext,
assert: Assert,
store: Store,
record: User,
mutation: Mutation,
rc: ReactiveContext
) {
assert.ok(true, `LOG: applying "${mutation.name}" with ids [${mutation.values.map((v) => v.id).join(',')}]`);

const { counters, fieldOrder } = rc;
Expand Down Expand Up @@ -436,8 +444,8 @@ export function runTestGroup(splitNum: number, offset: number) {
});
rc.reset();

await applyMutation(assert, store, user, mutation, rc);
await applyMutation(assert, store, user, mutation2, rc);
await applyMutation.call(this, assert, store, user, mutation, rc);
await applyMutation.call(this, assert, store, user, mutation2, rc);
});
});
}
Expand Down
Loading
Loading