From 35f374047aab09b62dbaa28a5b5f1be5ba5b42b4 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 16 Nov 2025 14:20:26 -0500 Subject: [PATCH] ssb: Faster loads around the profile page. An experiment with caching SQL queries, and make one query just plain faster. --- apps/ssb.json | 2 +- apps/ssb/app.js | 35 ++++++++++++++++++++++++++++++----- apps/ssb/tf-profile.js | 18 +++++++++++++----- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/apps/ssb.json b/apps/ssb.json index fe355fc1..a169a749 100644 --- a/apps/ssb.json +++ b/apps/ssb.json @@ -1,5 +1,5 @@ { "type": "tildefriends-app", "emoji": "🦀", - "previous": "&k075s5Ij+NjRS52GJ6AD8Pl2ZGNseuj63AKBeV4LMQg=.sha256" + "previous": "&Tozi8g5lPpLW7znc+jm7xDZxoTq1G3DJAh2YhG+FRAQ=.sha256" } diff --git a/apps/ssb/app.js b/apps/ssb/app.js index 163903b4..3373c85f 100644 --- a/apps/ssb/app.js +++ b/apps/ssb/app.js @@ -2,6 +2,7 @@ import * as tfrpc from '/tfrpc.js'; let g_database; let g_hash; +let g_sql_cache = {}; tfrpc.register(async function localStorageGet(key) { return app.localStorageGet(key); @@ -51,14 +52,38 @@ tfrpc.register(async function connect(token) { tfrpc.register(async function closeConnection(id) { await ssb.closeConnection(id); }); -tfrpc.register(async function query(sql, args) { +tfrpc.register(async function query(sql, args, options) { let start = new Date(); let result = []; - await ssb.sqlAsync(sql, args, function callback(row) { - result.push(row); - }); + let key = options?.cacheable ? JSON.stringify([sql, args]) : undefined; + let entry = key ? g_sql_cache[key] : undefined; + const k_ideal_count = 64; + if (entry) { + result = entry.result; + } else { + await ssb.sqlAsync(sql, args, function callback(row) { + result.push(row); + }); + if (key) { + g_sql_cache[key] = { + result: result, + time: new Date().valueOf(), + }; + if (Object.keys(g_sql_cache).length > k_ideal_count * 2) { + let aged = Object.entries(g_sql_cache).map(([k, v]) => [v.time, k]); + aged.sort(); + for (let i = 0; i < aged.length / 2; i++) { + delete g_sql_cache[aged[i][1]]; + } + } + } + } let end = new Date(); - print((end - start) / 1000, sql.replaceAll(/\s+/g, ' ').trim()); + print( + (end - start) / 1000, + entry ? 'from cache' : 'from db', + sql.replaceAll(/\s+/g, ' ').trim() + ); return result; }); tfrpc.register(async function appendMessage(id, message) { diff --git a/apps/ssb/tf-profile.js b/apps/ssb/tf-profile.js index 208d2834..240fb4c9 100644 --- a/apps/ssb/tf-profile.js +++ b/apps/ssb/tf-profile.js @@ -37,16 +37,22 @@ class TfProfileElement extends LitElement { this.following = undefined; this.blocking = undefined; + let latest = ( + await tfrpc.rpc.query('SELECT MAX(rowid) AS latest FROM messages') + )[0].latest; + let result = await tfrpc.rpc.query( ` SELECT json_extract(content, '$.following') AS following FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' AND json_extract(content, '$.contact') = ? AND - following IS NOT NULL + following IS NOT NULL AND + messages.rowid <= ? ORDER BY sequence DESC LIMIT 1 `, - [this.whoami, this.id] + [this.whoami, this.id, latest], + {cacheable: true} ); this.following = result?.[0]?.following ?? false; result = await tfrpc.rpc.query( @@ -55,10 +61,12 @@ class TfProfileElement extends LitElement { FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' AND json_extract(content, '$.contact') = ? AND - blocking IS NOT NULL + blocking IS NOT NULL AND + messages.rowid <= ? ORDER BY sequence DESC LIMIT 1 `, - [this.whoami, this.id] + [this.whoami, this.id, latest], + {cacheable: true} ); this.blocking = result?.[0]?.blocking ?? false; } @@ -238,7 +246,7 @@ class TfProfileElement extends LitElement { let profile = this.users[this.id] || {}; tfrpc.rpc .query( - `SELECT SUM(LENGTH(content)) AS size, MAX(sequence) AS sequence FROM messages WHERE author = ?`, + `SELECT size AS size, max_sequence AS sequence FROM messages_stats WHERE author = ?`, [this.id] ) .then(function (result) {