Diffstat (limited to 'internal/router/client-cache.go')
| -rw-r--r-- | internal/router/client-cache.go | 270 |
1 files changed, 25 insertions, 245 deletions
diff --git a/internal/router/client-cache.go b/internal/router/client-cache.go index 7c28b9e..3104a66 100644 --- a/internal/router/client-cache.go +++ b/internal/router/client-cache.go @@ -1,11 +1,8 @@ package router import ( - "database/sql" "encoding/base64" - "fmt" "log/slog" - "strings" "sync" "golang.org/x/crypto/argon2" @@ -13,131 +10,35 @@ import ( 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 } -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 - if err = rows.Scan(&pageRef, &userId); err != nil { - tx.Rollback() - return nil, fmt.Errorf("fail scanning blog_likes to fill cache: %w", err) - } - 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{} - } - 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{}{} - } - - 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")) +var CCache *ClientCache +func NewClientCache(salt []byte) *ClientCache { return &ClientCache{ hashMap: make(map[string]string), - likePageMap: likePageMap, - viewPageMap: viewPageMap, - pageMutexMap: pageMutexMap, + mutexLikeMap: make(map[string]*sync.Mutex), + mutexHashMap: make(map[string]*sync.Mutex), + likePageMap: make(map[string]map[string]struct{}), salt: salt, - db: db, - }, nil -} - -func (c *ClientCache) Close() error { - tx, err := c.db.Begin() - 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) } - - 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) - } - - 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) - } - - slog.Debug("ended db transaction in ClientCache.Close") - return nil } 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 +50,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 } @@ -175,27 +58,15 @@ func (c *ClientCache) GetLikeStatus(id string, page string) bool { return ok } -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) +func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) { + if _, ok := c.mutexLikeMap[id]; !ok { + c.mutexLikeMap[id] = &sync.Mutex{} } - return 0 -} + c.mutexLikeMap[id].Lock() + defer c.mutexLikeMap[id].Unlock() -func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) { - page = strings.Clone(page) hash := c.GetHash(id) - mutex := c.getPageMutex(page) - mutex.Lock() - defer mutex.Unlock() - if userSet, ok := c.likePageMap[page]; ok { _, alreadyLiked = userSet[hash] c.likePageMap[page][hash] = struct{}{} @@ -208,16 +79,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 +96,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 -} |