Skip to content

Commit

Permalink
feat(mongoose): force secondaryPreferred if no secondaries in cluster
Browse files Browse the repository at this point in the history
- Trigger early load of mongoose before any other import and implicit
  load of mongoose model
- Add a global schema plugin that inspects schema for read preference
  - if `secondary`, check the cluster using mongoose connection
  - if cluster without secondaries, force read pref to be
    `secondaryPreferred`
  • Loading branch information
LoneRifle committed Sep 13, 2024
1 parent cf52953 commit 68bde6e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 20 deletions.
66 changes: 46 additions & 20 deletions replacements/src/app/loaders/mongoose.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { MongoMemoryServer } from 'mongodb-memory-server-core'
import mongoose, { Connection } from 'mongoose'
import mongoose, { Connection, type Schema } from 'mongoose'

import config from '../config/config'
import { createLoggerWithLabel } from '../config/logger'

const readPrefSecondarySchemas = [] as Schema[]

// Invoke this at module load time
mongoose.plugin((schema) => {
if (schema.get('read') === 'secondary') {
readPrefSecondarySchemas.push(schema)
}
})

const logger = createLoggerWithLabel(module)

export default async (): Promise<Connection> => {
Expand Down Expand Up @@ -90,27 +99,44 @@ export default async (): Promise<Connection> => {
})
})

// Seed db with initial agency if we have none
mongoose.set('debug', (collectionName, method, query, doc) => {
logger.info({
message: `${collectionName}.${method}`,
meta: {
action: 'mongoose',
query,
doc,
},
})
})

// Inspect cluster topology from client
const client = mongoose.connection.getClient()
const {
description: { servers, type },
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
} = client.topology

if (
process.env.INIT_AGENCY_DOMAIN &&
process.env.INIT_AGENCY_SHORTNAME &&
process.env.INIT_AGENCY_FULLNAME
type === 'ReplicaSetWithPrimary' &&
!servers
.values()
.some(({ type }: { type: string }) => type === 'RSSecondary')
) {
const Agency = mongoose.model('Agency')
const agencyCount = await Agency.count()
if (agencyCount === 0) {
await mongoose.connection.collection(Agency.collection.name).updateOne(
{ shortName: process.env.INIT_AGENCY_SHORTNAME },
{
$setOnInsert: {
shortName: process.env.INIT_AGENCY_SHORTNAME,
fullName: process.env.INIT_AGENCY_FULLNAME,
emailDomain: [process.env.INIT_AGENCY_DOMAIN],
logo: '/logo192.png',
},
},
{ upsert: true },
)
// There are no secondary nodes in ReplicaSetWithPrimary cluster.
// Queries with `secondary` read preferences will fail, so rewrite
// those to be `secondaryPreferred`.
logger.warn({
message:
'ReplicaSetWithPrimary cluster has no secondary nodes. ' +
'Forcing secondary read preference to secondaryPreferred.',
meta: {
action: 'schema',
},
})
for (const schema of readPrefSecondarySchemas) {
schema.set('read', 'secondaryPreferred')
}
}

Expand Down
27 changes: 27 additions & 0 deletions replacements/src/app/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import './loaders/datadog-tracer'
import './loaders/mongoose'

import config from './config/config'
import { createLoggerWithLabel } from './config/logger'
import loadApp from './loaders'

const logger = createLoggerWithLabel(module)

const initServer = async () => {
const app = await loadApp()

// Configure aws-sdk based on environment
// sdk is later used to upload images to S3
await config.configureAws()

app.listen(config.port)

logger.info({
message: `[${config.nodeEnv}] Connected to port ${config.port}`,
meta: {
action: 'initServer',
},
})
}

void initServer()

0 comments on commit 68bde6e

Please sign in to comment.