[web] shard compile server persistence by compileBackendClass (#29156)

* [web] shard compile server persistence by compileBackendClass

* [web] make prettier happy

GitOrigin-RevId: d7cf8bde07e7110053d9d7531c007111af5cc46b
This commit is contained in:
Jakob Ackermann
2025-10-16 14:01:37 +02:00
committed by Copybot
parent d7e1ad5588
commit 706971ef41
3 changed files with 117 additions and 34 deletions

View File

@@ -20,7 +20,21 @@ if (Settings.redis.clsi_cookie_secondary != null) {
}
const ClsiCookieManagerFactory = function (backendGroup) {
function buildKey(projectId, userId) {
/**
* @param {string} projectId
* @param {string | null} userId
* @param {string} compileBackendClass
* @return {string}
*/
function buildKey(projectId, userId, compileBackendClass) {
if (backendGroup != null) {
return `clsiserver:${backendGroup}:${compileBackendClass}:${projectId}:${userId}`
} else {
return `clsiserver:${compileBackendClass}:${projectId}:${userId}`
}
}
function buildOldKey(projectId, userId) {
if (backendGroup != null) {
return `clsiserver:${backendGroup}:${projectId}:${userId}`
} else {
@@ -37,7 +51,14 @@ const ClsiCookieManagerFactory = function (backendGroup) {
if (!clsiCookiesEnabled) {
return
}
const serverId = await rclient.get(buildKey(projectId, userId))
let serverId = await rclient.get(
buildKey(projectId, userId, compileBackendClass)
)
if (!serverId) {
// Fallback to the old key.
// TODO(das7pad): remove this in 24h.
serverId = await rclient.get(buildOldKey(projectId, userId))
}
if (!serverId) {
return await cookieManager.promises._populateServerIdViaRequest(
@@ -157,7 +178,7 @@ const ClsiCookieManagerFactory = function (backendGroup) {
if (serverId == null) {
// We don't get a cookie back if it hasn't changed
return await rclient.expire(
buildKey(projectId, userId),
buildKey(projectId, userId, compileBackendClass),
_getTTLInSeconds(previous)
)
}
@@ -176,15 +197,28 @@ const ClsiCookieManagerFactory = function (backendGroup) {
rclientSecondary,
projectId,
userId,
compileBackendClass,
serverId
).catch(() => {})
}
await _setServerIdInRedis(rclient, projectId, userId, serverId)
await _setServerIdInRedis(
rclient,
projectId,
userId,
compileBackendClass,
serverId
)
}
async function _setServerIdInRedis(rclient, projectId, userId, serverId) {
async function _setServerIdInRedis(
rclient,
projectId,
userId,
compileBackendClass,
serverId
) {
await rclient.setex(
buildKey(projectId, userId),
buildKey(projectId, userId, compileBackendClass),
_getTTLInSeconds(serverId),
serverId
)

View File

@@ -38,13 +38,53 @@ const CLSI_COOKIES_ENABLED = (Settings.clsiCookie?.key ?? '') !== ''
// The timeout in services/clsi/app.js is 10 minutes, so we'll be on the safe side with 12 minutes
const COMPILE_REQUEST_TIMEOUT_MS = 12 * 60 * 1000
async function clearClsiServerId(projectId, userId) {
const jobs = [ClsiCookieManager.promises.clearServerId(projectId, userId)]
function getNewCompileBackendClass(projectId, compileBackendClass) {
// Sample x% of projects to move up one bracket.
if (
SplitTestHandler.getPercentile(projectId, 'double-compile', 'release') >=
Settings.apis.clsi_new.sample
) {
return null
}
switch (compileBackendClass) {
case 'n2d':
return 'n4'
case 'c2d':
return 'n4'
default:
throw new Error('unknown ?compileBackendClass')
}
}
/**
* @param {string} projectId
* @param {string | null} userId
* @param {string} compileBackendClass
* @return {Promise<void>}
*/
async function clearClsiServerId(projectId, userId, compileBackendClass) {
const jobs = [
ClsiCookieManager.promises.clearServerId(
projectId,
userId,
compileBackendClass
),
]
if (Settings.apis.clsi_new?.url) {
// Mirror resetting the clsiserverid in both backends.
jobs.push(
NewBackendCloudClsiCookieManager.promises.clearServerId(projectId, userId)
const newCompileBackendClass = getNewCompileBackendClass(
projectId,
compileBackendClass
)
if (newCompileBackendClass) {
jobs.push(
NewBackendCloudClsiCookieManager.promises.clearServerId(
projectId,
userId
)
)
}
}
await Promise.all(jobs)
}
@@ -176,14 +216,14 @@ async function deleteAuxFiles(projectId, userId, options, clsiserverid) {
await DocumentUpdaterHandler.promises.clearProjectState(projectId)
} finally {
// always clear the clsi server id, even if prior actions failed
await clearClsiServerId(projectId, userId)
await clearClsiServerId(projectId, userId, compileBackendClass)
}
}
}
async function _sendBuiltRequest(projectId, userId, req, options) {
if (options.forceNewClsiServer) {
await clearClsiServerId(projectId, userId)
await clearClsiServerId(projectId, userId, options.compileBackendClass)
}
const validationProblems = ClsiFormatChecker.checkRecoursesForProblems(
req.compile?.resources
@@ -436,28 +476,15 @@ async function _makeNewBackendRequest(
if (Settings.apis.clsi_new?.url == null) {
return null
}
const newCompileBackendClass = getNewCompileBackendClass(
projectId,
currentCompileBackendClass
)
if (!newCompileBackendClass) return null
url = new URL(
url.toString().replace(Settings.apis.clsi.url, Settings.apis.clsi_new.url)
)
// Sample x% of projects to move up one bracket.
if (
SplitTestHandler.getPercentile(projectId, 'double-compile', 'release') >=
Settings.apis.clsi_new.sample
)
return null
let newCompileBackendClass
switch (currentCompileBackendClass) {
case 'n2d':
newCompileBackendClass = 'n4'
break
case 'c2d':
newCompileBackendClass = 'n4'
break
default:
throw new Error('unknown ?compileBackendClass')
}
url.searchParams.set('compileBackendClass', newCompileBackendClass)
const clsiServerId =

View File

@@ -54,6 +54,28 @@ describe('ClsiCookieManager', function () {
'',
'n2d'
)
this.redis.get
.calledWith(`clsiserver:n2d:${this.project_id}:${this.user_id}`)
.should.equal(true)
serverId.should.equal('clsi-7')
})
it('should fallback to old key', async function () {
this.redis.get
.withArgs(`clsiserver:n2d:${this.project_id}:${this.user_id}`)
.resolves(null)
this.redis.get
.withArgs(`clsiserver:${this.project_id}:${this.user_id}`)
.resolves('clsi-7')
const serverId = await this.ClsiCookieManager.promises.getServerId(
this.project_id,
this.user_id,
'',
'n2d'
)
this.redis.get
.calledWith(`clsiserver:n2d:${this.project_id}:${this.user_id}`)
.should.equal(true)
this.redis.get
.calledWith(`clsiserver:${this.project_id}:${this.user_id}`)
.should.equal(true)
@@ -177,7 +199,7 @@ describe('ClsiCookieManager', function () {
null
)
this.redis.setex.should.have.been.calledWith(
`clsiserver:${this.project_id}:${this.user_id}`,
`clsiserver:n2d:${this.project_id}:${this.user_id}`,
this.settings.clsiCookie.ttlInSeconds,
this.clsiServerId
)
@@ -194,7 +216,7 @@ describe('ClsiCookieManager', function () {
null
)
expect(this.redis.setex).to.have.been.calledWith(
`clsiserver:${this.project_id}:${this.user_id}`,
`clsiserver:n2d:${this.project_id}:${this.user_id}`,
this.settings.clsiCookie.ttlInSecondsRegular,
this.clsiServerId
)
@@ -245,7 +267,7 @@ describe('ClsiCookieManager', function () {
null
)
this.redis_secondary.setex.should.have.been.calledWith(
`clsiserver:${this.project_id}:${this.user_id}`,
`clsiserver:n2d:${this.project_id}:${this.user_id}`,
this.settings.clsiCookie.ttlInSeconds,
this.clsiServerId
)