Updating the getAllApps function to use a cached version of the app metadata, rather than retrieving it individually everytime. Also invalidating the results everytime they are updated (at least in the important locations).

This commit is contained in:
mike12345567 2021-11-15 17:40:45 +00:00
parent 0f54787072
commit f13257bebe
11 changed files with 63 additions and 5 deletions

View File

@ -1,3 +1,4 @@
module.exports = { module.exports = {
user: require("./src/cache/user"), user: require("./src/cache/user"),
app: require("./src/cache/appMetadata"),
} }

35
packages/auth/src/cache/appMetadata.js vendored Normal file
View File

@ -0,0 +1,35 @@
const redis = require("../redis/authRedis")
const { getDB } = require("../db")
const { DocumentTypes } = require("../db/constants")
const EXPIRY_SECONDS = 3600
/**
* The default populate app metadata function
*/
const populateFromDB = async appId => {
return getDB(appId, { skip_setup: true }).get(DocumentTypes.APP_METADATA)
}
/**
* Get the requested app metadata by id.
* Use redis cache to first read the app metadata.
* If not present fallback to loading the app metadata directly and re-caching.
* @param {*} appId the id of the app to get metadata from.
* @returns {object} the app metadata.
*/
exports.getAppMetadata = async appId => {
const client = await redis.getAppClient()
// try cache
let metadata = await client.get(appId)
if (!metadata) {
metadata = await populateFromDB(appId)
client.store(appId, metadata, EXPIRY_SECONDS)
}
return metadata
}
exports.invalidateAppMetadata = async appId => {
const client = await redis.getAppClient()
await client.delete(appId)
}

View File

@ -4,8 +4,8 @@ module.exports.setDB = pouch => {
Pouch = pouch Pouch = pouch
} }
module.exports.getDB = dbName => { module.exports.getDB = (dbName, opts = {}) => {
return new Pouch(dbName) return new Pouch(dbName, opts)
} }
module.exports.getCouch = () => { module.exports.getCouch = () => {

View File

@ -6,6 +6,7 @@ const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants")
const { getTenantId, getTenantIDFromAppID } = require("../tenancy") const { getTenantId, getTenantIDFromAppID } = require("../tenancy")
const fetch = require("node-fetch") const fetch = require("node-fetch")
const { getCouch } = require("./index") const { getCouch } = require("./index")
const { getAppMetadata } = require("../cache/appMetadata")
const UNICODE_MAX = "\ufff0" const UNICODE_MAX = "\ufff0"
@ -234,7 +235,7 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
} }
const appPromises = appDbNames.map(db => const appPromises = appDbNames.map(db =>
// skip setup otherwise databases could be re-created // skip setup otherwise databases could be re-created
new CouchDB(db, { skip_setup: true }).get(DocumentTypes.APP_METADATA) getAppMetadata(db)
) )
if (appPromises.length === 0) { if (appPromises.length === 0) {
return [] return []

View File

@ -1,16 +1,18 @@
const Client = require("./index") const Client = require("./index")
const utils = require("./utils") const utils = require("./utils")
let userClient, sessionClient let userClient, sessionClient, appClient
async function init() { async function init() {
userClient = await new Client(utils.Databases.USER_CACHE).init() userClient = await new Client(utils.Databases.USER_CACHE).init()
sessionClient = await new Client(utils.Databases.SESSIONS).init() sessionClient = await new Client(utils.Databases.SESSIONS).init()
appClient = await new Client(utils.Databases.APP_METADATA).init()
} }
process.on("exit", async () => { process.on("exit", async () => {
if (userClient) await userClient.finish() if (userClient) await userClient.finish()
if (sessionClient) await sessionClient.finish() if (sessionClient) await sessionClient.finish()
if (appClient) await appClient.finish()
}) })
module.exports = { module.exports = {
@ -26,4 +28,10 @@ module.exports = {
} }
return sessionClient return sessionClient
}, },
getAppClient: async () => {
if (!appClient) {
await init()
}
return appClient
},
} }

View File

@ -15,6 +15,7 @@ exports.Databases = {
SESSIONS: "session", SESSIONS: "session",
USER_CACHE: "users", USER_CACHE: "users",
FLAGS: "flags", FLAGS: "flags",
APP_METADATA: "appMetadata",
} }
exports.SEPARATOR = SEPARATOR exports.SEPARATOR = SEPARATOR

View File

@ -45,6 +45,7 @@ const {
} = require("../../utilities/fileSystem/clientLibrary") } = require("../../utilities/fileSystem/clientLibrary")
const { getTenantId, isMultiTenant } = require("@budibase/auth/tenancy") const { getTenantId, isMultiTenant } = require("@budibase/auth/tenancy")
const { syncGlobalUsers } = require("./user") const { syncGlobalUsers } = require("./user")
const { app: appCache } = require("@budibase/auth/cache")
const URL_REGEX_SLASH = /\/|\\/g const URL_REGEX_SLASH = /\/|\\/g
@ -319,6 +320,7 @@ exports.delete = async ctx => {
} }
// make sure the app/role doesn't stick around after the app has been deleted // make sure the app/role doesn't stick around after the app has been deleted
await removeAppFromUserRoles(ctx, ctx.params.appId) await removeAppFromUserRoles(ctx, ctx.params.appId)
await appCache.invalidateAppMetadata(ctx.params.appId)
ctx.status = 200 ctx.status = 200
ctx.body = result ctx.body = result
@ -387,7 +389,10 @@ const updateAppPackage = async (ctx, appPackage, appId) => {
// Redis, shouldn't ever store it // Redis, shouldn't ever store it
delete newAppPackage.lockedBy delete newAppPackage.lockedBy
return await db.put(newAppPackage) const response = await db.put(newAppPackage)
// remove any cached metadata, so that it will be updated
await appCache.invalidateAppMetadata(appId)
return response
} }
const createEmptyAppPackage = async (ctx, app) => { const createEmptyAppPackage = async (ctx, app) => {

View File

@ -6,6 +6,7 @@ const {
disableAllCrons, disableAllCrons,
enableCronTrigger, enableCronTrigger,
} = require("../../../automations/utils") } = require("../../../automations/utils")
const { app: appCache } = require("@budibase/auth/cache")
// the max time we can wait for an invalidation to complete before considering it failed // the max time we can wait for an invalidation to complete before considering it failed
const MAX_PENDING_TIME_MS = 30 * 60000 const MAX_PENDING_TIME_MS = 30 * 60000
@ -103,6 +104,7 @@ async function deployApp(deployment) {
appDoc.appId = productionAppId appDoc.appId = productionAppId
appDoc.instance._id = productionAppId appDoc.instance._id = productionAppId
await db.put(appDoc) await db.put(appDoc)
await appCache.invalidateAppMetadata(productionAppId)
console.log("New app doc written successfully.") console.log("New app doc written successfully.")
await initDeployedApp(productionAppId) await initDeployedApp(productionAppId)
console.log("Deployed app initialised, setting deployment to successful") console.log("Deployed app initialised, setting deployment to successful")

View File

@ -6,6 +6,7 @@ const { request } = require("../../utilities/workerRequests")
const { clearLock } = require("../../utilities/redis") const { clearLock } = require("../../utilities/redis")
const { Replication } = require("@budibase/auth").db const { Replication } = require("@budibase/auth").db
const { DocumentTypes } = require("../../db/utils") const { DocumentTypes } = require("../../db/utils")
const { app: appCache } = require("@budibase/auth/cache")
async function redirect(ctx, method, path = "global") { async function redirect(ctx, method, path = "global") {
const { devPath } = ctx.params const { devPath } = ctx.params
@ -106,6 +107,7 @@ exports.revert = async ctx => {
appDoc.appId = appId appDoc.appId = appId
appDoc.instance._id = appId appDoc.instance._id = appId
await db.put(appDoc) await db.put(appDoc)
await appCache.invalidateAppMetadata(appId)
ctx.body = { ctx.body = {
message: "Reverted changes successfully.", message: "Reverted changes successfully.",
} }

View File

@ -8,6 +8,7 @@ const {
const CouchDB = require("../db") const CouchDB = require("../db")
const { DocumentTypes } = require("../db/utils") const { DocumentTypes } = require("../db/utils")
const { PermissionTypes } = require("@budibase/auth/permissions") const { PermissionTypes } = require("@budibase/auth/permissions")
const { app: appCache } = require("@budibase/auth/cache")
const DEBOUNCE_TIME_SEC = 30 const DEBOUNCE_TIME_SEC = 30
@ -51,6 +52,7 @@ async function updateAppUpdatedAt(ctx) {
const metadata = await db.get(DocumentTypes.APP_METADATA) const metadata = await db.get(DocumentTypes.APP_METADATA)
metadata.updatedAt = new Date().toISOString() metadata.updatedAt = new Date().toISOString()
await db.put(metadata) await db.put(metadata)
await appCache.invalidateAppMetadata(appId)
// set a new debounce record with a short TTL // set a new debounce record with a short TTL
await setDebounce(appId, DEBOUNCE_TIME_SEC) await setDebounce(appId, DEBOUNCE_TIME_SEC)
} }

View File

@ -48,6 +48,7 @@ exports.objectStoreUrl = () => {
* via a specific endpoint (under /api/assets/client). * via a specific endpoint (under /api/assets/client).
* @param {string} appId In production we need the appId to look up the correct bucket, as the * @param {string} appId In production we need the appId to look up the correct bucket, as the
* version of the client lib may differ between apps. * version of the client lib may differ between apps.
* @param {string} version The version to retrieve.
* @return {string} The URL to be inserted into appPackage response or server rendered * @return {string} The URL to be inserted into appPackage response or server rendered
* app index file. * app index file.
*/ */