Diffstat (limited to 'internal/router/client-cache.go')
| -rw-r--r-- | internal/router/client-cache.go | 272 |
1 files changed, 77 insertions, 195 deletions
diff --git a/internal/router/client-cache.go b/internal/router/client-cache.go index 7c28b9e..56cd595 100644 --- a/internal/router/client-cache.go +++ b/internal/router/client-cache.go @@ -11,88 +11,60 @@ import ( "golang.org/x/crypto/argon2" ) +type PageLike struct { + PageRef string + UserId string +} + type ClientCache struct { hashMap map[string]string - hashMapMutex sync.RWMutex - - likePageMap map[string]map[string]struct{} - viewPageMap map[string]map[string]struct{} - pageMutexMap map[string]*sync.RWMutex - pageMutexMapMutex sync.Mutex - - salt []byte - db *sql.DB + mutexLikeMap map[string]*sync.Mutex + mutexHashMap map[string]*sync.Mutex + likePageMap map[string]map[string]struct{} + salt []byte + db *sql.DB } +var CCache *ClientCache + func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) { tx, err := db.Begin() if err != nil { return nil, fmt.Errorf("fail to init transaction with db to fill cache: %w", err) } - slog.Debug("began db transaction", slog.String("method", "NewClientCache")) rows, err := tx.Query("select * from blog_likes;") if err != nil { tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) return nil, fmt.Errorf("fail to query db for blog_likes to fill cache: %w", err) } likePageMap := make(map[string]map[string]struct{}) - viewPageMap := make(map[string]map[string]struct{}) - pageMutexMap := make(map[string]*sync.RWMutex) for rows.Next() { - var pageRef string - var userId []byte + pageRef := make([]byte, 32) + userId := make([]byte, 32) if err = rows.Scan(&pageRef, &userId); err != nil { tx.Rollback() return nil, fmt.Errorf("fail scanning blog_likes to fill cache: %w", err) } + pageRefString := string(pageRef) userIdString := base64.RawStdEncoding.EncodeToString(userId) - if _, ok := likePageMap[pageRef]; !ok { - likePageMap[pageRef] = make(map[string]struct{}) - viewPageMap[pageRef] = make(map[string]struct{}) - pageMutexMap[pageRef] = &sync.RWMutex{} + if _, ok := likePageMap[pageRefString]; !ok { + likePageMap[pageRefString] = make(map[string]struct{}) } - likePageMap[pageRef][userIdString] = struct{}{} - viewPageMap[pageRef][userIdString] = struct{}{} - } - - rows, err = tx.Query("select * from blog_views;") - if err != nil { - tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) - return nil, fmt.Errorf("fail to query db for blog_views to fill cache: %w", err) - } - - for rows.Next() { - var pageRef string - var userId []byte - if err = rows.Scan(&pageRef, &userId); err != nil { - tx.Rollback() - return nil, fmt.Errorf("fail scanning blog_views to fill cache: %w", err) - } - userIdString := base64.RawStdEncoding.EncodeToString(userId) - if _, ok := viewPageMap[pageRef]; !ok { - viewPageMap[pageRef] = make(map[string]struct{}) - pageMutexMap[pageRef] = &sync.RWMutex{} - } - viewPageMap[pageRef][userIdString] = struct{}{} + likePageMap[pageRefString][userIdString] = struct{}{} } if err = tx.Commit(); err != nil { - tx.Rollback() - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) return nil, fmt.Errorf("fail to commit transaction in db: %w", err) } - slog.Debug("ended db transaction", slog.String("method", "NewClientCache")) return &ClientCache{ hashMap: make(map[string]string), + mutexLikeMap: make(map[string]*sync.Mutex), + mutexHashMap: make(map[string]*sync.Mutex), likePageMap: likePageMap, - viewPageMap: viewPageMap, - pageMutexMap: pageMutexMap, salt: salt, db: db, }, nil @@ -103,41 +75,65 @@ func (c *ClientCache) Close() error { if err != nil { return fmt.Errorf("fail to init transaction with db to dump cache: %w", err) } - slog.Debug("began db transaction in ClientCache.Close") - if err = batchSave(tx, "blog_likes", c.likePageMap); err != nil { - tx.Rollback() - slog.Debug("ended db transaction in ClientCache.Close") - return fmt.Errorf("fail to save blog_likes: %s", err) - } + userIdBytes := make(map[string][]byte) - if err = batchSave(tx, "blog_views", c.viewPageMap); err != nil { - tx.Rollback() - slog.Debug("ended db transaction in ClientCache.Close") - return fmt.Errorf("fail to save blog_views: %s", err) + sqlStatement := fmt.Sprintf(` + INSERT OR IGNORE INTO blog_likes (page_ref, user_id) + VALUES %s(?, ?); + `, strings.Repeat("(?, ?), ", 99)) + sqlStatementVars := make([]interface{}, 0, 200) + + for pageRef, userSet := range c.likePageMap { + pageRefBytes := []byte(pageRef) + + for userId := range userSet { + if _, ok := userIdBytes[userId]; !ok { + userIdBytes[userId], err = base64.RawStdEncoding.DecodeString(userId) + if err != nil { + slog.Warn("couldn't parse one of user hashes into bytes back", slog.String("hash", userId), slog.String("error", err.Error())) + continue + } + } + + sqlStatementVars = append(sqlStatementVars, interface{}(pageRefBytes), interface{}(userIdBytes[userId])) + if len(sqlStatementVars) < 200 { + continue + } + + if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { + slog.Warn("couldn't insert blog like pairs into db", slog.String("error", err.Error())) + } + + sqlStatementVars = make([]interface{}, 0, 200) + } } - if err = tx.Commit(); err != nil { - tx.Rollback() - slog.Debug("ended db transaction in ClientCache.Close") - return fmt.Errorf("fail to commit all the changes related to cache: %s", err) + if len(sqlStatementVars) > 0 { + sqlStatement = fmt.Sprintf(` + INSERT OR IGNORE INTO blog_likes (page_ref, user_id) + VALUES %s(?, ?); + `, strings.Repeat("(?, ?), ", len(sqlStatementVars)/2-1)) + + if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { + slog.Warn("couldn't insert blog like pairs into db", slog.String("error", err.Error())) + } } - slog.Debug("ended db transaction in ClientCache.Close") - return nil + return tx.Commit() } func (c *ClientCache) GetHash(id string) string { - c.hashMapMutex.RLock() if val, ok := c.hashMap[id]; ok { - c.hashMapMutex.RUnlock() slog.Debug("gave an old hash", slog.String("hash", val)) return val } - c.hashMapMutex.RUnlock() - c.hashMapMutex.Lock() - defer c.hashMapMutex.Unlock() + if _, ok := c.mutexHashMap[id]; !ok { + c.mutexHashMap[id] = &sync.Mutex{} + } + c.mutexHashMap[id].Lock() + defer c.mutexHashMap[id].Unlock() if val, ok := c.hashMap[id]; ok { slog.Debug("gave a newly generated hash", slog.String("hash", val)) @@ -149,25 +145,7 @@ func (c *ClientCache) GetHash(id string) string { return c.hashMap[id] } -func (c *ClientCache) getPageMutex(page string) *sync.RWMutex { - c.pageMutexMapMutex.Lock() - defer c.pageMutexMapMutex.Unlock() - - if mutex, ok := c.pageMutexMap[page]; ok { - return mutex - } - - c.pageMutexMap[page] = &sync.RWMutex{} - return c.pageMutexMap[page] -} - func (c *ClientCache) GetLikeStatus(id string, page string) bool { - page = strings.Clone(page) - - mutex := c.getPageMutex(page) - mutex.RLock() - defer mutex.RUnlock() - if _, ok := c.likePageMap[page]; !ok { return false } @@ -176,12 +154,6 @@ func (c *ClientCache) GetLikeStatus(id string, page string) bool { } func (c *ClientCache) GetLikeCount(page string) int { - page = strings.Clone(page) - - mutex := c.getPageMutex(page) - mutex.RLock() - defer mutex.RUnlock() - if userSet, ok := c.likePageMap[page]; ok { return len(userSet) } @@ -189,12 +161,13 @@ func (c *ClientCache) GetLikeCount(page string) int { } func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) { - page = strings.Clone(page) - hash := c.GetHash(id) + if _, ok := c.mutexLikeMap[id]; !ok { + c.mutexLikeMap[id] = &sync.Mutex{} + } + c.mutexLikeMap[id].Lock() + defer c.mutexLikeMap[id].Unlock() - mutex := c.getPageMutex(page) - mutex.Lock() - defer mutex.Unlock() + hash := c.GetHash(id) if userSet, ok := c.likePageMap[page]; ok { _, alreadyLiked = userSet[hash] @@ -208,16 +181,16 @@ func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) { } func (c *ClientCache) LikeOff(id string, page string) (alreadyUnliked bool) { - page = strings.Clone(page) - hash := c.GetHash(id) - - mutex := c.getPageMutex(page) - mutex.Lock() - defer mutex.Unlock() + if _, ok := c.mutexLikeMap[id]; !ok { + c.mutexLikeMap[id] = &sync.Mutex{} + } + c.mutexLikeMap[id].Lock() + defer c.mutexLikeMap[id].Unlock() if _, ok := c.likePageMap[page]; !ok { return true } + hash := c.GetHash(id) if _, ok := c.likePageMap[page][hash]; !ok { return true } @@ -225,94 +198,3 @@ func (c *ClientCache) LikeOff(id string, page string) (alreadyUnliked bool) { delete(c.likePageMap[page], hash) return false } - -func (c *ClientCache) GetViewStatus(id string, page string) bool { - page = strings.Clone(page) - - mutex := c.getPageMutex(page) - mutex.RLock() - defer mutex.RUnlock() - - if _, ok := c.viewPageMap[page]; !ok { - return false - } - _, ok := c.viewPageMap[page][c.GetHash(id)] - return ok -} - -func (c *ClientCache) GetViewCount(page string) int { - page = strings.Clone(page) - - mutex := c.getPageMutex(page) - mutex.RLock() - defer mutex.RUnlock() - - if userSet, ok := c.viewPageMap[page]; ok { - return len(userSet) - } - return 0 -} - -func (c *ClientCache) View(id string, page string) { - page = strings.Clone(page) - hash := c.GetHash(id) - - mutex := c.getPageMutex(page) - mutex.Lock() - defer mutex.Unlock() - - if _, ok := c.viewPageMap[page]; !ok { - c.viewPageMap[page] = make(map[string]struct{}) - } - c.viewPageMap[page][hash] = struct{}{} -} - -func batchSave(tx *sql.Tx, table string, pageMap map[string]map[string]struct{}) (err error) { - if _, err = tx.Exec(fmt.Sprintf("delete from %s;", table)); err != nil { - return fmt.Errorf("fail to truncate table %s: %w", table, err) - } - - userIdBytes := make(map[string][]byte) - - sqlStatement := fmt.Sprintf(` - INSERT OR IGNORE INTO %s (page_ref, user_id) - VALUES %s(?, ?); - `, table, strings.Repeat("(?, ?), ", 99)) - sqlStatementVars := make([]any, 0, 200) - - for pageRef, userSet := range pageMap { - for userId := range userSet { - if _, ok := userIdBytes[userId]; !ok { - userIdBytes[userId], err = base64.RawStdEncoding.DecodeString(userId) - if err != nil { - slog.Warn("couldn't parse one of user hashes into bytes back", slog.String("hash", userId), slog.String("error", err.Error())) - continue - } - } - - sqlStatementVars = append(sqlStatementVars, any(pageRef), any(userIdBytes[userId])) - if len(sqlStatementVars) < 200 { - continue - } - - if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { - slog.Warn("couldn't insert blog stat pairs into db", slog.String("table", table), slog.String("error", err.Error())) - } - - sqlStatementVars = make([]any, 0, 200) - } - } - - if len(sqlStatementVars) > 0 { - sqlStatement = fmt.Sprintf(` - INSERT OR IGNORE INTO %s (page_ref, user_id) - VALUES %s(?, ?); - `, table, strings.Repeat("(?, ?), ", len(sqlStatementVars)/2-1)) - - if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { - slog.Warn("couldn't insert blog stat pairs into db", slog.String("table", table), slog.String("error", err.Error())) - } - } - - return nil -} |