diff options
| author | 2025-08-31 18:40:13 +0700 | |
|---|---|---|
| committer | 2025-08-31 18:40:13 +0700 | |
| commit | 6af14241ff2c8050753f1d6ac702a938a017a839 (patch) | |
| tree | 29400fa749d7bc87ed30cc924e486791f8f72867 | |
| parent | f174579ef53fb726594a2f7064299b86c8691ea3 (diff) | |
| download | web-6af14241ff2c8050753f1d6ac702a938a017a839.tar.gz web-6af14241ff2c8050753f1d6ac702a938a017a839.zip | |
feat: add page cache
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | go.sum | 6 | ||||
| -rw-r--r-- | internal/router/api-v1-general-page-body.go | 9 | ||||
| -rw-r--r-- | internal/router/api-v1-general-page-bottom-embeds.go | 8 | ||||
| -rw-r--r-- | internal/router/api-v1-general-page-footer.go | 9 | ||||
| -rw-r--r-- | internal/router/api-v1-general-page-header.go | 9 | ||||
| -rw-r--r-- | internal/router/api-v1-general-page-top-embeds.go | 9 | ||||
| -rw-r--r-- | internal/router/client-cache.go | 5 | ||||
| -rw-r--r-- | internal/router/page-cache.go | 5 | ||||
| -rw-r--r-- | main.go | 14 |
10 files changed, 71 insertions, 6 deletions
@@ -15,6 +15,9 @@ require ( require ( github.com/andybalholm/brotli v1.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -2,9 +2,15 @@ github.com/Backblaze/blazer v0.7.2 h1:UWNHMLB+Nf+UmbO2qkVvgriODLEMz4kIyr2Hm+DVXQ github.com/Backblaze/blazer v0.7.2/go.mod h1:T4y3EYa9IQ5J0PKc/C/J8/CEnSd3qa/lgNw938wZg10= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk= +github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= diff --git a/internal/router/api-v1-general-page-body.go b/internal/router/api-v1-general-page-body.go index 4108a4f..2329c4a 100644 --- a/internal/router/api-v1-general-page-body.go +++ b/internal/router/api-v1-general-page-body.go @@ -8,6 +8,7 @@ import ( "regexp" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/internal/b2" "github.com/SayaAndy/saya-today-web/locale" @@ -33,6 +34,13 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("body.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -133,6 +141,7 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-bottom-embeds.go b/internal/router/api-v1-general-page-bottom-embeds.go index f0957f3..df766a7 100644 --- a/internal/router/api-v1-general-page-bottom-embeds.go +++ b/internal/router/api-v1-general-page-bottom-embeds.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/internal/b2" "github.com/SayaAndy/saya-today-web/locale" @@ -30,6 +31,12 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs [] } path := urlStruct.EscapedPath() + cacheKey := fmt.Sprintf("bottom-embeds.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -59,6 +66,7 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs [] return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-footer.go b/internal/router/api-v1-general-page-footer.go index 39b6bea..98da6bb 100644 --- a/internal/router/api-v1-general-page-footer.go +++ b/internal/router/api-v1-general-page-footer.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/locale" "github.com/gofiber/fiber/v2" @@ -29,6 +30,13 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("footer.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -58,6 +66,7 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-header.go b/internal/router/api-v1-general-page-header.go index 0529ba6..1d65966 100644 --- a/internal/router/api-v1-general-page-header.go +++ b/internal/router/api-v1-general-page-header.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/internal/b2" "github.com/SayaAndy/saya-today-web/locale" @@ -30,6 +31,13 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("header.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -67,6 +75,7 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/api-v1-general-page-top-embeds.go b/internal/router/api-v1-general-page-top-embeds.go index 090107b..b26675f 100644 --- a/internal/router/api-v1-general-page-top-embeds.go +++ b/internal/router/api-v1-general-page-top-embeds.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strings" + "time" "github.com/SayaAndy/saya-today-web/locale" "github.com/gofiber/fiber/v2" @@ -29,6 +30,13 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str } path := urlStruct.EscapedPath() + + cacheKey := fmt.Sprintf("top-embeds.%s", path) + if val, ok := PCache.Get(cacheKey); val != nil && ok { + c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) + return c.Status(fiber.StatusOK).Type("html").Send(val) + } + pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") @@ -58,6 +66,7 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") } + go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute) c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) return c.Status(fiber.StatusOK).Type("html").Send(content) } diff --git a/internal/router/client-cache.go b/internal/router/client-cache.go index 81d7641..e448fb3 100644 --- a/internal/router/client-cache.go +++ b/internal/router/client-cache.go @@ -11,11 +11,6 @@ import ( "golang.org/x/crypto/argon2" ) -type PageLike struct { - PageRef string - UserId string -} - type ClientCache struct { hashMap map[string]string hashMapMutex sync.RWMutex diff --git a/internal/router/page-cache.go b/internal/router/page-cache.go new file mode 100644 index 0000000..01e4792 --- /dev/null +++ b/internal/router/page-cache.go @@ -0,0 +1,5 @@ +package router + +import "github.com/dgraph-io/ristretto/v2" + +var PCache *ristretto.Cache[string, []byte] @@ -15,6 +15,7 @@ import ( "github.com/SayaAndy/saya-today-web/internal/router" "github.com/SayaAndy/saya-today-web/internal/tailwind" "github.com/SayaAndy/saya-today-web/locale" + "github.com/dgraph-io/ristretto/v2" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/redirect" "github.com/golang-migrate/migrate/v4" @@ -116,7 +117,17 @@ func main() { router.CCache, err = router.NewClientCache(db, []byte(cfg.Auth.Salt)) if err != nil { - slog.Error("fail to initialize cache", slog.String("error", err.Error())) + slog.Error("fail to initialize client cache", slog.String("error", err.Error())) + os.Exit(1) + } + + router.PCache, err = ristretto.NewCache(&ristretto.Config[string, []byte]{ + NumCounters: 1e6, // 1,000,000 + MaxCost: 1 << 29, // 512 MB + BufferItems: 64, // number of keys per Get buffer. + }) + if err != nil { + slog.Error("fail to initialize page cache", slog.String("error", err.Error())) os.Exit(1) } @@ -158,5 +169,6 @@ func main() { if err = db.Close(); err != nil { slog.Error("fail to close db connection", slog.String("error", err.Error())) } + router.PCache.Close() db.Close() } |