mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 00:20:45 +00:00
perf: batching get_apps cache look ups (#2521)
should not be slow in any case, but this should help a bit more with speed in trade of more ram and cpu usage
This commit is contained in:
+74
-27
@@ -63,6 +63,43 @@ const safe_json_parse = (value, fallback) => {
|
||||
}
|
||||
};
|
||||
|
||||
const redisGetJsonMany = async (keys) => {
|
||||
if ( !Array.isArray(keys) || keys.length === 0 ) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
const uniqueKeys = [...new Set(keys)];
|
||||
let valuesByIndex = null;
|
||||
|
||||
// MGET over Redis Cluster can fail for cross-slot keys; use pipelined GETs there.
|
||||
if ( typeof redisClient.nodes === 'function' ) {
|
||||
const pipeline = redisClient.pipeline();
|
||||
for ( const key of uniqueKeys ) {
|
||||
pipeline.get(key);
|
||||
}
|
||||
const results = await pipeline.exec();
|
||||
if ( Array.isArray(results) ) {
|
||||
valuesByIndex = results.map((item) => {
|
||||
if ( !Array.isArray(item) || item.length < 2 ) return null;
|
||||
const [error, value] = item;
|
||||
return error ? null : value;
|
||||
});
|
||||
}
|
||||
} else if ( typeof redisClient.mget === 'function' ) {
|
||||
valuesByIndex = await redisClient.mget(...uniqueKeys);
|
||||
}
|
||||
|
||||
if ( ! Array.isArray(valuesByIndex) ) {
|
||||
valuesByIndex = await Promise.all(uniqueKeys.map(key => redisClient.get(key)));
|
||||
}
|
||||
|
||||
const valuesByKey = new Map();
|
||||
for ( let i = 0; i < uniqueKeys.length; i++ ) {
|
||||
valuesByKey.set(uniqueKeys[i], safe_json_parse(valuesByIndex[i], null));
|
||||
}
|
||||
return valuesByKey;
|
||||
};
|
||||
|
||||
const buildAppIconUrl = (app_uid, size = DEFAULT_APP_ICON_SIZE) => {
|
||||
if ( ! app_uid ) return null;
|
||||
const uid_string = String(app_uid);
|
||||
@@ -500,44 +537,54 @@ export const get_apps = spanify('get_apps', async (specifiers, options = {}) =>
|
||||
}
|
||||
};
|
||||
|
||||
for ( const spec of normalized ) {
|
||||
const cacheLookupPlan = normalized.map((spec) => {
|
||||
if ( spec.uid ) {
|
||||
const cached = await AppRedisCacheSpace.getCachedApp({
|
||||
return {
|
||||
lookup: 'uid',
|
||||
value: spec.uid,
|
||||
rawIcon: rawIcon,
|
||||
});
|
||||
if ( cached ) {
|
||||
addApp(decorateApp(cached));
|
||||
} else {
|
||||
queueMissing('uid', spec.uid);
|
||||
}
|
||||
continue;
|
||||
cacheKey: AppRedisCacheSpace.key({
|
||||
lookup: 'uid',
|
||||
value: spec.uid,
|
||||
rawIcon,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if ( spec.name ) {
|
||||
const cached = await AppRedisCacheSpace.getCachedApp({
|
||||
return {
|
||||
lookup: 'name',
|
||||
value: spec.name,
|
||||
rawIcon: rawIcon,
|
||||
});
|
||||
if ( cached ) {
|
||||
addApp(decorateApp(cached));
|
||||
} else {
|
||||
queueMissing('name', spec.name);
|
||||
}
|
||||
continue;
|
||||
cacheKey: AppRedisCacheSpace.key({
|
||||
lookup: 'name',
|
||||
value: spec.name,
|
||||
rawIcon,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if ( spec.id ) {
|
||||
const cached = await AppRedisCacheSpace.getCachedApp({
|
||||
return {
|
||||
lookup: 'id',
|
||||
value: spec.id,
|
||||
rawIcon: rawIcon,
|
||||
});
|
||||
if ( cached ) {
|
||||
addApp(decorateApp(cached));
|
||||
} else {
|
||||
queueMissing('id', spec.id);
|
||||
}
|
||||
cacheKey: AppRedisCacheSpace.key({
|
||||
lookup: 'id',
|
||||
value: spec.id,
|
||||
rawIcon,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const cachedAppsByKey = await redisGetJsonMany(
|
||||
cacheLookupPlan.filter(Boolean).map(item => item.cacheKey),
|
||||
);
|
||||
|
||||
for ( const plannedLookup of cacheLookupPlan ) {
|
||||
if ( ! plannedLookup ) continue;
|
||||
const cached = cachedAppsByKey.get(plannedLookup.cacheKey);
|
||||
if ( cached ) {
|
||||
addApp(decorateApp(cached));
|
||||
} else {
|
||||
queueMissing(plannedLookup.lookup, plannedLookup.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ const DEFAULT_ICON_SIZE = 128;
|
||||
const RAW_BASE64_REGEX = /^[A-Za-z0-9+/]+={0,2}$/;
|
||||
const LEGACY_ICON_FILENAME = ({ appUid, size }) => `${appUid}-${size}.png`;
|
||||
const ORIGINAL_ICON_FILENAME = ({ appUid }) => `${appUid}.png`;
|
||||
const REDIRECT_MAX_AGE_SIZE = 30 * 24 * 60 * 60; // 1 month
|
||||
const REDIRECT_MAX_AGE_ORIGINAL = 7 * 24 * 60 * 60; // 1 week
|
||||
const REDIRECT_MAX_AGE_SIZE = 15 * 60; // 15 min
|
||||
const REDIRECT_MAX_AGE_ORIGINAL = 60; // 1 min
|
||||
|
||||
/**
|
||||
* AppIconService handles icon generation and serving for apps.
|
||||
@@ -344,8 +344,10 @@ export class AppIconService extends BaseService {
|
||||
|
||||
async ensureAppIconsSubdomain ({ dirAppIcons }) {
|
||||
const dbSites = this.services.get('database').get(DB_WRITE, 'sites');
|
||||
const existing = await dbSites.read('SELECT * FROM subdomains WHERE subdomain = ? LIMIT 1',
|
||||
[APP_ICONS_SUBDOMAIN]);
|
||||
const existing = await dbSites.read(
|
||||
'SELECT * FROM subdomains WHERE subdomain = ? LIMIT 1',
|
||||
[APP_ICONS_SUBDOMAIN],
|
||||
);
|
||||
if ( existing[0] ) return existing[0];
|
||||
|
||||
const systemUser = await get_user({ username: 'system' });
|
||||
@@ -362,8 +364,10 @@ export class AppIconService extends BaseService {
|
||||
`sd-${this.modules.uuidv4()}`,
|
||||
]);
|
||||
|
||||
const rows = await dbSites.read('SELECT * FROM subdomains WHERE subdomain = ? LIMIT 1',
|
||||
[APP_ICONS_SUBDOMAIN]);
|
||||
const rows = await dbSites.read(
|
||||
'SELECT * FROM subdomains WHERE subdomain = ? LIMIT 1',
|
||||
[APP_ICONS_SUBDOMAIN],
|
||||
);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user