MongoDBSaver.*list yields nothing when options.filter is defined, and fixing it requires a breaking change #106

Open
opened 2026-02-15 17:15:53 -05:00 by yindo · 3 comments
Owner

Originally created by @benjamincburns on GitHub (Oct 11, 2024).

For #541 I decided the best course of action for testing CheckpointSaver list implementations would be to write a combinatorial test that spams list with a bunch of different argument combinations. For MongoDBSaver, every test that involved specifying values under options.filter failed.

The options.filter argument appears to be meant to constrain the result based on values in the stored CheckpointMetadata. The MongoDBSaver attempts to do this here, with the following code:

    if (filter) {
      Object.entries(filter).forEach(([key, value]) => {
        query[`metadata.${key}`] = value;
      });
    }

Unfortunately because of how checkpoint and metadata are serialized in the put method (the output of this.serde.dumpsTyped is of type [string, UInt8Array]), metadata is stored as a BSON Binary field, so any time options.filter is passed in, list yields no results.

At first glance you might think that this would be fixable without a breaking change by applying the filter in the client (in the for (const doc of result) loop). That would work, but only if you also applied the limit constraint client-side as well. Otherwise any time a limit constraint is applied, you'll likely miss items that would've matched your filter.

The same problem exists on the SqliteSaver as well, however that's solvable without a breaking change by joining the query with a CTE that deserializes the stored metadata object (will PR that change tomorrow). I'm not a MongoDB aficionado, but I don't think it has facilities for transforming documents in any way prior to applying filters.

The simple fix is to persist the serialized fields as objects nested into the document object, rather than byte arrays. Unfortunately, doing this without a migration will break existing collections of checkpoint history.

At present there's no mechanism baked into either the langgraph-checkpoint library or the MongoDBSaver to check for out of date document structures, or to define or kick off migrations like these, so the design of this will likely require a bit of care. It would be easy enough to write an automatic migration that runs on start, but that gets ugly in production environments when there are multiple concurrent processes. As a result, I'd recommend making it so the updated MongoDBSaver fails on initialization if the migration is required for an existing db. That way the migration can be applied out of band, and there's no risk that someone will blow away their checkpoint storage as a result of an npm update.

For now I've pruned the filter checks in my list tests in order to prevent this from blocking progress on #541.

Originally created by @benjamincburns on GitHub (Oct 11, 2024). For #541 I decided the best course of action for testing CheckpointSaver `list` implementations would be to write [a combinatorial test](https://github.com/benjamincburns/langgraphjs/blob/9d78f0735c6632475ed8c80cfb511af5d5b69681/libs/checkpoint-validation/src/tests/spec/list.ts#L88-L143) that spams `list` with [a bunch of different argument combinations](https://github.com/benjamincburns/langgraphjs/blob/9d78f0735c6632475ed8c80cfb511af5d5b69681/libs/checkpoint-validation/src/tests/spec/list.ts#L225-L275). For `MongoDBSaver`, every test that involved specifying values under `options.filter` failed. The `options.filter` argument appears to be meant to constrain the result based on values in the stored `CheckpointMetadata`. The `MongoDBSaver` attempts to do this [here](https://github.com/langchain-ai/langgraphjs/blob/45e59e64175a89a7cf782015b011b7d348ef835f/libs/checkpoint-mongodb/src/index.ts#L148-L152), with the following code: ```ts if (filter) { Object.entries(filter).forEach(([key, value]) => { query[`metadata.${key}`] = value; }); } ``` Unfortunately because of [how `checkpoint` and `metadata` are serialized in the `put` method](https://github.com/langchain-ai/langgraphjs/blob/45e59e64175a89a7cf782015b011b7d348ef835f/libs/checkpoint-mongodb/src/index.ts#L219-L228) (the output of `this.serde.dumpsTyped` is of type `[string, UInt8Array]`), `metadata` is stored as a BSON Binary field, so any time `options.filter` is passed in, `list` yields no results. At first glance you might think that this would be fixable without a breaking change by applying the `filter` in the client (in the `for (const doc of result)` loop). That would work, but only if you also applied the `limit` constraint client-side as well. Otherwise any time a `limit` constraint is applied, you'll likely miss items that would've matched your filter. The same problem exists on the `SqliteSaver` as well, however that's solvable without a breaking change by joining the query with [a CTE that deserializes the stored `metadata` object](https://github.com/benjamincburns/langgraphjs/blob/9d78f0735c6632475ed8c80cfb511af5d5b69681/libs/checkpoint-sqlite/src/index.ts#L175-L182) (will PR that change tomorrow). I'm not a MongoDB aficionado, but I don't think it has facilities for transforming documents in any way prior to applying filters. The simple fix is to persist the serialized fields as objects nested into the document object, rather than byte arrays. Unfortunately, doing this without a migration will break existing collections of checkpoint history. At present there's no mechanism baked into either the `langgraph-checkpoint` library or the `MongoDBSaver` to check for out of date document structures, or to define or kick off migrations like these, so the design of this will likely require a bit of care. It would be easy enough to write an automatic migration that runs on start, but that gets ugly in production environments when there are multiple concurrent processes. As a result, I'd recommend making it so the updated `MongoDBSaver` fails on initialization if the migration is required for an existing db. That way the migration can be applied out of band, and there's no risk that someone will blow away their checkpoint storage as a result of an `npm update`. For now I've pruned the `filter` checks in [my `list` tests](https://github.com/benjamincburns/langgraphjs/blob/9d78f0735c6632475ed8c80cfb511af5d5b69681/libs/checkpoint-validation/src/tests/spec/list.ts#L217-L221) in order to prevent this from blocking progress on #541.
yindo added the bug label 2026-02-15 17:15:53 -05:00
Author
Owner

@jacoblee93 commented on GitHub (Oct 11, 2024):

Thanks for flagging and for the detailed writeup - we can make a minor bump with breaking changes.

@jacoblee93 commented on GitHub (Oct 11, 2024): Thanks for flagging and for the detailed writeup - we can make a minor bump with breaking changes.
Author
Owner

@benjamincburns commented on GitHub (Oct 11, 2024):

Easy enough. I'll see if I can't submit a PR later today my time.

@benjamincburns commented on GitHub (Oct 11, 2024): Easy enough. I'll see if I can't submit a PR later today my time.
Author
Owner

@benjamincburns commented on GitHub (Oct 14, 2024):

Easy enough. I'll see if I can't submit a PR later today my time.

Yeah, that was wildly optimistic on my part. I'm going to get the tests into PR tonight with exclusions for the issues that I found while writing them (just raised the last of those), and then I'll circle back and submit some more PRs for those issues. 😅

@benjamincburns commented on GitHub (Oct 14, 2024): > Easy enough. I'll see if I can't submit a PR later today my time. Yeah, that was wildly optimistic on my part. I'm going to get the tests into PR tonight with exclusions for the issues that I found while writing them (just raised the last of those), and then I'll circle back and submit some more PRs for those issues. 😅
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraphjs#106