-
Notifications
You must be signed in to change notification settings - Fork 672
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
Investigate potential memory leak issues with weakmapMemoize
#635
Comments
More notes from Lenz and me:
|
Lenz's latest example: import { weakMapMemoize, createSelectorCreator } from "reselect";
const memoize = createSelectorCreator(weakMapMemoize);
const selector = memoize(
(array: any[], from: number, to: number) => array,
(array: any[], from: number, to: number) => from,
(array: any[], from: number, to: number) => to,
(array: any[], from: number, to: number) => array.slice(from, to),
);
let state = Array(10000)
.fill(null)
.map((_, i) => i);
const initialResult = new WeakRef(selector(state, 2000, 2500));
for (let i = 0; i < 10000; i++) {
selector(state, i, i + 100);
}
const laterResult = selector(state, 2000, 2500);
console.log(
"results are still memoized?",
initialResult.deref() === laterResult,
); In one sense that works as intended. But I think the point is there ought to be some limits. |
I really like that you provide this new function However I am worried about the fact that it is now the default memoize function (BTW it's not clear in the doc but clear in the release announcement). As soon as we're using a primitive value in a selector, and the result of this selector is used in other selectors, we may have a chain of leaks (this depends on the other arguments too of course). (I'm more concerned about the The result of a selector may also have a significant size and we may have relied on the fact that reselect keeps just one copy, and suddenly this changes. (Though I understand that's what a major update is for). Of course we can always go back to the import { createSelectorCreator, lruMemoize } from 'reselect';
const createSelectorLru = createSelectorCreator({ memoize: lruMemoize, argsMemoize: lruMemoize }); Maybe such an export could be exported from Last bit of feedback: the I recognize this comment isn't super constructive, feel free to delete it if you feel it's useless for you. |
(This is adapted from this discord thread: https://discord.com/channels/102860784329052160/103538784460615680/1192629604284907651) ProblemI've recently updated a project to RTK2. Everything seemed fine, but we started getting user reports of stutters and performance issues. After investigating deeper, it appears that Specifically, we are getting very frequent major GC events when before we had virtually none. RTK2 without
|
Pending resolution of reduxjs/reselect#635, we can patch `reselect` to use `lruMemoize` exclusively. Pin RTK and react-redux versions too just to be safe. This reduces the major GC events that were causing lag/stutters in the app, particularly in canvas and workflow editor.
@psychedelicious I'll be very interested to see the repro, but based on your explanation I have a suspicion that either the combination of |
@aryaemami59 There's actually a separate issue I've not yet reported. Repro (increment the counter): https://codesandbox.io/p/sandbox/dazzling-kapitsa-gtqrl5?file=%2Fsrc%2Fapp%2Fstore.js%3A12%2C5 When I upgraded to RTK2, I had to choose between using Def could be related to Will work towards a repro to clarify. |
@psychedelicious On first glance, one thing that can cause a lot of issues is having |
@aryaemami59 I must have missed that memo (heh). Thanks for pointing that out - I've used I just went through the whole app and converted all input selectors to be like Unfortunately, this has had no discernable impact on the memory issue. Also, using |
Pending resolution of reduxjs/reselect#635, we can patch `reselect` to use `lruMemoize` exclusively. Pin RTK and react-redux versions too just to be safe. This reduces the major GC events that were causing lag/stutters in the app, particularly in canvas and workflow editor.
Pending resolution of reduxjs/reselect#635, we can patch `reselect` to use `lruMemoize` exclusively. Pin RTK and react-redux versions too just to be safe. This reduces the major GC events that were causing lag/stutters in the app, particularly in canvas and workflow editor.
Pending resolution of reduxjs/reselect#635, we can patch `reselect` to use `lruMemoize` exclusively. Pin RTK and react-redux versions too just to be safe. This reduces the major GC events that were causing lag/stutters in the app, particularly in canvas and workflow editor.
Updates for my issue
These improvements save about 4ms per animation frame on my computer, which is relevant for us. Thanks Arya! Unfortunately, the memory usage with
|
@psychedelicious I've opened a PR reduxjs/redux-toolkit#4048 to allow customising the |
Since updating from reselect 4.1.7 to 5.1.0, we have noticed a memory leak issue on our node servers. On that zabbix graph, the memory is released when node (v20.12.2) restart. After investigating the Heap snapshot, I discovered multiple references to previous selector values, which led me to suspect that the change in reselect version is the cause of this issue. As a temporary solution, I reverted back to using |
@paulgreg just to check, when you say "weakmaps were never released", do you mean "why didn't the weakmaps let go of the references they were holding on to"? |
@markerikson yes, exactly. |
Hello there, I confirm that the weakMapMemoize resulted in a memory leak on my side as well. My use case is a realtime application with very frequent updates on 2k elements (flights). 3 slices using entity adapters and its selectors to access those flights and their information. An example slice, if you find something weird import {
createEntityAdapter,
createSelectorCreator,
lruMemoize,
createSlice,
EntityState,
} from '@reduxjs/toolkit';
import { tracksActions } from '@hmi/core-features-tracks';
import type { TrackLabel } from './track-label.type';
// See issue https://github.com/reduxjs/reselect/issues/635#issuecomment-1850350607
const createSelector = createSelectorCreator({
memoize: lruMemoize,
});
export const TRACK_LABELS_FEATURE_KEY = 'trackLabels';
export interface TrackLabelsState extends EntityState<TrackLabel, string> {
loadingStatus: 'not loaded' | 'loading' | 'loaded' | 'error';
error?: string | null;
}
export const trackLabelsAdapter = createEntityAdapter<
TrackLabel,
TrackLabel['trackId']
>({
selectId: (model) => model.trackId,
});
export const initialTrackLabelsState: TrackLabelsState =
trackLabelsAdapter.getInitialState({
loadingStatus: 'not loaded',
error: null,
});
export const trackLabelsSlice = createSlice({
name: TRACK_LABELS_FEATURE_KEY,
initialState: initialTrackLabelsState,
reducers: {
add: trackLabelsAdapter.addOne,
remove: trackLabelsAdapter.removeOne,
removeMany: trackLabelsAdapter.removeMany,
upsertMany: trackLabelsAdapter.upsertMany,
upsertOne: trackLabelsAdapter.upsertOne,
},
extraReducers: (builder) => {
builder.addCase(tracksActions.removeMany, (state, action) => {
trackLabelsAdapter.removeMany(state, action.payload);
});
},
});
export const trackLabelsReducer = trackLabelsSlice.reducer;
export const trackLabelsActions = trackLabelsSlice.actions;
type LocalRootState = {
[TRACK_LABELS_FEATURE_KEY]: TrackLabelsState;
};
export const selectTrackLabelsState = (
rootState: LocalRootState,
): TrackLabelsState => rootState[TRACK_LABELS_FEATURE_KEY];
const { selectAll, selectEntities, selectIds, selectById } =
trackLabelsAdapter.getSelectors(undefined, { createSelector });
export const selectAllTrackLabels = selectAll;
export const selectTrackLabelsEntities = selectEntities;
export const selectTrackLabelsIds = selectIds;
export const selectTrackLabelById = createSelector(
[selectTrackLabelsState, (_: LocalRootState, id: string) => id],
(state, id) => selectById(state, id),
);
export const selectTrackLabelFieldByNameAndUid = createSelector(
[
selectTrackLabelsState,
(_, args: { trackId: string; fieldName: string; uid: string }) => args,
],
(trackLabelsState, { trackId, fieldName, uid }) =>
selectById(trackLabelsState, trackId)?.fields[fieldName]?.configValues?.[
uid
] ?? '',
); I am still unhappy about the memory consumption when the trafic increases, even if it goes down more often (like observed in the comments). |
@hpierre74 any chance you could turn that into a small repro? Usage patterns are going to affect this and it would help to see how you're dispatching and reading the values. If this is causing issues for you, you may want to switch back to |
Hey @markerikson, thanks for your answer. I sent you a PM on twitter with more info :) |
Copying notes from @phryneas :
The text was updated successfully, but these errors were encountered: