Skip to content

feat: upgrade to mongodb@6#83

Merged
robertsLando merged 28 commits intomoscajs:masterfrom
seriousme:mongodb6-dev
May 12, 2025
Merged

feat: upgrade to mongodb@6#83
robertsLando merged 28 commits intomoscajs:masterfrom
seriousme:mongodb6-dev

Conversation

@seriousme
Copy link
Copy Markdown
Contributor

@seriousme seriousme commented Apr 18, 2025

This PR closes: #82

It updates package.json

  • all dependencies to their latest versions, especially:
    • mongodb@6 which does not support callbacks anymore
    • mqemitter-mongodb@9 which also uses mongodb@6 (both need the same major version of mongodb for testing)
  • replaces
    • nyc by c8 because of npm audit warnings
    • standard + snazzy by eslint + neostandard because of npm audit warnings
    • pre-commit by @fastify/pre-commit as pre-commit is already 8 years old
    • tape & faucet by node:test
  • lets go of unused dependencies
  • sets minimum engine version to 20 as required by some dependencies
  • adds new scripts:
    • mongodb script to ease testing
    • a lint:fix script

It updates CI to use:

  • recent versions of Node
  • the same mongodb as used during testing

It splits testing in separate files to ease test debugging

It creates a clean interface between the callback world of aedes-persistence and the async world of mongodb@6

It migrates testing to async because of mongodb@6 and caugth a few bugs in the tests that previously got lost in the callbacks.

It updates the README to fix the link to the badge.

Enjoy!

Kind regards,
Hans

Copy link
Copy Markdown
Collaborator

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work!

Comment thread .github/workflows/ci.yml Outdated
Comment thread test/abs.js
Comment thread asyncPersistence.js
Comment thread persistence.js Outdated
Comment thread persistence.js Outdated
Comment thread persistence.js Outdated
Comment thread persistence.js Outdated
Copy link
Copy Markdown
Member

@robertsLando robertsLando left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done with this @seriousme ! You used a clever approach with the separated asyncPersistence and I also like a lot that lot of deps have been removed. KUDOS 💪🏼

Comment thread persistence.js Outdated
@seriousme
Copy link
Copy Markdown
Contributor Author

I think I have applied all the suggested changes.
I'm still a bit curious why only some of the .then(() => cb(null, client)) need a process.nextTick and not all of them.
Is there a way to test for the behaviour that process.nextTick solves?

@seriousme
Copy link
Copy Markdown
Contributor Author

ps. I looked at the coverage report as well and most of the missing lines and branches are in persistence.js and like:

if (!this.ready) {
      this.once('ready', this.storeRetained.bind(this, packet, cb))
      return
    }

asyncPersistence.js has nearly 100% coverage.

@seriousme seriousme requested a review from mcollina April 18, 2025 12:08
@seriousme
Copy link
Copy Markdown
Contributor Author

Well done with this @seriousme ! You used a clever approach with the separated asyncPersistence and I also like a lot that lot of deps have been removed. KUDOS 💪🏼

Thx, its been quite a journey updating all dependencies , and some of their dependencies as well 😉
If we would like to use this approach more often then we can generalize persistence.js and move it to aedes-persistence-cached or even aedes-abstract-persistence so we only need to maintain it once. Similarly we could seperate the promisified versions of the instance methods from own.js and reuse them as well in multiple persistences.

Just let me know what you guys want. I like solving the puzzles 😄

Kind regards,
Hans

@seriousme
Copy link
Copy Markdown
Contributor Author

One more weird thought: now that require can load ESM (as of Node 20.19) would you guys be interested if further updates migrate to ESM? That would remove the need for 'use strict' and would allow the use of escape-string-regexp@5 which is ESM only. We would only need to make sure we don't use toplevel awaits as I understand it.

@robertsLando
Copy link
Copy Markdown
Member

If we would like to use this approach more often then we can generalize persistence.js and move it to aedes-persistence-cached or even aedes-abstract-persistence so we only need to maintain it once

I think that's a good idea! @mcollina thoughts?

One more weird thought: now that require can load ESM (as of Node 20.19) would you guys be interested if further updates migrate to ESM?

Absolutely, NodeJS 18 will soon reach EOL (the end of this month) so I think it's a good time to do it

Copy link
Copy Markdown
Member

@robertsLando robertsLando left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@seriousme
Copy link
Copy Markdown
Contributor Author

Seems like GitHub had an issue with starting MongoDB, can someone restart the tests? I just tried again using github codespaces and testing works there.

Kind regards,
Hans

@robertsLando
Copy link
Copy Markdown
Member

@seriousme Done, could you add a timeout minutes on tests please? I think 3 minutes are enough?

@robertsLando
Copy link
Copy Markdown
Member

@seriousme I have a feel there is a race condition where the docker container is not ready before test runs, I suggest to use https://github.com/vishnubob/wait-for-it

@seriousme
Copy link
Copy Markdown
Contributor Author

seriousme commented Apr 22, 2025

Instead of adding yet another dependency I have added test/_connected.js which simply waits a connection.
If this turns out not to be enough I can add a loop here as well.

I also set a generic timeout of 3 minutes for all tests as requested.
Hope this works as planned.

Kind regards,
Hans
(commit 5e80499 has a green tickmark now, so that might be a good sign)

@robertsLando
Copy link
Copy Markdown
Member

@seriousme I also sent you an invitation to join our org, would you accept it?

@robertsLando
Copy link
Copy Markdown
Member

For some reason tests are still failing :(

@seriousme
Copy link
Copy Markdown
Contributor Author

@seriousme I also sent you an invitation to join our org, would you accept it?

What does joining the org mean? I.e. what do you expect from me if I join ?

@seriousme
Copy link
Copy Markdown
Contributor Author

For some reason tests are still failing :(

It seems like the test still starts with abs.js instead of _connected.js.
I will add the connection test directly to abs.js and own.js

@robertsLando
Copy link
Copy Markdown
Member

What does joining the org mean? I.e. what do you expect from me if I join ?

I personally don't expect nothing, there are other people in the org that are not active in years, considering you are an active contributor I think it makes sense to have you in the org, being part of the org means you don't have to wait me to approve running actions on your PR and you can work directly on repos branches insted of your own fork :)

Also it's kind of an acknowledgment for the help you are giving to our org 🙏🏼

@robertsLando
Copy link
Copy Markdown
Member

robertsLando commented Apr 28, 2025

@seriousme I meant aedes-cached-persistence, not this, you should bump cached persistence here I think? And remove the custom abstract.js

@seriousme
Copy link
Copy Markdown
Contributor Author

Right, best option would imo be to first do another release of aedes-persistence, I can then update aedes-cached-persistence to take advantage of the promisified.js in aedes-persistence.
(and I hope to have the generic version of "persistence.js" calling the asyncPersistence in there as well)
And once that is fixed and released we can bump the version of aedes-cached-persistence here and in the redis variant.
What do you think?

@robertsLando
Copy link
Copy Markdown
Member

And once that is fixed and released we can bump the version of aedes-cached-persistence here and in the redis variant.
What do you think?

It's ok for me 👍🏼

@seriousme
Copy link
Copy Markdown
Contributor Author

Back on the wild goose chase again.
I upgraded aedes-cached-persistence, removed the local version of abstract.js and the test in abs.js works fine.
Running own.js makes it hang on the second test, somehow this._addedSubscriptions() never returns.

I tried reverting to the commit that passed CI above (rebuilding node_modules) and even that now hangs on the second test in own.js.

Any hints would be much apreciated ;-)

Kind regards,
Hans

Comment thread test/own.js Outdated
Comment on lines +15 to +22
function waitForEventOnce (emitter, eventName, errorEvent) {
return new Promise((resolve, reject) => {
emitter.once(eventName, resolve)
if (errorEvent) {
emitter.once(errorEvent, reject)
}
})
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can replace this with once imported from node:events/promises

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node 22.14.0 says: Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: node:events/promises

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry it's node:events

Comment thread persistence.js Outdated
Comment on lines +52 to +60
const addSubs1 = this.asyncPersistence.addSubscriptions(client, subs)
// promisify
const addSubs2 = new Promise((resolve, reject) => {
this._addedSubscriptions(client, subs, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is wrong as you should ensure the first completes before the second one

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this one and removeSubscriptions but test/own.js still fails and test/abs.js still succeeds.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed it in moscajs/aedes-cached-persistence#61, once mqemitter-mongodb releases a new version I can use callBackPersistence.js from the updated aedes-cached-persistence and things will run after each other.

Comment thread persistence.js Outdated
Comment on lines 74 to 84
const remSubs1 = this.asyncPersistence.removeSubscriptions(client, subs)
// promisify
const mappedSubs = subs.map(sub => { return { topic: sub } })
const remSubs2 = new Promise((resolve, reject) => {
this._removedSubscriptions(client, mappedSubs, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Comment thread abstract.js Outdated
Comment thread asyncPersistence.js Outdated
Comment on lines +454 to +458
try {
await ctx._cl.retained.bulkWrite(operations)
} catch (err) {
// ingnore error
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
try {
await ctx._cl.retained.bulkWrite(operations)
} catch (err) {
// ingnore error
}
await ctx._cl.retained.bulkWrite(operations).catch(() => {})

@seriousme
Copy link
Copy Markdown
Contributor Author

Small update: when using mongosh and running own.js I see the record stored in the collection 'pubsub' , but somehow the emitter is not picking it up. I wiretapped mqemitter-mongodb.js to trace any records coming in via:

   const cursor = await that._collection.find({ _id: { $gt: that._lastObj._id } }, {
        tailable: true,
        timeout: false,
        awaitData: true
      })

When running abs.js I see packets being picked up by the cursor, but when using own.js I don't see them coming in.
There are 2 main differences between own.js and abs.js if I see it correctly:
a) abs.js always uses the same client session, own.js lets the persistence layer create new client connections as well
b) abs.js uses deleteMany({}) on each individual collection, own.js uses db.dropDatabase()

I'm going to check these.

I'm still in the dark how 3442cef made it through CI, because when I check it out in a new codespace now and run the test then abs.js succeeds and own.js fails, so it must be something related to Mongodb or timing.

To be continued.

@robertsLando
Copy link
Copy Markdown
Member

@seriousme Could it be that maybe the database drop causes mqemitter to loose the tracking of the collection someway?

@seriousme
Copy link
Copy Markdown
Contributor Author

@robertsLando I traced it back to a change in the Mongo:8 container. I checked out mqemitter-mongodb version that passed CI and Matteo released some weeks ago and that now fails too.

MongoDB@6 has some challenges around tailable cursors when the collection is empty.
I submitted a question to the MongoDB community before and got no reply.
I think we need to fix mqemitter-mongodb to work around this. I will look into this.

Kind regards,
Hans

@robertsLando
Copy link
Copy Markdown
Member

MongoDB@6 has some challenges around tailable cursors when the collection is empty.
I submitted a question to the MongoDB community before and got no reply.
I think we need to fix mqemitter-mongodb to work around this. I will look into this.

Nice finding! I remember a while ago I had some headaches to fix an issue on mqemitter mongo to ensure packet were processed in order, that's why I introduced ordered bulks there and also here then as MongoDB was emitting packets in the wrong order. Hopefully they will be able to help you track down this issue, keep me updated 🙏🏼

@seriousme
Copy link
Copy Markdown
Contributor Author

MongoDB@6 has some challenges around tailable cursors when the collection is empty.
I submitted a question to the MongoDB community before and got no reply.
I think we need to fix mqemitter-mongodb to work around this. I will look into this.

Nice finding! I remember a while ago I had some headaches to fix an issue on mqemitter mongo to ensure packet were processed in order, that's why I introduced ordered bulks there and also here then as MongoDB was emitting packets in the wrong order. Hopefully they will be able to help you track down this issue, keep me updated 🙏🏼

I was wrong, It turned out to be a bug in my upgrade to mongodb@6 of mqemitter-mongodb (see issue) How that ever got past CI (at least 3 times) is a mystery to me, but it is solved now. As soon as a new version of mqemitter-mongodb is released I can update this branch and we should finally be done here (fingers crossed). I already did a dryrun with a local copy of the updated mqemitter-mongodb and it passed all the tests.

Kind regards,
Hans

@seriousme
Copy link
Copy Markdown
Contributor Author

@robertsLando : CI is green again after upgrading mqemitter-mongodb to 9.0.1. Once aedes-cached-persistence is released again we can include its updated version here and then this PR should be complete.

Kind regards,
Hans

@robertsLando robertsLando changed the title chore: upgrade to mongodb@6 feat: upgrade to mongodb@6 May 12, 2025
@robertsLando robertsLando merged commit 0d4217b into moscajs:master May 12, 2025
5 checks passed
@robertsLando
Copy link
Copy Markdown
Member

robertsLando commented May 12, 2025

@seriousme I tried to release the package but tests are failing locally.

First test that fails is:

✖ store and look up subscriptions by client (10003.617175ms)
  Error: Lost connection to MongoDB
      at /home/daniel/GitProjects/aedes-persistence-mongodb/node_modules/mqemitter-mongodb/mqemitter-mongodb.js:177:37
      at node:internal/util:539:20
      at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

Then it hangs indeterminately on:

✔ do not error if unkown messageId in outoingClearMessageId (10.110282ms)
ℹ instance created
ℹ instance cleaned up
...no more output here

Any thoughts? Node version is 20.18.1, also tried with 20.19.1 and 22.15.0 without success

@seriousme
Copy link
Copy Markdown
Contributor Author

Sorry to read that the test failed 😞
Seems like this PR is not meant to go live 😉

I just created a clean Codespace on master and tests pass, so troubleshooting will be a bit dificult. Does it consitently fail on your machine?

The source of the error is: https://github.com/mcollina/mqemitter-mongodb/blob/97b7703cf691f0ac3aff104e1b9569d3eac92989/mqemitter-mongodb.js#L177
which means that _stream lost its connection to MongoDB too often.

The stream is started in the setup phase of mqemitter-mongodb,
store and look up subscriptions by client is the 10th test in abstract.js, so the DB must have been available before.
Each test makes it own connection to mongodb and tears that connection down when done.

'do not error if unkown messageId in outoingClearMessageId' is the last test of abstract.js and if cleanup of one of the previous tests failed then the test suite will need to wait for timeout.

Setup and teardown is identical for all tests in abstract.js so I can't explain why it only fails at the 10th test.

If it consistently fails at the 10th test then from the top of my head I can think of the following options:
a) mongodb is still busy cleaning out the previous test, but I would expect the error on the startup of the persistence, not in the MQemitter.
b) there is some external factor (memory limit, CPU limit etc) that causes mongodb to misbehave after a set of tests. Did you already restart mongodb?

node --test --test-name-pattern 'store and look up subscriptions by client' test/abs.js should perform just the 10th test.

docker logs mongodb might give you a hint as to why the connection is dropped, but they are not very readable :-(

Hope this helps,
Hans

@robertsLando
Copy link
Copy Markdown
Member

@seriousme let me try to run the release from a codespace first :)

@robertsLando
Copy link
Copy Markdown
Member

Ok so the tests are working on codespace, no clue why, I tried everything locally but I'm not able to make them work, who knows 🤷🏼‍♂️

BTW the release commit CI failed, I tried to re-run jobs now. Seems tests are a bit flaky now, no clue why but I don't want you to invest too much time on this as it has been already a pain

@seriousme
Copy link
Copy Markdown
Contributor Author

I think adding a (fraction of a) second after cleaning the DB might help as this would allow Mongodb to catchup
e.g.:

we could add an:

 await sleep(500) 

just after:

async function cleanDB () {
const mongoClient = new MongoClient(mongourl, { w: 1 })
const oldDB = mongoClient.db(dbname)
await oldDB.dropDatabase()
await mongoClient.close()

and after

async function cleanDB (collections) {
await Promise.all(collections.map((c) => c.deleteMany({})))
}

This would require adding sleep() to abs.js as well.

It's a bit hard to test as I haven't had failing tests yet, but if it consistently fails on your machine then this would be my first guess.

Kind regards,
Hans

@seriousme seriousme deleted the mongodb6-dev branch May 13, 2025 16:34
@robertsLando
Copy link
Copy Markdown
Member

robertsLando commented May 13, 2025

It also failed on master branch, check last ci runs. But maybe the reason is different: https://github.com/moscajs/aedes-persistence-mongodb/actions/runs/14990090351/job/42112474933#step:7:103

Seems that the packets are actually equal but not strictly as props are in a different order?

But here the error still looks like the same I get on my machine: https://github.com/moscajs/aedes-persistence-mongodb/actions/runs/14990090351/job/42111317625#step:7:111

Might be worth a try adding a sleep I think

@seriousme
Copy link
Copy Markdown
Contributor Author

Now this is interesting: the order of the props is not the issue I think, but the actual has an "added: 2025-05-13T07:13:21.450Z" property which the expected does not have.

My codespace uses Mongodb 8.0.9 , checked using docker exec mongodb mongosh

@seriousme
Copy link
Copy Markdown
Contributor Author

The second error is indeed the same error: 'Lost connection to MongoDB' in test 10.

I will add the sleeps and see what that does.

@seriousme seriousme mentioned this pull request May 13, 2025
@robertsLando
Copy link
Copy Markdown
Member

Now this is interesting: the order of the props is not the issue I think, but the actual has an "added: 2025-05-13T07:13:21.450Z" property which the expected does not have.

Oh that key is added when a TTL is set for that collection, maybe you should also drop indexes when cleaning the db? Dunno if when you delete a collection also indexes are dropped (I think so but who knows 🤷🏼‍♂️ )

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: bump deps and switch to node:test

3 participants