Search code examples
mongodbmongoosegoogle-cloud-functions

How do I gracefully disconnect MongoDB in Google Functions? Behavior of "normal" Cloud Run and "Functions Cloud Run" seems to be different


In a normal Cloud Run something like the following seems to properly close a Mongoose/MongoDB connection.

const cleanup = async () => {
    await mongoose.disconnect()
    console.log('database | disconnected from db')
    process.exit()
}

const shutdownSignals = ['SIGTERM', 'SIGINT']
shutdownSignals.forEach((sig) => process.once(sig, cleanup))

But for a Cloud-Functions-managed Cloud Run this seems not to be the case. The instances shut down without waiting the usual 10s that "normal" Cloud Runs give after the SIGTERM is sent, so I never see the database | disconnected from db.

How would one go about this? I don't wanna create a connection for every single Cloud Functions call (very wasteful in my case).


Solution

  • Well, here is what I went with for now:

    import mongoose from 'mongoose'
    import { Sema } from 'async-sema'
    
    functions.cloudEvent('someCloudFunction', async (event) => {
        await connect()
    
        // actual computation here
    
        await disconnect()
    })
    
    const state = {
        num: 0,
        sema: new Sema(1),
    }
    
    export async function connect() {
        await state.sema.acquire()
        if (state.num === 0) {
            try {
                await mongoose.connect(MONGO_DB_URL)
            } catch (e) {
                process.exit(1)
            }
        }
        state.num += 1
        state.sema.release()
    }
    
    export async function disconnect() {
        await state.sema.acquire()
        state.num -= 1
        if (state.num === 0) {
            await mongoose.disconnect()
        }
        state.sema.release()
    }
    

    As one can see I used kind of a "reference counting" of the processes which want to use the connection, and ensured proper concurrency with async-sema.

    I should note that this works well with the setup I have; I allow many concurrent requests to one of my Cloud Functions instances. In other cases this solution might not improve over just opening up (and closing) a connection every single time the function is called. But as stuff like https://cloud.google.com/functions/docs/writing/write-event-driven-functions#termination seems to imply, everything has to be handled inside the cloudEvent function.