Skip to content

Commit

Permalink
Cache SolvedStatus queries for pairs of users and problems.
Browse files Browse the repository at this point in the history
  • Loading branch information
mraron committed Jan 22, 2024
1 parent 888a0ae commit 0e4cbf4
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 42 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/erni27/imcache v1.2.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gorilla/context v1.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ericlagergren/decimal v0.0.0-20190420051523-6335edbaa640/go.mod h1:mdYyfAkzn9kyJ/kMk/7WE9ufl9lflh+2NvecQ5mAghs=
github.com/erni27/imcache v1.2.0 h1:EbHHhwzJPcAYK//cVDq0use9tep1z9/kenIQhMarGnk=
github.com/erni27/imcache v1.2.0/go.mod h1:KNUCBr1U9nOFTyUEC9CsMKZE33NboYTPavsQsuEufoA=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
Expand Down
2 changes: 1 addition & 1 deletion internal/glue/glue.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (s *Server) ConnectToDB() {
}

s.Submissions = db.NewSubmissions(s.DB)
s.Problems = db.NewProblems(s.DB)
s.Problems = db.NewProblems(s.DB, db.NewSolvedStatusQuery(s.DB))
s.SubmissionsQuery = s.Submissions.(*db.Submissions)
}

Expand Down
41 changes: 41 additions & 0 deletions internal/njudge/cached/problems.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cached

import (
"github.com/erni27/imcache"
"github.com/mraron/njudge/internal/njudge"
"golang.org/x/net/context"
"time"
)

type problemAndUser struct {
ProblemID, UserID int
}

type SolvedStatusQuery struct {
solvedStatusQuery njudge.SolvedStatusQuery
cache *imcache.Cache[problemAndUser, njudge.SolvedStatus]
}

func (ssq *SolvedStatusQuery) GetSolvedStatus(ctx context.Context, problemID, userID int) (njudge.SolvedStatus, error) {
key := problemAndUser{problemID, userID}
if val, ok := ssq.cache.Get(key); ok {
return val, nil
}

val, err := ssq.solvedStatusQuery.GetSolvedStatus(ctx, problemID, userID)
if err != nil {
return 0, nil
}

ssq.cache.Set(key, val, imcache.WithDefaultExpiration())
return val, nil
}

func NewSolvedStatusQuery(solvedStatusQuery njudge.SolvedStatusQuery, ttl time.Duration) *SolvedStatusQuery {
return &SolvedStatusQuery{
cache: imcache.New[problemAndUser, njudge.SolvedStatus](
imcache.WithDefaultExpirationOption[problemAndUser, njudge.SolvedStatus](ttl),
),
solvedStatusQuery: solvedStatusQuery,
}
}
82 changes: 46 additions & 36 deletions internal/njudge/db/problems.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ import (
"context"
"database/sql"
"errors"
"github.com/mraron/njudge/pkg/problems"

"github.com/mraron/njudge/internal/njudge"
"github.com/mraron/njudge/internal/njudge/db/models"
"github.com/mraron/njudge/pkg/problems"
"github.com/volatiletech/null/v8"
"github.com/volatiletech/sqlboiler/v4/boil"
"github.com/volatiletech/sqlboiler/v4/queries/qm"
"go.uber.org/multierr"
)

type Problems struct {
db *sql.DB
db *sql.DB
solvedStatusQuery njudge.SolvedStatusQuery
}

func NewProblems(db *sql.DB) *Problems {
func NewProblems(db *sql.DB, solvedStatusQuery njudge.SolvedStatusQuery) *Problems {
return &Problems{
db: db,
db: db,
solvedStatusQuery: solvedStatusQuery,
}
}

Expand Down Expand Up @@ -222,37 +224,6 @@ func (ps *Problems) GetProblemsWithCategory(ctx context.Context, f njudge.Catego
}
}

func (ps *Problems) hasUserSolved(ctx context.Context, userID, problemID int) (njudge.SolvedStatus, error) {
solvedStatus := njudge.Unattempted

cnt, err := models.Submissions(
models.SubmissionWhere.ProblemID.EQ(problemID),
models.SubmissionWhere.Verdict.EQ(int(problems.VerdictAC)),
models.SubmissionWhere.UserID.EQ(userID),
).Count(ctx, ps.db)

if err != nil && !errors.Is(err, sql.ErrNoRows) {
return njudge.Unknown, err
} else {
if cnt > 0 {
solvedStatus = njudge.Solved
} else {
cnt, err := models.Submissions(
models.SubmissionWhere.ProblemID.EQ(problemID),
models.SubmissionWhere.UserID.EQ(userID),
).Count(ctx, ps.db)

if err != nil && !errors.Is(err, sql.ErrNoRows) {
return njudge.Unknown, err
} else if cnt > 0 {
solvedStatus = njudge.Attempted
}
}
}

return solvedStatus, nil
}

func (ps *Problems) getUserLastLanguage(ctx context.Context, userID int) (string, error) {
if userID > 0 {
sub, err := models.Submissions(
Expand Down Expand Up @@ -282,7 +253,7 @@ func (ps *Problems) GetProblemData(ctx context.Context, problemID int, userID in
if userID > 0 {
res.UserInfo = &njudge.ProblemUserInfo{}

if res.UserInfo.SolvedStatus, err = ps.hasUserSolved(ctx, userID, problemID); err != nil {
if res.UserInfo.SolvedStatus, err = ps.solvedStatusQuery.GetSolvedStatus(ctx, problemID, userID); err != nil {
return nil, err
}

Expand All @@ -302,3 +273,42 @@ func (ps *Problems) GetProblemData(ctx context.Context, problemID int, userID in

return &res, nil
}

type SolvedStatusQuery struct {
db *sql.DB
}

func NewSolvedStatusQuery(db *sql.DB) *SolvedStatusQuery {
return &SolvedStatusQuery{db: db}
}

func (ss *SolvedStatusQuery) GetSolvedStatus(ctx context.Context, problemID, userID int) (njudge.SolvedStatus, error) {
solvedStatus := njudge.Unattempted

cnt, err := models.Submissions(
models.SubmissionWhere.ProblemID.EQ(problemID),
models.SubmissionWhere.Verdict.EQ(int(problems.VerdictAC)),
models.SubmissionWhere.UserID.EQ(userID),
).Count(ctx, ss.db)

if err != nil && !errors.Is(err, sql.ErrNoRows) {
return njudge.Unknown, err
} else {
if cnt > 0 {
solvedStatus = njudge.Solved
} else {
cnt, err := models.Submissions(
models.SubmissionWhere.ProblemID.EQ(problemID),
models.SubmissionWhere.UserID.EQ(userID),
).Count(ctx, ss.db)

if err != nil && !errors.Is(err, sql.ErrNoRows) {
return njudge.Unknown, err
} else if cnt > 0 {
solvedStatus = njudge.Attempted
}
}
}

return solvedStatus, nil
}
4 changes: 4 additions & 0 deletions internal/njudge/problem.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ type ProblemInfoQuery interface {
GetProblemData(ctx context.Context, problemID, userID int) (*ProblemInfo, error)
}

type SolvedStatusQuery interface {
GetSolvedStatus(ctx context.Context, problemID, userID int) (SolvedStatus, error)
}

type ProblemQuery interface {
GetProblem(ctx context.Context, problemset, problem string) (*Problem, error)
GetProblemsWithCategory(ctx context.Context, f CategoryFilter) ([]Problem, error)
Expand Down
7 changes: 6 additions & 1 deletion internal/web/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package web
import (
"context"
"fmt"
"github.com/mraron/njudge/internal/njudge/cached"
"log"
"net/http"
"strconv"
Expand Down Expand Up @@ -85,7 +86,11 @@ func (s *Server) SetupDataAccess() {

s.Categories = db.NewCategories(s.DB.DB)
s.Tags = db.NewTags(s.DB.DB)
s.Problems = db.NewProblems(s.DB.DB)
s.SolvedStatusQuery = cached.NewSolvedStatusQuery(db.NewSolvedStatusQuery(s.DB.DB), 30*time.Second)
s.Problems = db.NewProblems(
s.DB.DB,
s.SolvedStatusQuery,
)
s.Submissions = db.NewSubmissions(s.DB.DB)
s.Users = db.NewUsers(s.DB.DB)

Expand Down
6 changes: 3 additions & 3 deletions internal/web/handlers/taskarchive/taskarchive.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type TreeNode struct {
Visible bool
}

func Get(cats njudge.Categories, problemQuery njudge.ProblemQuery, problemInfoQuery njudge.ProblemInfoQuery, problemStore problems.Store) echo.HandlerFunc {
func Get(cats njudge.Categories, problemQuery njudge.ProblemQuery, solvedStatusQuery njudge.SolvedStatusQuery, problemStore problems.Store) echo.HandlerFunc {
return func(c echo.Context) error {
tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator)

Expand Down Expand Up @@ -58,11 +58,11 @@ func Get(cats njudge.Categories, problemQuery njudge.ProblemQuery, problemInfoQu
}

if u != nil {
pinfo, err := problemInfoQuery.GetProblemData(c.Request().Context(), p.ID, u.ID)
solvedStatus, err := solvedStatusQuery.GetSolvedStatus(c.Request().Context(), p.ID, u.ID)
if err != nil {
return err
}
elem.SolvedStatus = pinfo.UserInfo.SolvedStatus
elem.SolvedStatus = solvedStatus
}

tree.Children = append(tree.Children, elem)
Expand Down
2 changes: 1 addition & 1 deletion internal/web/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (s *Server) prepareRoutes(e *echo.Echo) {

e.GET("/submission/:id", handlers.GetSubmission(s.Submissions)).Name = "getSubmission"
e.GET("/submission/rejudge/:id", handlers.RejudgeSubmission(s.Submissions), user.RequireLoginMiddleware()).Name = "rejudgeSubmission"
e.GET("/task_archive", taskarchive.Get(s.Categories, s.ProblemQuery, s.ProblemInfoQuery, s.ProblemStore))
e.GET("/task_archive", taskarchive.Get(s.Categories, s.ProblemQuery, s.SolvedStatusQuery, s.ProblemStore))

ps := e.Group("/problemset", problemset.SetNameMiddleware())
ps.GET("/:name/", problemset.GetProblemList(s.ProblemStore, s.Problems, s.Categories, s.ProblemListQuery, s.ProblemInfoQuery))
Expand Down
1 change: 1 addition & 0 deletions internal/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Server struct {
Problems njudge.Problems
Users njudge.Users
Submissions njudge.Submissions
SolvedStatusQuery njudge.SolvedStatusQuery
ProblemInfoQuery njudge.ProblemInfoQuery
ProblemQuery njudge.ProblemQuery
ProblemListQuery njudge.ProblemListQuery
Expand Down

0 comments on commit 0e4cbf4

Please sign in to comment.