fix: optimize LID migration performance and introduce cache (#1782)

* chore: lid-map cache, bulk migration, fixes

* fix: type error

* fix: signalrepository with lid type, remove check from assertsessions, lid-mapping store level deduplication

* fix: migrations logs

* fix: remove migrationsops lenght check

* fix: skip deletesession if cached or existing migrations

* chore: remove check migrated session

* fix: remove return null loadsession

* fix: getLIDforPN call and jidsrequiringfetch

* fix: longer lid map cache TTL

* fix: lint

* fix: lid type

* fix: lid socket type
This commit is contained in:
Gustavo Quadri
2025-09-14 15:58:59 -03:00
committed by GitHub
parent 42c980778e
commit 194b557b0f
10 changed files with 241 additions and 220 deletions
+66 -23
View File
@@ -1,9 +1,14 @@
import { LRUCache } from 'lru-cache'
import type { SignalKeyStoreWithTransaction } from '../Types'
import logger from '../Utils/logger'
import { isLidUser, isPnUser, jidDecode } from '../WABinary'
//TODO: Caching
export class LIDMappingStore {
private readonly mappingCache = new LRUCache<string, string>({
ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
ttlAutopurge: true,
updateAgeOnGet: true
})
private readonly keys: SignalKeyStoreWithTransaction
private onWhatsAppFunc?: (...jids: string[]) => Promise<
| {
@@ -29,13 +34,6 @@ export class LIDMappingStore {
this.onWhatsAppFunc = onWhatsAppFunc // needed to get LID from PN if not found
}
/**
* Store LID-PN mapping - USER LEVEL
*/
async storeLIDPNMapping(lid: string, pn: string): Promise<void> {
return this.storeLIDPNMappings([{ lid, pn }])
}
/**
* Store LID-PN mapping - USER LEVEL
*/
@@ -57,6 +55,25 @@ export class LIDMappingStore {
const pnUser = pnDecoded.user
const lidUser = lidDecoded.user
// Check if mapping already exists (cache first, then database)
let existingLidUser = this.mappingCache.get(`pn:${pnUser}`)
if (!existingLidUser) {
// Cache miss - check database
const stored = await this.keys.get('lid-mapping', [pnUser])
existingLidUser = stored[pnUser]
if (existingLidUser) {
// Update cache with database value
this.mappingCache.set(`pn:${pnUser}`, existingLidUser)
this.mappingCache.set(`lid:${existingLidUser}`, pnUser)
}
}
if (existingLidUser === lidUser) {
logger.debug({ pnUser, lidUser }, 'LID mapping already exists, skipping')
continue
}
pairMap[pnUser] = lidUser
}
@@ -70,6 +87,10 @@ export class LIDMappingStore {
[`${lidUser}_reverse`]: pnUser // "102765716062358_reverse" -> "554396160286"
}
})
// Update cache with both directions
this.mappingCache.set(`pn:${pnUser}`, lidUser)
this.mappingCache.set(`lid:${lidUser}`, pnUser)
}
}, 'lid-mapping')
}
@@ -83,22 +104,38 @@ export class LIDMappingStore {
const decoded = jidDecode(pn)
if (!decoded) return null
// Look up user-level mapping (whatsmeow approach)
// Check cache first for PN → LID mapping
const pnUser = decoded.user
const stored = await this.keys.get('lid-mapping', [pnUser])
let lidUser = stored[pnUser]
let lidUser = this.mappingCache.get(`pn:${pnUser}`)
if (!lidUser) {
logger.trace(`No LID mapping found for PN user ${pnUser}; getting from USync`)
const { exists, lid } = (await this.onWhatsAppFunc?.(pn))?.[0]! // this function already adds LIDs to mapping
if (exists) {
lidUser = jidDecode(lid)?.user
// Cache miss - check database
const stored = await this.keys.get('lid-mapping', [pnUser])
lidUser = stored[pnUser]
if (lidUser) {
// Cache the database result
this.mappingCache.set(`pn:${pnUser}`, lidUser)
} else {
return null
// Not in database - try USync
logger.trace(`No LID mapping found for PN user ${pnUser}; getting from USync`)
const { exists, lid } = (await this.onWhatsAppFunc?.(pn))?.[0]! // this function already adds LIDs to mapping
if (exists && lid) {
lidUser = jidDecode(lid)?.user
if (lidUser) {
// Cache the USync result
this.mappingCache.set(`pn:${pnUser}`, lidUser)
}
} else {
return null
}
}
}
if (typeof lidUser !== 'string') return null
if (typeof lidUser !== 'string' || !lidUser) {
logger.warn(`Invalid or empty LID user for PN ${pn}: lidUser = "${lidUser}"`)
return null
}
// Push the PN device ID to the LID to maintain device separation
const pnDevice = decoded.device !== undefined ? decoded.device : 0
@@ -117,15 +154,21 @@ export class LIDMappingStore {
const decoded = jidDecode(lid)
if (!decoded) return null
// Look up reverse user mapping
// Check cache first for LID → PN mapping
const lidUser = decoded.user
// TODO: remove this style and instead load all mappings somehow, and then assign them in the map
const stored = await this.keys.get('lid-mapping', [`${lidUser}_reverse`])
const pnUser = stored[`${lidUser}_reverse`]
let pnUser = this.mappingCache.get(`lid:${lidUser}`)
if (!pnUser || typeof pnUser !== 'string') {
logger.trace(`No reverse mapping found for LID user: ${lidUser}`)
return null
// Cache miss - check database
const stored = await this.keys.get('lid-mapping', [`${lidUser}_reverse`])
pnUser = stored[`${lidUser}_reverse`]
if (!pnUser || typeof pnUser !== 'string') {
logger.trace(`No reverse mapping found for LID user: ${lidUser}`)
return null
}
this.mappingCache.set(`lid:${lidUser}`, pnUser)
}
// Construct device-specific PN JID