mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-31 08:56:48 +00:00
⚡ perf: Append Saved Message Ids Instead of Rebuilding the Conversation Array (#15141)
* ⚡ perf: Append Saved Message Ids Instead of Rebuilding the Conversation Array Every saveConvo read every message id in the conversation (sorted) and wrote the array back onto the document — twice per chat turn, O(n) in conversation length, from a write path. The turn's savers know exactly which message they just wrote, so they now pass it as metadata.appendMessageIds and saveConvo $addToSet-s it, skipping the read and the full-array rewrite. Every save without the option — titles, archive, fork, import, threads — still rebuilds from the database, which remains the heal point for the drift that message deletion has always left behind (deletes never ran saveConvo). The array's consumers read presence or length, or use it as an optimistic cache placeholder, so incremental maintenance is behaviorally identical; on traced turns the array stays exactly equal to the messages collection. Per-turn queries: 15 -> 13 (two Message.find gone), and the growing array payload no longer crosses the wire twice per turn. * 🎯 fix: Brand the Lineage-Only Resolved Conversation Instead of Guessing by Shape The resolved-conversation files fast path treated an absent files property as unresolved so the lineage-only partial from a bound agent-event continuation could not silently hide a conversation's uploads. But MongoDB never stores an empty files array, so nearly every real conversation also lacks the property and the fast path never fired — a follow-up turn on an upload-free conversation still paid the getConvoFiles round trip. The synthesized partial is the one object that cannot speak for the database, so it now carries an explicit symbol brand (PARTIAL_RESOLVED_CONVERSATION, non-serializing and invisible to key iteration), and a stored document without files means what it means: no files. Traced follow-up turns drop from 14 queries to 13. * 🧪 test: Expect the Appended Message Id in the Route's saveConvo Metadata messages-get.spec.js pins the exact metadata POST /api/messages passes to saveConvo; the route now forwards the saved message's _id as appendMessageIds, which is the behavior the append path depends on.
This commit is contained in:
parent
9da51cb507
commit
d864597731
9 changed files with 159 additions and 8 deletions
|
|
@ -1315,6 +1315,7 @@ class BaseClient {
|
|||
unsetFields,
|
||||
noUpsert: req?._agentEventBindingParentConversationId != null,
|
||||
createdAtOnInsert: shouldSetCreatedAtOnInsert ? validCreatedAtOnInsert : undefined,
|
||||
...(savedMessage?._id != null ? { appendMessageIds: [savedMessage._id] } : {}),
|
||||
});
|
||||
|
||||
return { message: savedMessage, conversation };
|
||||
|
|
|
|||
|
|
@ -1871,6 +1871,36 @@ describe('BaseClient', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('saveMessageToDatabase appends the saved message id instead of rebuilding the array', async () => {
|
||||
const savedId = new (require('mongoose').Types.ObjectId)();
|
||||
saveMessage.mockResolvedValueOnce({ _id: savedId, messageId: 'saved-1' });
|
||||
saveConvo.mockResolvedValueOnce({ conversationId });
|
||||
|
||||
await TestClient.saveMessageToDatabase(
|
||||
{ messageId: 'saved-1', conversationId, text: 'hi' },
|
||||
TestClient.getSaveOptions(),
|
||||
);
|
||||
|
||||
expect(saveConvo).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ appendMessageIds: [savedId] }),
|
||||
);
|
||||
});
|
||||
|
||||
test('saveMessageToDatabase rebuilds the array when the saved message has no _id', async () => {
|
||||
saveMessage.mockResolvedValueOnce({ messageId: 'saved-2' });
|
||||
saveConvo.mockResolvedValueOnce({ conversationId });
|
||||
|
||||
await TestClient.saveMessageToDatabase(
|
||||
{ messageId: 'saved-2', conversationId, text: 'hi' },
|
||||
TestClient.getSaveOptions(),
|
||||
);
|
||||
|
||||
const metadata = saveConvo.mock.calls[saveConvo.mock.calls.length - 1][2];
|
||||
expect(metadata).not.toHaveProperty('appendMessageIds');
|
||||
});
|
||||
|
||||
test('saveMessageToDatabase returns early when this.options is null (client disposed)', async () => {
|
||||
const savedOptions = TestClient.options;
|
||||
TestClient.options = null;
|
||||
|
|
|
|||
|
|
@ -443,7 +443,10 @@ describe('message route conversation ownership filters', () => {
|
|||
model: savedMessage.model,
|
||||
iconURL: savedMessage.iconURL,
|
||||
},
|
||||
{ context: 'POST /api/messages/:conversationId' },
|
||||
{
|
||||
context: 'POST /api/messages/:conversationId',
|
||||
appendMessageIds: [savedMessage._id],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -504,6 +504,7 @@ router.post('/:conversationId', storedMessageMutationMiddleware, async (req, res
|
|||
};
|
||||
await db.saveConvo(reqCtx, conversationUpdate, {
|
||||
context: 'POST /api/messages/:conversationId',
|
||||
...(savedMessage._id != null ? { appendMessageIds: [savedMessage._id] } : {}),
|
||||
});
|
||||
res.status(201).json(savedMessage);
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue