Sasha commited on
Commit
80fc693
·
1 Parent(s): 613007f

Optimize message ingestion with logChatMessagesBatch: bulk upsert to database.

Browse files
Files changed (2) hide show
  1. server/db.js +128 -0
  2. server/server.js +6 -6
server/db.js CHANGED
@@ -565,6 +565,134 @@ export async function logChatMessage(msg) {
565
  }
566
  }
567
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  /**
569
  * Log voice words (bulk insert)
570
  */
 
565
  }
566
  }
567
 
568
+ /**
569
+ * Log chat messages in batch (performance optimized)
570
+ */
571
+ export async function logChatMessagesBatch(messages) {
572
+ if (!messages || messages.length === 0) return;
573
+
574
+ // 1. Fetch all streams to search in-memory instead of making DB queries for each message
575
+ const streams = await getStreamsList();
576
+ const activeStream = await getActiveStream();
577
+
578
+ const getStreamIdForTime = (timestampIso) => {
579
+ if (!timestampIso) return activeStream ? activeStream.id : null;
580
+ const time = new Date(timestampIso);
581
+ const matchedStream = streams.find(s => {
582
+ const start = new Date(s.start_time);
583
+ const isAfterStart = start <= time;
584
+ const isBeforeEnd = !s.end_time || new Date(s.end_time) >= time;
585
+ return isAfterStart && isBeforeEnd;
586
+ });
587
+ return matchedStream ? matchedStream.id : (activeStream ? activeStream.id : null);
588
+ };
589
+
590
+ if (dbMode === 'sqlite') {
591
+ const insertMsg = sqliteDb.prepare(`
592
+ INSERT INTO messages (id, stream_id, username, display_name, message, timestamp, is_streamer, is_mod, is_sub)
593
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
594
+ `);
595
+
596
+ const insertViewer = sqliteDb.prepare(`
597
+ INSERT INTO stream_viewers (stream_id, username, display_name, has_chatted, is_mod, is_sub, first_seen)
598
+ VALUES (?, ?, ?, 1, ?, ?, ?)
599
+ ON CONFLICT(stream_id, username) DO UPDATE SET
600
+ has_chatted = 1,
601
+ is_mod = excluded.is_mod,
602
+ is_sub = excluded.is_sub
603
+ `);
604
+
605
+ const runTransaction = sqliteDb.transaction((msgs) => {
606
+ for (const msg of msgs) {
607
+ const streamId = getStreamIdForTime(msg.timestamp);
608
+ try {
609
+ insertMsg.run(
610
+ msg.id,
611
+ streamId,
612
+ msg.username.toLowerCase(),
613
+ msg.displayName || msg.username,
614
+ msg.message,
615
+ msg.timestamp || new Date().toISOString(),
616
+ msg.isStreamer ? 1 : 0,
617
+ msg.isMod ? 1 : 0,
618
+ msg.isSub ? 1 : 0
619
+ );
620
+ } catch (err) {
621
+ if (!err.message.includes('UNIQUE constraint failed')) {
622
+ console.error('[Database] SQLite Batch Error saving message:', err.message);
623
+ }
624
+ }
625
+
626
+ try {
627
+ insertViewer.run(
628
+ streamId,
629
+ msg.username.toLowerCase(),
630
+ msg.displayName || msg.username,
631
+ msg.isMod ? 1 : 0,
632
+ msg.isSub ? 1 : 0,
633
+ msg.timestamp || new Date().toISOString()
634
+ );
635
+ } catch (err) {
636
+ console.error('[Database] SQLite Batch Error updating viewer:', err.message);
637
+ }
638
+ }
639
+ });
640
+
641
+ runTransaction(messages);
642
+ } else {
643
+ // Supabase Mode: Build batch lists
644
+ const messagesToInsert = [];
645
+ const viewersToUpsert = new Map(); // Use Map to unique-fy viewers by stream_id + username
646
+
647
+ for (const msg of messages) {
648
+ const streamId = getStreamIdForTime(msg.timestamp);
649
+
650
+ messagesToInsert.push({
651
+ id: msg.id,
652
+ stream_id: streamId,
653
+ username: msg.username.toLowerCase(),
654
+ display_name: msg.displayName || msg.username,
655
+ message: msg.message,
656
+ timestamp: msg.timestamp || new Date().toISOString(),
657
+ is_streamer: msg.isStreamer || false,
658
+ is_mod: msg.isMod || false,
659
+ is_sub: msg.isSub || false
660
+ });
661
+
662
+ const viewerKey = `${streamId || 'null'}-${msg.username.toLowerCase()}`;
663
+ viewersToUpsert.set(viewerKey, {
664
+ stream_id: streamId,
665
+ username: msg.username.toLowerCase(),
666
+ display_name: msg.displayName || msg.username,
667
+ has_chatted: true,
668
+ is_mod: msg.isMod || false,
669
+ is_sub: msg.isSub || false,
670
+ first_seen: msg.timestamp || new Date().toISOString()
671
+ });
672
+ }
673
+
674
+ // 1. Bulk upsert messages
675
+ const { error: msgError } = await supabase
676
+ .from('messages')
677
+ .upsert(messagesToInsert, { onConflict: 'id' });
678
+
679
+ if (msgError) {
680
+ console.error('[Supabase] Bulk message upsert error:', msgError.message);
681
+ }
682
+
683
+ // 2. Bulk upsert viewers
684
+ const uniqueViewers = Array.from(viewersToUpsert.values());
685
+ const { error: viewerError } = await supabase
686
+ .from('stream_viewers')
687
+ .upsert(uniqueViewers, { onConflict: 'stream_id, username' });
688
+
689
+ if (viewerError) {
690
+ console.error('[Supabase] Bulk viewers upsert error:', viewerError.message);
691
+ }
692
+ }
693
+ }
694
+
695
+
696
  /**
697
  * Log voice words (bulk insert)
698
  */
server/server.js CHANGED
@@ -7,6 +7,7 @@ import path from 'path';
7
  import fs from 'fs';
8
  import {
9
  logChatMessage,
 
10
  logVoiceWords,
11
  logModAction,
12
  logViewerJoin,
@@ -175,12 +176,11 @@ app.post('/api/log/messages', authenticateWorker, async (req, res) => {
175
  return res.status(400).json({ error: 'Invalid input. Expected array of messages.' });
176
  }
177
 
178
- for (const msg of messages) {
179
- try {
180
- await logChatMessage(msg);
181
- } catch (e) {
182
- console.error('Error logging batch message:', e);
183
- }
184
  }
185
 
186
  res.json({ success: true, count: messages.length });
 
7
  import fs from 'fs';
8
  import {
9
  logChatMessage,
10
+ logChatMessagesBatch,
11
  logVoiceWords,
12
  logModAction,
13
  logViewerJoin,
 
176
  return res.status(400).json({ error: 'Invalid input. Expected array of messages.' });
177
  }
178
 
179
+ try {
180
+ await logChatMessagesBatch(messages);
181
+ } catch (err) {
182
+ console.error('Error logging batch messages:', err);
183
+ return res.status(500).json({ error: 'Failed to log messages' });
 
184
  }
185
 
186
  res.json({ success: true, count: messages.length });