perf(cache): O(1) author lookup for metadata invalidation
consumeMetadata() was looping ALL notes in cache (O(N)) to find notes by the author whose metadata changed. With hundreds of cached notes, this caused lag on every metadata event. Added notesByAuthor index (ConcurrentHashMap<HexKey, MutableSet<Note>>) populated at loadEvent time. consumeMetadata now looks up notes by author in O(1) instead of scanning the entire cache. Before: ~500 notes × per metadata event = lag After: ~3 notes per author × per metadata event = instant Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+24
-4
@@ -85,8 +85,18 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
companion object {
|
companion object {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Index of notes by author pubkey — for fast metadata invalidation */
|
||||||
|
private val notesByAuthor = ConcurrentHashMap<HexKey, MutableSet<Note>>()
|
||||||
|
|
||||||
val paymentTracker = NwcPaymentTracker()
|
val paymentTracker = NwcPaymentTracker()
|
||||||
|
|
||||||
|
private fun trackNoteAuthor(
|
||||||
|
note: Note,
|
||||||
|
authorPubkey: HexKey,
|
||||||
|
) {
|
||||||
|
notesByAuthor.getOrPut(authorPubkey) { ConcurrentHashMap.newKeySet() }.add(note)
|
||||||
|
}
|
||||||
|
|
||||||
// ----- User operations -----
|
// ----- User operations -----
|
||||||
|
|
||||||
override fun getUserIfExists(pubkey: HexKey): User? = users.get(pubkey)
|
override fun getUserIfExists(pubkey: HexKey): User? = users.get(pubkey)
|
||||||
@@ -156,10 +166,9 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
if (newUserMetadata != null) {
|
if (newUserMetadata != null) {
|
||||||
user.updateUserInfo(newUserMetadata, event)
|
user.updateUserInfo(newUserMetadata, event)
|
||||||
_metadataVersion.value++
|
_metadataVersion.value++
|
||||||
// Invalidate metadata flows on notes by this author that have observers
|
// Invalidate metadata flows on notes by this author (O(K) via index)
|
||||||
// so QuotedNoteEmbed/FeedNoteCard recompose with updated avatar/name
|
notesByAuthor[event.pubKey]?.forEach { note ->
|
||||||
notes.forEach { _, note ->
|
if (note.flowSet?.metadata?.hasObservers() == true) {
|
||||||
if (note.author?.pubkeyHex == event.pubKey && note.flowSet?.metadata?.hasObservers() == true) {
|
|
||||||
note.flowSet?.metadata?.invalidateData()
|
note.flowSet?.metadata?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,6 +254,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) }
|
val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) }
|
||||||
note.loadEvent(event, author, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
repliesTo.forEach { it.addReply(note) }
|
repliesTo.forEach { it.addReply(note) }
|
||||||
return true
|
return true
|
||||||
@@ -263,6 +273,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) }
|
val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) }
|
||||||
note.loadEvent(event, author, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
repliesTo.forEach { it.addReply(note) }
|
repliesTo.forEach { it.addReply(note) }
|
||||||
return true
|
return true
|
||||||
@@ -283,6 +294,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
event.originalPost().mapNotNull { getNoteIfExists(it) } +
|
event.originalPost().mapNotNull { getNoteIfExists(it) } +
|
||||||
event.taggedAddresses().mapNotNull { addressableNotes.get(it.toValue()) }
|
event.taggedAddresses().mapNotNull { addressableNotes.get(it.toValue()) }
|
||||||
note.loadEvent(event, author, reactedTo)
|
note.loadEvent(event, author, reactedTo)
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
reactedTo.forEach { it.addReaction(note) }
|
reactedTo.forEach { it.addReaction(note) }
|
||||||
return true
|
return true
|
||||||
@@ -300,6 +312,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
if (note.event != null) return false
|
if (note.event != null) return false
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
note.loadEvent(event, author, emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -331,6 +344,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
event.taggedAddresses().mapNotNull { addressableNotes.get(it.toValue()) }
|
event.taggedAddresses().mapNotNull { addressableNotes.get(it.toValue()) }
|
||||||
|
|
||||||
note.loadEvent(event, author, zappedNotes)
|
note.loadEvent(event, author, zappedNotes)
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
|
|
||||||
// Link zap to target notes
|
// Link zap to target notes
|
||||||
@@ -358,6 +372,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
val boostedNote = boostedId?.let { getOrCreateNote(it) }
|
val boostedNote = boostedId?.let { getOrCreateNote(it) }
|
||||||
val repliesTo = listOfNotNull(boostedNote)
|
val repliesTo = listOfNotNull(boostedNote)
|
||||||
note.loadEvent(event, author, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
boostedNote?.addBoost(note)
|
boostedNote?.addBoost(note)
|
||||||
return true
|
return true
|
||||||
@@ -377,6 +392,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
val boostedNote = event.boostedEventId()?.let { getOrCreateNote(it) }
|
val boostedNote = event.boostedEventId()?.let { getOrCreateNote(it) }
|
||||||
val repliesTo = listOfNotNull(boostedNote)
|
val repliesTo = listOfNotNull(boostedNote)
|
||||||
note.loadEvent(event, author, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
boostedNote?.addBoost(note)
|
boostedNote?.addBoost(note)
|
||||||
return true
|
return true
|
||||||
@@ -408,6 +424,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
if (note.event != null) return false
|
if (note.event != null) return false
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
note.loadEvent(event, author, emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -467,6 +484,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
if (note.event != null) return false
|
if (note.event != null) return false
|
||||||
|
|
||||||
note.loadEvent(event, author, emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
|
|
||||||
zappedNote?.addZapPayment(note, null)
|
zappedNote?.addZapPayment(note, null)
|
||||||
@@ -498,6 +516,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
if (note.event != null) return false
|
if (note.event != null) return false
|
||||||
|
|
||||||
note.loadEvent(event, author, emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
trackNoteAuthor(note, event.pubKey)
|
||||||
relay?.let { note.addRelay(it) }
|
relay?.let { note.addRelay(it) }
|
||||||
|
|
||||||
// Link response to zapped note via request
|
// Link response to zapped note via request
|
||||||
@@ -617,6 +636,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
_followedUsers.value = emptySet()
|
_followedUsers.value = emptySet()
|
||||||
followerCounts.clear()
|
followerCounts.clear()
|
||||||
followingCounts.clear()
|
followingCounts.clear()
|
||||||
|
notesByAuthor.clear()
|
||||||
lastContactListCreatedAt = 0L
|
lastContactListCreatedAt = 0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user