Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use redis as a cache to reduce database reads #53

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions cache/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
Copyright (C) 2024 Pagefault Games

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package cache

import (
"fmt"
"time"
)

func AddAccountSession(uuid []byte, token []byte) bool {
key := fmt.Sprintf("session:%s", token)
err := rdb.Set(key, string(uuid), 24*time.Hour).Err()
return err == nil
}

func FetchUsernameBySessionToken(token []byte) (string, bool) {
key := fmt.Sprintf("session:%s", token)
username, err := rdb.Get(key).Result()
if err != nil {
return "", false
}

return username, true
}

func UpdateAccountLastActivity(uuid []byte) bool {
key := fmt.Sprintf("account:%s", uuid)
err := rdb.HSet(key, "lastActivity", time.Now().Format("2006-01-02 15:04:05")).Err()
if err != nil {
return false
}
err = rdb.Expire(key, 5*time.Minute).Err()
return err == nil
}

func FetchTrainerIds(uuid []byte) (int, int, bool) {
key := fmt.Sprintf("account:%s", uuid)
vals, err := rdb.HMGet(key, "trainerId", "secretId").Result()
if err == nil && len(vals) == 2 && vals[0] != nil && vals[1] != nil {
trainerId, ok1 := vals[0].(int)
secretId, ok2 := vals[1].(int)
if ok1 && ok2 {
return trainerId, secretId, true
}
}

return 0, 0, false
}

func UpdateTrainerIds(trainerId, secretId int, uuid []byte) bool {
key := fmt.Sprintf("account:%s", uuid)
err := rdb.HMSet(key, map[string]interface{}{
"trainerId": trainerId,
"secretId": secretId,
}).Err()
if err != nil {
return false
}

err = rdb.Expire(key, 5*time.Minute).Err()
return err == nil
}

func IsActiveSession(uuid []byte, sessionId string) (bool, bool) {
key := fmt.Sprintf("active_sessions:%s", uuid)
id, err := rdb.Get(key).Result()
return id == sessionId, err == nil
}

func UpdateActiveSession(uuid []byte, sessionId string) bool {
key := fmt.Sprintf("active_sessions:%s", uuid)
err := rdb.Set(key, sessionId, 0).Err()
if err != nil {
return false
}
err = rdb.Expire(key, 5*time.Minute).Err()
if err != nil {
return false
}

err = rdb.SAdd("active_players", uuid).Err()
if err != nil {
return false
}
err = rdb.Expire("active_players", 5*time.Minute).Err()

return err == nil
}

func FetchUUIDFromToken(token []byte) ([]byte, bool) {
key := fmt.Sprintf("session:%s", token)
uuid, err := rdb.Get(key).Bytes()
return uuid, err == nil
}

func RemoveSessionFromToken(token []byte) bool {
key := fmt.Sprintf("session:%s", token)
err := rdb.Del(key).Err()
return err == nil
}
46 changes: 46 additions & 0 deletions cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright (C) 2024 Pagefault Games

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package cache

import (
"fmt"
"strconv"

"github.com/go-redis/redis"
)

var rdb *redis.Client

func InitRedis(address, password, database string) error {
db, err := strconv.Atoi(database)
if err != nil {
return fmt.Errorf("failed to convert database to int: %w", err)
}
rdb = redis.NewClient(&redis.Options{
Addr: address,
Password: password,
DB: db,
})

_, err = rdb.Ping().Result()
if err != nil {
return fmt.Errorf("failed to connect to redis: %w", err)
}

return nil
}
42 changes: 42 additions & 0 deletions cache/daily.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright (C) 2024 Pagefault Games

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package cache

import (
"fmt"
"time"
)

func TryAddDailyRun(seed string) bool {
key := fmt.Sprintf("daily:%s", time.Now().Format("2006-01-02"))
now := time.Now()
midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 1, 0, 0, now.Location())
duration := time.Until(midnight)
err := rdb.Set(key, seed, duration).Err()
return err == nil
}

func GetDailyRunSeed() (string, bool) {
key := fmt.Sprintf("daily:%s", time.Now().Format("2006-01-02"))
cachedSeed, err := rdb.Get(key).Result()
if err != nil {
return "", false
}

return cachedSeed, true
}
31 changes: 31 additions & 0 deletions cache/game.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright (C) 2024 Pagefault Games

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package cache

func FetchPlayerCount() (int, bool) {
cachedPlayerCount, err := rdb.SCard("active_players").Result()
if err != nil {
return 0, false
}

return int(cachedPlayerCount), true
}

// TODO Cache battle count

// TODO Cache classic session count
39 changes: 39 additions & 0 deletions cache/savedata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright (C) 2024 Pagefault Games

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package cache

import (
"fmt"
"time"
)

func TryAddSeedCompletion(uuid []byte, seed string, mode int) bool {
key := fmt.Sprintf("savedata:%s", uuid)
err := rdb.HMSet(key, map[string]interface{}{
"mode": mode,
"seed": seed,
"timestamp": time.Now().Unix(),
}).Err()
return err == nil
}

func ReadSeedCompletion(uuid []byte, seed string) (bool, bool) {
key := fmt.Sprintf("savedata:%s", uuid)
completed, err := rdb.HExists(key, seed).Result()
return completed, err == nil
}
42 changes: 41 additions & 1 deletion db/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"slices"

_ "github.com/go-sql-driver/mysql"
"github.com/pagefaultgames/rogueserver/cache"
"github.com/pagefaultgames/rogueserver/defs"
)

Expand All @@ -37,7 +38,18 @@ func AddAccountRecord(uuid []byte, username string, key, salt []byte) error {
}

func AddAccountSession(username string, token []byte) error {
_, err := handle.Exec("INSERT INTO sessions (uuid, token, expire) SELECT a.uuid, ?, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 1 WEEK) FROM accounts a WHERE a.username = ?", token, username)
// _, err := handle.Exec("INSERT INTO sessions (uuid, token, expire) SELECT a.uuid, ?, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 1 WEEK) FROM accounts a WHERE a.username = ?", token, username)
// if err != nil {
// return err
// }

var uuid []byte
err := handle.QueryRow("SELECT uuid FROM accounts WHERE username = ?", username).Scan(&uuid)
if err != nil {
return err
}

_, err = handle.Exec("INSERT INTO sessions (uuid, token, expire) VALUES (?, ?, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 1 WEEK))", uuid, token)
if err != nil {
return err
}
Expand All @@ -47,6 +59,8 @@ func AddAccountSession(username string, token []byte) error {
return err
}

cache.AddAccountSession(uuid, token)

return nil
}

Expand Down Expand Up @@ -145,6 +159,10 @@ func FetchGoogleIdByUUID(uuid []byte) (string, error) {
}

func FetchUsernameBySessionToken(token []byte) (string, error) {
if username, ok := cache.FetchUsernameBySessionToken(token); ok {
return username, nil
}

var username string
err := handle.QueryRow("SELECT a.username FROM accounts a JOIN sessions s ON a.uuid = s.uuid WHERE s.token = ?", token).Scan(&username)
if err != nil {
Expand All @@ -169,6 +187,8 @@ func UpdateAccountLastActivity(uuid []byte) error {
return err
}

cache.UpdateAccountLastActivity(uuid)

return nil
}

Expand Down Expand Up @@ -269,11 +289,17 @@ func FetchAccountKeySaltFromUsername(username string) ([]byte, []byte, error) {
}

func FetchTrainerIds(uuid []byte) (trainerId, secretId int, err error) {
if trainerId, secretId, ok := cache.FetchTrainerIds(uuid); ok {
return trainerId, secretId, nil
}

err = handle.QueryRow("SELECT trainerId, secretId FROM accounts WHERE uuid = ?", uuid).Scan(&trainerId, &secretId)
if err != nil {
return 0, 0, err
}

cache.UpdateTrainerIds(trainerId, secretId, uuid)

return trainerId, secretId, nil
}

Expand All @@ -283,10 +309,16 @@ func UpdateTrainerIds(trainerId, secretId int, uuid []byte) error {
return err
}

cache.UpdateTrainerIds(trainerId, secretId, uuid)

return nil
}

func IsActiveSession(uuid []byte, sessionId string) (bool, error) {
if result, ok := cache.IsActiveSession(uuid, sessionId); ok {
return result, nil
}

var id string
err := handle.QueryRow("SELECT clientSessionId FROM activeClientSessions WHERE uuid = ?", uuid).Scan(&id)
if err != nil {
Expand All @@ -311,10 +343,16 @@ func UpdateActiveSession(uuid []byte, clientSessionId string) error {
return err
}

cache.UpdateActiveSession(uuid, clientSessionId)

return nil
}

func FetchUUIDFromToken(token []byte) ([]byte, error) {
if uuid, ok := cache.FetchUUIDFromToken(token); ok {
return uuid, nil
}

var uuid []byte
err := handle.QueryRow("SELECT uuid FROM sessions WHERE token = ?", token).Scan(&uuid)
if err != nil {
Expand All @@ -330,6 +368,8 @@ func RemoveSessionFromToken(token []byte) error {
return err
}

cache.RemoveSessionFromToken(token)

return nil
}

Expand Down
Loading