Skip to content

[BUG] Memory leak when migrating from v5 to v6 #1138

Open

Description

@andresz1

Is there an existing issue for this?

  • I have searched the existing issues

SDK Version

6.3.1

Current Behavior

Memory increases over time and is not garbage collected

Expected Behavior

Memory is like it was before

Steps To Reproduce

We rewrote the optimizely instance creation from:

const DEFAULT_DATAFILE_OPTIONS = {
  autoUpdate: true,
  updateInterval: 5 * 60 * 1000 // 5 minutes
}

const DEFAULT_EVENTS_OPTIONS = {
  batchSize: 10,
  flushInterval: 1000
}

const DEFAULT_TIMEOUT = 500

const LOGGER_LEVEL = process.env.NODE_ENV === 'production' ? ERROR : INFO

export default class OptimizelyAdapter {
  static createOptimizelyInstance({
    options: optionParameter,
    sdkKey,
    datafile,
    optimizely = optimizelySDK,
    eventDispatcher = optimizelySDK.eventDispatcher
  }) {
    const options = {...DEFAULT_DATAFILE_OPTIONS, ...optionParameter}
    optimizely.setLogLevel(LOGGER_LEVEL)
    optimizely.setLogger(optimizely.logging.createLogger())
    if (!datafile && typeof window !== 'undefined' && window.__INITIAL_CONTEXT_VALUE__?.pde) {
      datafile = window.__INITIAL_CONTEXT_VALUE__.pde
      sdkKey = undefined
    }

    const isServer = typeof window === 'undefined'
    const optimizelyInstance = optimizely.createInstance({
      sdkKey,
      datafileOptions: options,
      datafile,
      eventDispatcher,
      ...DEFAULT_EVENTS_OPTIONS,
      defaultDecideOptions: isServer ? [optimizely.OptimizelyDecideOption.DISABLE_DECISION_EVENT] : []
    })

    return optimizelyInstance
  }
}

To:

const DEFAULT_DATAFILE_OPTIONS = {
  autoUpdate: true,
  updateInterval: 5 * 60 * 1000 // 5 minutes
}

const DEFAULT_EVENTS_OPTIONS = {
  batchSize: 10,
  flushInterval: 1000
}

const DEFAULT_TIMEOUT = 500

const LOGGER_LEVEL = process.env.NODE_ENV === 'production' ? ERROR : INFO

export default class OptimizelyAdapter {
  static createOptimizelyInstance({options: optionParameter, sdkKey, datafile}) {
    if (!datafile && typeof window !== 'undefined' && window.__INITIAL_CONTEXT_VALUE__?.pde) {
      datafile = window.__INITIAL_CONTEXT_VALUE__.pde
      sdkKey = undefined
    }

    const isServer = typeof window === 'undefined'

    const projectConfigManager = sdkKey
      ? createPollingProjectConfigManager({sdkKey, datafile, ...DEFAULT_DATAFILE_OPTIONS, ...optionParameter})
      : createStaticProjectConfigManager({datafile})

    const eventProcessor = createBatchEventProcessor({
      ...DEFAULT_EVENTS_OPTIONS,
      ...optionParameter
    })

    const odpManager = createOdpManager()

    const logger = createLogger({
      level: LOGGER_LEVEL
    })

    return createInstance({
      projectConfigManager,
      eventProcessor,
      odpManager,
      logger,
      defaultDecideOptions: isServer ? [OptimizelyDecideOption.DISABLE_DECISION_EVENT] : []
    })
  }
}

The instance is created in the server during bootstrap and we just create one. We would like to keep the current behaviour as it is (not using the disposable flag). I will try to do some memory snapshots but I was wondering is something is wrong since we only changed this part following your migration from v5 to v6 guide

SDK Type

Browser

Node Version

JS SDK

Browsers impacted

No response

Link

No response

Logs

No response

Severity

No response

Workaround/Solution

No response

Recent Change

No response

Conflicts

No response

Activity

  1. changed the title [-][BUG] <title>Memory leak when migrating from v5 to v6[/-] [+][BUG] Memory leak when migrating from v5 to v6[/+] on Feb 12, 2026
  2. junaed-optimizely commented on Feb 13, 2026

    @junaed-optimizely
    Contributor

    @andresz1, Thanks for reporting this. Your instance creation code looks correct based on the migration guide. To help us investigate, could you provide a few more details?

    • Node version and runtime environment (e.g., Express, Next.js, NestJS, etc.)
    • How are you using the instance and more importantly, how are you calling the decision APIs? It would be nice to have more implementation details.
    • Memory snapshots (If possible)

    The v6 architecture creates separate service objects (event processor, config manager, ODP manager) each with their own internal queues, timers, and event emitters. If the instance is created once and reused, these should remain stable - but knowing the call patterns above will help us pinpoint where the growth is happening.

  3. andresz1 commented on Feb 16, 2026

    @andresz1
    Author
    • Node version: 22.18.0
    • Runtime: Express.js 4.22.0. Server-side rendered application using React.
    • The instance is instantiated once on the server side during server bootstrap. The polling config manager is used to refresh the datafile every 5 minutes. The optimizely.close() is never called since we just use one instance. Here you have the wrapper that we created and I migrated from v5 to v6.

    Normally we call the decision API this way.

    const {enabled} = useDecision('key')

    Here you have the useDecision and the decide function wrapper code.

    We noticed the memory leak because, with only those changes, the memory usage increased exponentially in production. Will try to provide some memory snapshots soon.

    cc @junaed-optimizely

  4. julian-schwab commented on Feb 24, 2026

    @julian-schwab

    I can confirm that we also experienced issues with the SDK version 6.3.0.
    We had increased memory in some nodejs services (both with node 22 and 24) and in other services our maximum nodejs eventloop lag increased significantly with version 6.3.0 compared to version 6.2.0.
    We rolled back to version 6.2.0 for now where we don't have these issues.

  5. andresz1 commented on Feb 24, 2026

    @andresz1
    Author

    I can confirm that we also experienced issues with the SDK version 6.3.0. We had increased memory in some nodejs services (both with node 22 and 24) and in other services our maximum nodejs eventloop lag increased significantly with version 6.3.0 compared to version 6.2.0. We rolled back to version 6.2.0 for now where we don't have these issues.

    Thank you @julian-schwab will try downgrading to 6.2.0 👍🏻

  6. junaed-optimizely commented on Feb 27, 2026

    @junaed-optimizely
    Contributor

    Hey @andresz1, @julian-schwab - I have done some initial investigation using a load test setup (Express server, 50 concurrent connections, ~9.5K req/sec, 5-minute runs) comparing v6.2.0 and v6.3.0. In our tests, heap usage remained stable at ~22MB for both versions, and we weren't able to reproduce the memory growth or event loop lag regression.

    Without reproducing the issue, it's difficult to pinpoint the root cause. A few follow-up questions that would help us set up a more representative test environment:

    1. Datafile scale: How many feature flags, experiments, and holdouts does your project have? Our test used a small datafile (2 flags, 4 holdouts). A larger configuration could amplify per-decision allocations.

    2. Event loop lag specifics: @julian-schwab , what event loop lag numbers did you observe on v6.3.0 vs v6.2.0 (e.g., p99 went from 50ms to 500ms)? What monitoring tool are you using to track this (Datadog, Prometheus, etc.)?

    3. ODP usage: Are either of you using ODP ?

    4. Heap snapshot: Would either of you be able to share a V8 heap snapshot comparison?

    Any of these details would go a long way toward helping us reproduce and resolve this. Thanks!

  7. julian-schwab commented on Mar 6, 2026

    @julian-schwab

    Hello @junaed-optimizely,
    thank you for investigating the issue.

    Regarding your questions:

    1. We have only 2 feature flags, 0 experiments and 0 holdouts in each of the two services where we use the SDK and encountered the issue
    2. The p99 quantile is not showing any problematic values, we just saw the maximum eventloop lag increase from around 50 ms (v6.2.0) to 100 ms (v6.3.0) in one service and from 100 ms (v6.2.0) to around 1000ms (v6.3.0) - the latter service has a higher load and these high lags appear in times of peak load. There it can cause problems for us when the lag is over 1s and health check requests are not getting answered in time.

    Regarding 3 and 4, I would need to clarify and get back at you.

  8. julian-schwab commented on Mar 12, 2026

    @julian-schwab

    Hello @junaed-optimizely,
    regarding the other open points:

    1. can you explain what you mean exactly with "ODP"? I guess it's "Optimizely Data Platform", but I don't find this term anywhere when I'm logged in. When I follow the link to the documentation, it just says "Optimizely Feature Experimentation"
    2. Unfortunately, it is not possible for us to provide a heap snapshot due to data privacy concerns.

    Thank you for investigating, I hope it helps you to find the issue.
    Kind regards

  9. junaed-optimizely commented on Mar 12, 2026

    @junaed-optimizely
    Contributor

    @julian-schwab, Yes, ODP refers to Optimizely Data Platform. If your instance creation code includes odpManager, then ODP is being used. You can find more details about ODP here: Init doc and here Real Time Audience and here Send data to ODP

    Thanks for providing the details. Much appreciated. I'll get back if I find anything.

  10. julian-schwab commented on Mar 13, 2026

    @julian-schwab

    Alright, in that case we are using ODP, yes.

    Thanks and kind regards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions