pubkey / pubkey/rxdb

Fulltext Search High memory usage with 200K+ documents during rehydration and cleanup

Open
#9,023 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

stale
Dominant language
TypeScript
Stars
23.4k
Forks
1.2k
Avg merge
1d 4h
Merged PRs (30d)
110

Description

Hi Team,

We are using the RxDB Premium Full-Text Search plugin with FlexSearch on a large collection (~200K documents) in an Angular 15 hybrid mobile application.

The initial Full-Text Search indexing completes successfully. However, after restarting the application, the application can run out of memory while restoring the persisted FlexSearch state.

I also see a similar memory concern when using RxFulltextSearch.cleanup() after a large indexing operation.

While investigating this, I reviewed the Premium FlexSearch implementation in rx-fulltext-search.js and noticed two places where a large amount of persisted data can potentially be materialized in memory.

Environment

  • RxDB: 16.9.0
  • rxdb-premium: 16.9.0
  • Angular: 15
  • Application: Angular 15 hybrid mobile application
  • Storage: SQLite with encryption
  • Full-text search: RxDB Premium FlexSearch
  • Indexed collection size: ~200K documents
  • FTS pipeline batch size: 500–1000

1. Rehydration in addFulltextSearch()

The original addFulltextSearch() implementation restores the persisted FlexSearch state using:

const docs = await collection.find().exec();

It then separates the returned documents into type: "index" and type: "append" records and replays the append data into the FlexSearch index.

The relevant implementation is approximately:

const docs = await collection.find().exec();

const indexDocs = docs.filter(
    doc => doc.type === "index"
);

await Promise.all(
    indexDocs.map(doc => {
        doc.dataStr &&
            flexSearch.import(
                doc.name,
                doc.dataStr
            );
    })
);

docs
    .filter(doc => doc.type === "append")
    .forEach(doc => {
        doc.dataAr.forEach(item => {
            flexSearch.add(
                item.id,
                item.searchable
            );
        });
    });

With our ~200K document collection, the persisted append records collectively contain approximately 200K searchable entries.

The concern is that find().exec() materializes the complete FTS persistence collection in memory before the append records are processed.

As an experiment, I changed the rehydration logic to load the append records in smaller batches instead of loading the complete collection at once:

for (;;) {
    const appendDocs = await collection.find({
        selector: {
            type: "append"
        }
    })
    .sort({
        id: "asc"
    })
    .skip(offset)
    .limit(batchSize)
    .exec();

    if (appendDocs.length === 0) {
        break;
    }

    appendDocs.forEach(doc => {
        doc.dataAr.forEach(item => {
            flexSearch.add(
                item.id,
                item.searchable
            );
        });
    });

    offset += appendDocs.length;
}

The intention of this change is to reduce the amount of persisted append data held in memory at any one time.

However, the FlexSearch index itself still needs to be reconstructed in memory.

I would like to confirm whether processing the persisted append records in smaller batches is the recommended approach for large Full-Text Search indexes, or whether RxDB provides another mechanism for memory-efficient rehydration.

2. RxFulltextSearch.cleanup()

I also noticed a similar pattern in the cleanup() implementation:

const appendDocs = await this.collection.find({
    selector: {
        type: "append"
    }
}).exec();

The complete FlexSearch index is then exported:

this.index.export(async (name, dataStr) => {
    await this.collection.upsert({
        type: "index",
        token: this.collection.database.token,
        name: name + "",
        dataStr
    });
});

The append records are then removed:

await this.collection.bulkRemove(
    appendDocs.map(doc => doc.primary)
);

For a large index, this could potentially result in memory being used by:

Existing FlexSearch index
        +
All append documents / dataAr
        +
Serialized FlexSearch export
        +
RxDB storage/encryption buffers

This could create a significant temporary memory peak on a mobile device.

Persisted state

After the initial indexing, before calling cleanup(), our FTS persistence collection contains approximately:

total       = 400
indexCount  = 0
appendCount = 400

This is expected because the persisted FlexSearch snapshot has not yet been created.

My understanding is that cleanup() is responsible for exporting the current FlexSearch index into persisted type: "index" records and removing the corresponding append records.

What I would like to confirm

For a large Full-Text Search index (~200K+ documents):

  1. Is cleanup() the intended mechanism for periodically creating the persisted type: "index" snapshot?

  2. Is there a recommended way to make both FTS rehydration and cleanup more memory-efficient for large indexes?

  3. In particular, would processing persisted append records in smaller chunks/batches be the recommended approach instead of materializing all of them with .find().exec()?

  4. For cleanup(), is there a recommended way to avoid holding all append documents/data in memory while creating the FlexSearch snapshot?

  5. Does RxDB/FlexSearch provide a supported lower-memory approach for exporting or restoring a large persisted index?

I have attached a single JavaScript reproduction file that creates a large collection, initializes Full-Text Search, closes/reopens the database, and exercises the rehydration and cleanup paths.

Please let me know if any additional information is needed to reproduce and diagnose the issue.

flexsearch-oom.js.txt

Thanks!

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the attached flexsearch-oom.js.txt reproduction, then inspect addFulltextSearch() and cleanup() in rx-fulltext-search.js, focusing on where find().exec() materializes persisted records and where the FlexSearch export is created. Compare memory behavior during rehydration and cleanup; done means an agreed, supported lower-memory approach is demonstrated for both paths.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, typescript
Domain
database, mobile-dev, search
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.