[perf] dont immediately update view cache
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Evan Reichard 2024-01-21 11:29:26 -05:00
parent 9bd6bf7727
commit 2d63a7d109
7 changed files with 41 additions and 131 deletions

View File

@ -35,6 +35,7 @@ const (
adminBackup adminAction = "BACKUP" adminBackup adminAction = "BACKUP"
adminRestore adminAction = "RESTORE" adminRestore adminAction = "RESTORE"
adminMetadataMatch adminAction = "METADATA_MATCH" adminMetadataMatch adminAction = "METADATA_MATCH"
adminCacheTables adminAction = "CACHE_TABLES"
) )
type importType string type importType string
@ -373,6 +374,8 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
switch rAdminAction.Action { switch rAdminAction.Action {
case adminImport: case adminImport:
// TODO // TODO
case adminCacheTables:
go api.DB.CacheTempTables()
case adminMetadataMatch: case adminMetadataMatch:
// TODO // TODO
// 1. Documents xref most recent metadata table? // 1. Documents xref most recent metadata table?

View File

@ -172,13 +172,6 @@ func (api *API) koSetProgress(c *gin.Context) {
return return
} }
// Update Statistic
start := time.Now()
if err := api.DB.UpdateDocumentUserStatistic(rPosition.DocumentID, auth.UserName); err != nil {
log.Error("[koSetProgress] UpdateDocumentUserStatistic Error:", err)
}
log.Debug("[koSetProgress] UpdateDocumentUserStatistic Performance: ", time.Since(start))
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"document": progress.DocumentID, "document": progress.DocumentID,
"timestamp": progress.CreatedAt, "timestamp": progress.CreatedAt,
@ -301,15 +294,6 @@ func (api *API) koAddActivities(c *gin.Context) {
return return
} }
// Update Statistic
for _, doc := range allDocuments {
log.Info("[koAddActivities] UpdateDocumentUserStatistic Running...")
if err := api.DB.UpdateDocumentUserStatistic(doc, auth.UserName); err != nil {
log.Error("[koAddActivities] UpdateDocumentUserStatistic Error:", err)
}
log.Info("[koAddActivities] UpdateDocumentUserStatistic Complete")
}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"added": len(rActivity.Activity), "added": len(rActivity.Activity),
}) })

View File

@ -5,9 +5,11 @@ import (
"database/sql" "database/sql"
_ "embed" _ "embed"
"fmt" "fmt"
"path"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
"path"
"reichard.io/antholume/config" "reichard.io/antholume/config"
) )
@ -20,12 +22,6 @@ type DBManager struct {
//go:embed schema.sql //go:embed schema.sql
var ddl string var ddl string
//go:embed update_temp_tables.sql
var tsql string
//go:embed update_document_user_statistics.sql
var doc_user_stat_sql string
func NewMgr(c *config.Config) *DBManager { func NewMgr(c *config.Config) *DBManager {
// Create Manager // Create Manager
dbm := &DBManager{ dbm := &DBManager{
@ -63,24 +59,26 @@ func (dbm *DBManager) Shutdown() error {
return dbm.DB.Close() return dbm.DB.Close()
} }
func (dbm *DBManager) UpdateDocumentUserStatistic(documentID string, userID string) error {
// Prepare Statement
stmt, err := dbm.DB.PrepareContext(dbm.Ctx, doc_user_stat_sql)
if err != nil {
return err
}
defer stmt.Close()
// Execute
if _, err := stmt.ExecContext(dbm.Ctx, documentID, userID); err != nil {
return err
}
return nil
}
func (dbm *DBManager) CacheTempTables() error { func (dbm *DBManager) CacheTempTables() error {
if _, err := dbm.DB.ExecContext(dbm.Ctx, tsql); err != nil { start := time.Now()
user_streaks_sql := `
DELETE FROM user_streaks;
INSERT INTO user_streaks SELECT * FROM view_user_streaks;
`
if _, err := dbm.DB.ExecContext(dbm.Ctx, user_streaks_sql); err != nil {
return err return err
} }
log.Debug("[CacheTempTables] Cached 'user_streaks' in: ", time.Since(start))
start = time.Now()
document_statistics_sql := `
DELETE FROM document_user_statistics;
INSERT INTO document_user_statistics SELECT * FROM view_document_user_statistics;
`
if _, err := dbm.DB.ExecContext(dbm.Ctx, document_statistics_sql); err != nil {
return err
}
log.Debug("[CacheTempTables] Cached 'document_user_statistics' in: ", time.Since(start))
return nil return nil
} }

View File

@ -1,77 +0,0 @@
INSERT INTO document_user_statistics
WITH intermediate_ga AS (
SELECT
ga1.id AS row_id,
ga1.user_id,
ga1.document_id,
ga1.duration,
ga1.start_time,
ga1.start_percentage,
ga1.end_percentage,
-- Find Overlapping Events (Assign Unique ID)
(
SELECT MIN(id)
FROM activity AS ga2
WHERE
ga1.document_id = ga2.document_id
AND ga1.user_id = ga2.user_id
AND ga1.start_percentage <= ga2.end_percentage
AND ga1.end_percentage >= ga2.start_percentage
) AS group_leader
FROM activity AS ga1
WHERE
document_id = ?
AND user_id = ?
),
grouped_activity AS (
SELECT
user_id,
document_id,
MAX(start_time) AS start_time,
MIN(start_percentage) AS start_percentage,
MAX(end_percentage) AS end_percentage,
MAX(end_percentage) - MIN(start_percentage) AS read_percentage,
SUM(duration) AS duration
FROM intermediate_ga
GROUP BY group_leader
),
current_progress AS (
SELECT
user_id,
document_id,
COALESCE((
SELECT percentage
FROM document_progress AS dp
WHERE
dp.user_id = iga.user_id
AND dp.document_id = iga.document_id
ORDER BY created_at DESC
LIMIT 1
), end_percentage) AS percentage
FROM intermediate_ga AS iga
GROUP BY user_id, document_id
HAVING MAX(start_time)
)
SELECT
ga.document_id,
ga.user_id,
MAX(start_time) AS last_read,
SUM(duration) AS total_time_seconds,
SUM(read_percentage) AS read_percentage,
cp.percentage,
(CAST(COALESCE(d.words, 0.0) AS REAL) * SUM(read_percentage))
AS words_read,
(CAST(COALESCE(d.words, 0.0) AS REAL) * SUM(read_percentage))
/ (SUM(duration) / 60.0) AS wpm
FROM grouped_activity AS ga
INNER JOIN
current_progress AS cp
ON ga.user_id = cp.user_id AND ga.document_id = cp.document_id
INNER JOIN
documents AS d
ON d.id = ga.document_id
GROUP BY ga.document_id, ga.user_id
ORDER BY wpm DESC;

View File

@ -1,6 +0,0 @@
DELETE FROM user_streaks;
INSERT INTO user_streaks SELECT * FROM view_user_streaks;
DELETE FROM document_user_statistics;
INSERT INTO document_user_statistics
SELECT *
FROM view_document_user_statistics;

View File

@ -78,11 +78,11 @@ func (s *Server) StartServer(wg *sync.WaitGroup, done <-chan struct{}) {
} }
func (s *Server) RunScheduledTasks() { func (s *Server) RunScheduledTasks() {
log.Info("[RunScheduledTasks] Refreshing Temp Table Cache") start := time.Now()
if err := s.API.DB.CacheTempTables(); err != nil { if err := s.API.DB.CacheTempTables(); err != nil {
log.Warn("[RunScheduledTasks] Refreshing Temp Table Cache Failure:", err) log.Warn("[RunScheduledTasks] Refreshing Temp Table Cache Failure:", err)
} }
log.Info("[RunScheduledTasks] Refreshing Temp Table Success") log.Debug("[RunScheduledTasks] Completed in: ", time.Since(start))
} }
func (s *Server) StopServer(wg *sync.WaitGroup, done chan<- struct{}) { func (s *Server) StopServer(wg *sync.WaitGroup, done chan<- struct{}) {

View File

@ -163,15 +163,23 @@
<tr> <tr>
<td> <td>
<p>Logs</p> <p>Cache Tables</p>
</td> </td>
<td class="py-2 float-right"> <td class="py-2 float-right">
<a <form action="./admin" method="POST">
href="./admin/logs" <input
class="inline-block w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2" type="text"
> name="action"
<span class="w-full">View</span> value="CACHE_TABLES"
</a> class="hidden"
/>
<button
type="submit"
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2"
>
<span class="w-full">Run</span>
</button>
</form>
</td> </td>
</tr> </tr>
</tbody> </tbody>