Skip to content

Commit

Permalink
fix(db-mongodb): mongodb optimizations (#10120)
Browse files Browse the repository at this point in the history
There are few issues introduced in #9594 that we need to look into with
these changes.
  • Loading branch information
r1tsuu authored Dec 21, 2024
1 parent 08eb13d commit b08ff88
Show file tree
Hide file tree
Showing 46 changed files with 941 additions and 1,339 deletions.
41 changes: 23 additions & 18 deletions packages/db-mongodb/src/count.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
import type { CountOptions } from 'mongodb'
import type { Count } from 'payload'

import { flattenWhereToOperators } from 'payload'

import type { MongooseAdapter } from './index.js'

import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
import { getSession } from './utilities/getSession.js'

export const count: Count = async function count(
this: MongooseAdapter,
{ collection, locale, req, where },
) {
const Model = this.collections[collection]
const session = await getSession(this, req)
const options: CountOptions = {
session: await getSession(this, req),
}

const hasNearConstraint = getHasNearConstraint(where)
let hasNearConstraint = false

if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}

const query = await Model.buildQuery({
locale,
payload: this.payload,
session,
where,
})

// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0

if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}

let result: number
if (useEstimatedCount) {
result = await Model.collection.estimatedDocumentCount()
result = await Model.estimatedDocumentCount({ session: options.session })
} else {
const options: CountOptions = { session }

if (this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}

result = await Model.collection.countDocuments(query, options)
result = await Model.countDocuments(query, options)
}

return {
Expand Down
41 changes: 23 additions & 18 deletions packages/db-mongodb/src/countGlobalVersions.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
import type { CountOptions } from 'mongodb'
import type { CountGlobalVersions } from 'payload'

import { flattenWhereToOperators } from 'payload'

import type { MongooseAdapter } from './index.js'

import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
import { getSession } from './utilities/getSession.js'

export const countGlobalVersions: CountGlobalVersions = async function countGlobalVersions(
this: MongooseAdapter,
{ global, locale, req, where },
) {
const Model = this.versions[global]
const session = await getSession(this, req)
const options: CountOptions = {
session: await getSession(this, req),
}

const hasNearConstraint = getHasNearConstraint(where)
let hasNearConstraint = false

if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}

const query = await Model.buildQuery({
locale,
payload: this.payload,
session,
where,
})

// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0

if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}

let result: number
if (useEstimatedCount) {
result = await Model.collection.estimatedDocumentCount()
result = await Model.estimatedDocumentCount({ session: options.session })
} else {
const options: CountOptions = { session }

if (Object.keys(query).length === 0 && this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}

result = await Model.collection.countDocuments(query, options)
result = await Model.countDocuments(query, options)
}

return {
Expand Down
41 changes: 23 additions & 18 deletions packages/db-mongodb/src/countVersions.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
import type { CountOptions } from 'mongodb'
import type { CountVersions } from 'payload'

import { flattenWhereToOperators } from 'payload'

import type { MongooseAdapter } from './index.js'

import { getHasNearConstraint } from './utilities/getHasNearConstraint.js'
import { getSession } from './utilities/getSession.js'

export const countVersions: CountVersions = async function countVersions(
this: MongooseAdapter,
{ collection, locale, req, where },
) {
const Model = this.versions[collection]
const session = await getSession(this, req)
const options: CountOptions = {
session: await getSession(this, req),
}

const hasNearConstraint = getHasNearConstraint(where)
let hasNearConstraint = false

if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}

const query = await Model.buildQuery({
locale,
payload: this.payload,
session,
where,
})

// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0

if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}

let result: number
if (useEstimatedCount) {
result = await Model.collection.estimatedDocumentCount()
result = await Model.estimatedDocumentCount({ session: options.session })
} else {
const options: CountOptions = { session }

if (this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}

result = await Model.collection.countDocuments(query, options)
result = await Model.countDocuments(query, options)
}

return {
Expand Down
50 changes: 27 additions & 23 deletions packages/db-mongodb/src/create.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,48 @@
import type { Create } from 'payload'
import type { CreateOptions } from 'mongoose'
import type { Create, Document } from 'payload'

import type { MongooseAdapter } from './index.js'

import { getSession } from './utilities/getSession.js'
import { handleError } from './utilities/handleError.js'
import { transform } from './utilities/transform.js'
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'

export const create: Create = async function create(
this: MongooseAdapter,
{ collection, data, req },
) {
const Model = this.collections[collection]
const session = await getSession(this, req)

const fields = this.payload.collections[collection].config.flattenedFields

if (this.payload.collections[collection].customIDType) {
data._id = data.id
const options: CreateOptions = {
session: await getSession(this, req),
}

transform({
adapter: this,
let doc

const sanitizedData = sanitizeRelationshipIDs({
config: this.payload.config,
data,
fields,
operation: 'create',
fields: this.payload.collections[collection].config.fields,
})

try {
const { insertedId: insertedID } = await Model.collection.insertOne(data, { session })
data._id = insertedID

transform({
adapter: this,
data,
fields,
operation: 'read',
})
if (this.payload.collections[collection].customIDType) {
sanitizedData._id = sanitizedData.id
}

return data
try {
;[doc] = await Model.create([sanitizedData], options)
} catch (error) {
handleError({ collection, error, req })
}

// doc.toJSON does not do stuff like converting ObjectIds to string, or date strings to date objects. That's why we use JSON.parse/stringify here
const result: Document = JSON.parse(JSON.stringify(doc))
const verificationToken = doc._verificationToken

// custom id type reset
result.id = result._id
if (verificationToken) {
result._verificationToken = verificationToken
}

return result
}
41 changes: 20 additions & 21 deletions packages/db-mongodb/src/createGlobal.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,38 @@
import type { CreateOptions } from 'mongoose'
import type { CreateGlobal } from 'payload'

import type { MongooseAdapter } from './index.js'

import { getSession } from './utilities/getSession.js'
import { transform } from './utilities/transform.js'
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'

export const createGlobal: CreateGlobal = async function createGlobal(
this: MongooseAdapter,
{ slug, data, req },
) {
const Model = this.globals

const fields = this.payload.config.globals.find(
(globalConfig) => globalConfig.slug === slug,
).flattenedFields

transform({
adapter: this,
data,
fields,
globalSlug: slug,
operation: 'create',
const global = sanitizeRelationshipIDs({
config: this.payload.config,
data: {
globalType: slug,
...data,
},
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
})

const session = await getSession(this, req)
const options: CreateOptions = {
session: await getSession(this, req),
}

const { insertedId: insertedID } = await Model.collection.insertOne(data, { session })
;(data as any)._id = insertedID
let [result] = (await Model.create([global], options)) as any

transform({
adapter: this,
data,
fields,
operation: 'read',
})
result = JSON.parse(JSON.stringify(result))

// custom id type reset
result.id = result._id
result = sanitizeInternalFields(result)

return data
return result
}
Loading

0 comments on commit b08ff88

Please sign in to comment.