mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[web] resync_projects: use the secondaries for all reads (#33684)
* [docstore] add useSecondary flag to projectHasRanges The rev-check for unarchiving always consults with the primary. Two extra changes: - Add a projection argument to peekDoc in order to skip lines download from projectHasRanges. - Add one retry to peekDoc to reduce chances of surfacing a rev-check violation. * [web] resync_projects: use the secondaries for all reads * [web] add default value for useSecondary * [docstore] add default value for useSecondary * [k8s] docstore: set MONGO_HAS_SECONDARIES=true GitOrigin-RevId: f15ec4fdc1cabe74c1eab87bec85f28d6f7a587d
This commit is contained in:
@@ -77,15 +77,17 @@ const DocManager = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// returns the doc without any version information
|
// returns the doc without any version information
|
||||||
async _peekRawDoc(projectId, docId) {
|
async _peekRawDoc(projectId, docId, projection, useSecondary) {
|
||||||
const doc = await MongoManager.findDoc(projectId, docId, {
|
const doc = await MongoManager.findDoc(
|
||||||
lines: true,
|
projectId,
|
||||||
rev: true,
|
docId,
|
||||||
deleted: true,
|
{
|
||||||
version: true,
|
...projection,
|
||||||
ranges: true,
|
rev: true,
|
||||||
inS3: true,
|
inS3: true,
|
||||||
})
|
},
|
||||||
|
useSecondary
|
||||||
|
)
|
||||||
|
|
||||||
if (doc == null) {
|
if (doc == null) {
|
||||||
throw new Errors.NotFoundError(
|
throw new Errors.NotFoundError(
|
||||||
@@ -97,6 +99,8 @@ const DocManager = {
|
|||||||
// skip the unarchiving to mongo when getting a doc
|
// skip the unarchiving to mongo when getting a doc
|
||||||
const archivedDoc = await DocArchive.getDoc(projectId, docId)
|
const archivedDoc = await DocArchive.getDoc(projectId, docId)
|
||||||
Object.assign(doc, archivedDoc)
|
Object.assign(doc, archivedDoc)
|
||||||
|
// Always use the primary for the rev-check.
|
||||||
|
await MongoManager.checkRevUnchanged(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
@@ -104,10 +108,21 @@ const DocManager = {
|
|||||||
|
|
||||||
// get the doc from mongo if possible, or from the persistent store otherwise,
|
// get the doc from mongo if possible, or from the persistent store otherwise,
|
||||||
// without unarchiving it (avoids unnecessary writes to mongo)
|
// without unarchiving it (avoids unnecessary writes to mongo)
|
||||||
async peekDoc(projectId, docId) {
|
async peekDoc(projectId, docId, projection, useSecondary = false) {
|
||||||
const doc = await DocManager._peekRawDoc(projectId, docId)
|
try {
|
||||||
await MongoManager.checkRevUnchanged(doc)
|
return await DocManager._peekRawDoc(
|
||||||
return doc
|
projectId,
|
||||||
|
docId,
|
||||||
|
projection,
|
||||||
|
useSecondary
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Errors.DocModifiedError) {
|
||||||
|
// Try again once on rev mismatch. Always use the primary for retries.
|
||||||
|
return await DocManager._peekRawDoc(projectId, docId, projection, false)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getDocLines(projectId, docId) {
|
async getDocLines(projectId, docId) {
|
||||||
@@ -181,11 +196,20 @@ const DocManager = {
|
|||||||
return Array.from(userIds)
|
return Array.from(userIds)
|
||||||
},
|
},
|
||||||
|
|
||||||
async projectHasRanges(projectId) {
|
async projectHasRanges(projectId, useSecondary) {
|
||||||
const docs = await MongoManager.getProjectsDocs(projectId, {}, { _id: 1 })
|
const docs = await MongoManager.getProjectsDocs(
|
||||||
|
projectId,
|
||||||
|
{ useSecondary },
|
||||||
|
{ _id: 1 }
|
||||||
|
)
|
||||||
const docIds = docs.map(doc => doc._id)
|
const docIds = docs.map(doc => doc._id)
|
||||||
for (const docId of docIds) {
|
for (const docId of docIds) {
|
||||||
const doc = await DocManager.peekDoc(projectId, docId)
|
const doc = await DocManager.peekDoc(
|
||||||
|
projectId,
|
||||||
|
docId,
|
||||||
|
{ ranges: true },
|
||||||
|
useSecondary
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
(doc.ranges?.comments != null && doc.ranges.comments.length > 0) ||
|
(doc.ranges?.comments != null && doc.ranges.comments.length > 0) ||
|
||||||
(doc.ranges?.changes != null && doc.ranges.changes.length > 0)
|
(doc.ranges?.changes != null && doc.ranges.changes.length > 0)
|
||||||
|
|||||||
@@ -22,7 +22,14 @@ async function getDoc(req, res) {
|
|||||||
async function peekDoc(req, res) {
|
async function peekDoc(req, res) {
|
||||||
const { doc_id: docId, project_id: projectId } = req.params
|
const { doc_id: docId, project_id: projectId } = req.params
|
||||||
logger.debug({ projectId, docId }, 'peeking doc')
|
logger.debug({ projectId, docId }, 'peeking doc')
|
||||||
const doc = await DocManager.peekDoc(projectId, docId)
|
const doc = await DocManager.peekDoc(projectId, docId, {
|
||||||
|
deleted: true,
|
||||||
|
inS3: true,
|
||||||
|
lines: true,
|
||||||
|
ranges: true,
|
||||||
|
rev: 1,
|
||||||
|
version: true,
|
||||||
|
})
|
||||||
res.setHeader('x-doc-status', doc.inS3 ? 'archived' : 'active')
|
res.setHeader('x-doc-status', doc.inS3 ? 'archived' : 'active')
|
||||||
res.json(_buildDocView(doc))
|
res.json(_buildDocView(doc))
|
||||||
}
|
}
|
||||||
@@ -121,7 +128,11 @@ async function getTrackedChangesUserIds(req, res) {
|
|||||||
|
|
||||||
async function projectHasRanges(req, res) {
|
async function projectHasRanges(req, res) {
|
||||||
const { project_id: projectId } = req.params
|
const { project_id: projectId } = req.params
|
||||||
const projectHasRanges = await DocManager.projectHasRanges(projectId)
|
const useSecondary = req.query.useSecondary === 'true'
|
||||||
|
const projectHasRanges = await DocManager.projectHasRanges(
|
||||||
|
projectId,
|
||||||
|
useSecondary
|
||||||
|
)
|
||||||
res.json({ projectHasRanges })
|
res.json({ projectHasRanges })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,18 @@ const { db, ObjectId, BSON } = mongodb
|
|||||||
|
|
||||||
const ARCHIVING_LOCK_DURATION_MS = Settings.archivingLockDurationMs
|
const ARCHIVING_LOCK_DURATION_MS = Settings.archivingLockDurationMs
|
||||||
|
|
||||||
async function findDoc(projectId, docId, projection) {
|
function readPreference(useSecondary) {
|
||||||
|
if (useSecondary) return { readPreference: mongodb.READ_PREFERENCE_SECONDARY }
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findDoc(projectId, docId, projection, useSecondary = false) {
|
||||||
const doc = await db.docs.findOne(
|
const doc = await db.docs.findOne(
|
||||||
{
|
{
|
||||||
_id: new ObjectId(docId.toString()),
|
_id: new ObjectId(docId.toString()),
|
||||||
project_id: new ObjectId(projectId.toString()),
|
project_id: new ObjectId(projectId.toString()),
|
||||||
},
|
},
|
||||||
{ projection }
|
{ projection, ...readPreference(useSecondary) }
|
||||||
)
|
)
|
||||||
if (doc && projection.version && !doc.version) {
|
if (doc && projection.version && !doc.version) {
|
||||||
doc.version = 0
|
doc.version = 0
|
||||||
@@ -45,6 +50,7 @@ async function getProjectsDocs(projectId, options, projection) {
|
|||||||
}
|
}
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
projection,
|
projection,
|
||||||
|
...readPreference(options.useSecondary),
|
||||||
}
|
}
|
||||||
if (options.limit) {
|
if (options.limit) {
|
||||||
queryOptions.limit = options.limit
|
queryOptions.limit = options.limit
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Settings from '@overleaf/settings'
|
|||||||
import MongoUtils from '@overleaf/mongo-utils'
|
import MongoUtils from '@overleaf/mongo-utils'
|
||||||
import mongodb from 'mongodb-legacy'
|
import mongodb from 'mongodb-legacy'
|
||||||
|
|
||||||
const { MongoClient, ObjectId, BSON } = mongodb
|
const { MongoClient, ObjectId, BSON, ReadPreference } = mongodb
|
||||||
|
|
||||||
const mongoClient = new MongoClient(Settings.mongo.url, Settings.mongo.options)
|
const mongoClient = new MongoClient(Settings.mongo.url, Settings.mongo.options)
|
||||||
const mongoDb = mongoClient.db()
|
const mongoDb = mongoClient.db()
|
||||||
@@ -21,7 +21,14 @@ async function cleanupTestDatabase() {
|
|||||||
await MongoUtils.cleanupTestDatabase(mongoClient)
|
await MongoUtils.cleanupTestDatabase(mongoClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const READ_PREFERENCE_PRIMARY = ReadPreference.primary.mode
|
||||||
|
const READ_PREFERENCE_SECONDARY = Settings.mongo.hasSecondaries
|
||||||
|
? ReadPreference.secondary.mode
|
||||||
|
: ReadPreference.secondaryPreferred.mode
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
READ_PREFERENCE_PRIMARY,
|
||||||
|
READ_PREFERENCE_SECONDARY,
|
||||||
db,
|
db,
|
||||||
mongoClient,
|
mongoClient,
|
||||||
ObjectId,
|
ObjectId,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const Settings = {
|
|||||||
options: {
|
options: {
|
||||||
monitorCommands: true,
|
monitorCommands: true,
|
||||||
},
|
},
|
||||||
|
hasSecondaries: process.env.MONGO_HAS_SECONDARIES === 'true',
|
||||||
},
|
},
|
||||||
|
|
||||||
docstore: {
|
docstore: {
|
||||||
|
|||||||
@@ -323,10 +323,12 @@ async function updateDoc(
|
|||||||
* Asks docstore whether any doc in the project has ranges
|
* Asks docstore whether any doc in the project has ranges
|
||||||
*
|
*
|
||||||
* @param {string} projectId
|
* @param {string} projectId
|
||||||
|
* @param {boolean} useSecondary
|
||||||
*/
|
*/
|
||||||
async function projectHasRanges(projectId) {
|
async function projectHasRanges(projectId, useSecondary = false) {
|
||||||
const url = new URL(settings.apis.docstore.url)
|
const url = new URL(settings.apis.docstore.url)
|
||||||
url.pathname = path.posix.join('project', projectId, 'has-ranges')
|
url.pathname = path.posix.join('project', projectId, 'has-ranges')
|
||||||
|
if (useSecondary) url.searchParams.set('useSecondary', 'true')
|
||||||
try {
|
try {
|
||||||
const body = await fetchJson(url, { signal: AbortSignal.timeout(TIMEOUT) })
|
const body = await fetchJson(url, { signal: AbortSignal.timeout(TIMEOUT) })
|
||||||
return body.projectHasRanges
|
return body.projectHasRanges
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import minimist from 'minimist'
|
import minimist from 'minimist'
|
||||||
import { scriptRunner } from '../lib/ScriptRunner.mjs'
|
import { scriptRunner } from '../lib/ScriptRunner.mjs'
|
||||||
import logger from '@overleaf/logger'
|
import logger from '@overleaf/logger'
|
||||||
import ProjectGetter from '../../app/src/Features/Project/ProjectGetter.mjs'
|
|
||||||
import {
|
import {
|
||||||
db,
|
db,
|
||||||
ObjectId,
|
ObjectId,
|
||||||
@@ -268,7 +267,7 @@ async function hasHistoryMetadata(projectId) {
|
|||||||
if (await hasLinkedFileData(projectId)) {
|
if (await hasLinkedFileData(projectId)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (await DocstoreManager.promises.projectHasRanges(projectId)) {
|
if (await DocstoreManager.promises.projectHasRanges(projectId, true)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -296,10 +295,11 @@ async function hasHistoryMetadata(projectId) {
|
|||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async function hasLinkedFileData(projectId) {
|
async function hasLinkedFileData(projectId) {
|
||||||
const project = await ProjectGetter.promises.getProjectWithoutLock(
|
const project = await db.projects.findOne(
|
||||||
projectId,
|
{ _id: new ObjectId(projectId) },
|
||||||
{
|
{
|
||||||
rootFolder: 1,
|
projection: { rootFolder: 1 },
|
||||||
|
readPreference: READ_PREFERENCE_SECONDARY,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (!project) {
|
if (!project) {
|
||||||
|
|||||||
Reference in New Issue
Block a user