Optimize sorting gallery by Likes/Comments via caching
This commit is contained in:
parent
0cd72a96ed
commit
955ace1e91
|
@ -261,6 +261,21 @@ func main() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "photo-counts",
|
||||||
|
Usage: "repopulate cached Likes and Comment counts on photos",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
initdb(c)
|
||||||
|
|
||||||
|
log.Info("Running BackfillPhotoCounts()")
|
||||||
|
err := backfill.BackfillPhotoCounts()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -204,6 +204,13 @@ func Likes() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh cached like counts.
|
||||||
|
if req.TableName == "photos" {
|
||||||
|
if err := models.UpdatePhotoCachedCounts(tableID); err != nil {
|
||||||
|
log.Error("UpdatePhotoCachedCount(%d): %s", tableID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send success response.
|
// Send success response.
|
||||||
SendJSON(w, http.StatusOK, Response{
|
SendJSON(w, http.StatusOK, Response{
|
||||||
OK: true,
|
OK: true,
|
||||||
|
|
|
@ -121,6 +121,14 @@ func PostComment() http.HandlerFunc {
|
||||||
// Log the change.
|
// Log the change.
|
||||||
models.LogDeleted(&models.User{ID: comment.UserID}, currentUser, "comments", comment.ID, "Deleted a comment.", comment)
|
models.LogDeleted(&models.User{ID: comment.UserID}, currentUser, "comments", comment.ID, "Deleted a comment.", comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh cached like counts.
|
||||||
|
if tableName == "photos" {
|
||||||
|
if err := models.UpdatePhotoCachedCounts(tableID); err != nil {
|
||||||
|
log.Error("UpdatePhotoCachedCount(%d): %s", tableID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
templates.Redirect(w, fromURL)
|
templates.Redirect(w, fromURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -174,6 +182,13 @@ func PostComment() http.HandlerFunc {
|
||||||
session.Flash(w, r, "Comment added!")
|
session.Flash(w, r, "Comment added!")
|
||||||
templates.Redirect(w, fromURL)
|
templates.Redirect(w, fromURL)
|
||||||
|
|
||||||
|
// Refresh cached comment counts.
|
||||||
|
if tableName == "photos" {
|
||||||
|
if err := models.UpdatePhotoCachedCounts(tableID); err != nil {
|
||||||
|
log.Error("UpdatePhotoCachedCount(%d): %s", tableID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Log the change.
|
// Log the change.
|
||||||
models.LogCreated(currentUser, "comments", comment.ID, "Posted a new comment.\n\n---\n\n"+message)
|
models.LogCreated(currentUser, "comments", comment.ID, "Posted a new comment.\n\n---\n\n"+message)
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,8 @@ func SiteGallery() http.HandlerFunc {
|
||||||
var sortWhitelist = []string{
|
var sortWhitelist = []string{
|
||||||
"created_at desc",
|
"created_at desc",
|
||||||
"created_at asc",
|
"created_at asc",
|
||||||
|
"like_count desc",
|
||||||
// Custom (advanced) sort options.
|
"comment_count desc",
|
||||||
"by_likes",
|
|
||||||
"by_comments",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -19,10 +19,8 @@ func UserPhotos() http.HandlerFunc {
|
||||||
"pinned desc nulls last, updated_at desc",
|
"pinned desc nulls last, updated_at desc",
|
||||||
"created_at desc",
|
"created_at desc",
|
||||||
"created_at asc",
|
"created_at asc",
|
||||||
|
"like_count desc",
|
||||||
// Custom (advanced) sort options.
|
"comment_count desc",
|
||||||
"by_likes",
|
|
||||||
"by_comments",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
25
pkg/models/backfill/backfill_photo_counts.go
Normal file
25
pkg/models/backfill/backfill_photo_counts.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package backfill
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackfillPhotoCounts recomputes the cached Likes and Comment counts on photos.
|
||||||
|
func BackfillPhotoCounts() error {
|
||||||
|
res := models.DB.Exec(`
|
||||||
|
UPDATE photos
|
||||||
|
SET like_count = (
|
||||||
|
SELECT count(id)
|
||||||
|
FROM likes
|
||||||
|
WHERE table_name='photos'
|
||||||
|
AND table_id=photos.id
|
||||||
|
),
|
||||||
|
comment_count = (
|
||||||
|
SELECT count(id)
|
||||||
|
FROM comments
|
||||||
|
WHERE table_name='photos'
|
||||||
|
AND table_id=photos.id
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
return res.Error
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ type Photo struct {
|
||||||
Gallery bool `gorm:"index"` // photo appears in the public gallery (if public)
|
Gallery bool `gorm:"index"` // photo appears in the public gallery (if public)
|
||||||
Explicit bool `gorm:"index"` // is an explicit photo
|
Explicit bool `gorm:"index"` // is an explicit photo
|
||||||
Pinned bool `gorm:"index"` // user pins it to the front of their gallery
|
Pinned bool `gorm:"index"` // user pins it to the front of their gallery
|
||||||
|
LikeCount int64 `gorm:"index"` // cache of 'likes' count
|
||||||
|
CommentCount int64 `gorm:"index"` // cache of comments count
|
||||||
CreatedAt time.Time `gorm:"index"`
|
CreatedAt time.Time `gorm:"index"`
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
@ -135,24 +137,6 @@ func PaginateUserPhotos(userID uint64, conf UserGallery, pager *Pagination) ([]*
|
||||||
placeholders = append(placeholders, explicit[0])
|
placeholders = append(placeholders, explicit[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom SORT parameters.
|
|
||||||
switch pager.Sort {
|
|
||||||
case "by_likes":
|
|
||||||
pager.Sort = `(
|
|
||||||
SELECT count(likes.id)
|
|
||||||
FROM likes
|
|
||||||
WHERE likes.table_name = 'photos'
|
|
||||||
AND likes.table_id = photos.id
|
|
||||||
) DESC`
|
|
||||||
case "by_comments":
|
|
||||||
pager.Sort = `(
|
|
||||||
SELECT count(comments.id)
|
|
||||||
FROM comments
|
|
||||||
WHERE comments.table_name = 'photos'
|
|
||||||
AND comments.table_id = photos.id
|
|
||||||
) DESC NULLS LAST`
|
|
||||||
}
|
|
||||||
|
|
||||||
query := DB.Where(
|
query := DB.Where(
|
||||||
strings.Join(wheres, " AND "),
|
strings.Join(wheres, " AND "),
|
||||||
placeholders...,
|
placeholders...,
|
||||||
|
@ -690,38 +674,33 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get count pre-sorting.
|
|
||||||
query.Model(&Photo{}).Count(&pager.Total)
|
|
||||||
|
|
||||||
// Custom SORT parameters.
|
|
||||||
switch pager.Sort {
|
|
||||||
case "by_likes":
|
|
||||||
query = query.Select(`
|
|
||||||
photos.*,
|
|
||||||
COUNT(likes.id) AS like_count
|
|
||||||
`).Joins(
|
|
||||||
"LEFT OUTER JOIN likes ON (likes.table_name='photos' AND likes.table_id=photos.id)",
|
|
||||||
).Where(
|
|
||||||
"likes.table_name = 'photos' AND likes.table_id = photos.id",
|
|
||||||
).Group("photos.id")
|
|
||||||
pager.Sort = `like_count DESC`
|
|
||||||
case "by_comments":
|
|
||||||
query = query.Select(`
|
|
||||||
photos.*,
|
|
||||||
COUNT(comments.id) AS comment_count
|
|
||||||
`).Joins(
|
|
||||||
"LEFT OUTER JOIN comments ON (comments.table_name='photos' AND comments.table_id=photos.id)",
|
|
||||||
).Where(
|
|
||||||
"comments.table_name = 'photos' AND comments.table_id = photos.id",
|
|
||||||
).Group("photos.id")
|
|
||||||
pager.Sort = `comment_count DESC`
|
|
||||||
}
|
|
||||||
|
|
||||||
query = query.Order(pager.Sort)
|
query = query.Order(pager.Sort)
|
||||||
|
query.Model(&Photo{}).Count(&pager.Total)
|
||||||
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&p)
|
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&p)
|
||||||
return p, result.Error
|
return p, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePhotoCachedCounts will refresh the cached like/comment count on the photos table.
|
||||||
|
func UpdatePhotoCachedCounts(photoID uint64) error {
|
||||||
|
res := DB.Exec(`
|
||||||
|
UPDATE photos
|
||||||
|
SET like_count = (
|
||||||
|
SELECT count(id)
|
||||||
|
FROM likes
|
||||||
|
WHERE table_name='photos'
|
||||||
|
AND table_id=photos.id
|
||||||
|
),
|
||||||
|
comment_count = (
|
||||||
|
SELECT count(id)
|
||||||
|
FROM comments
|
||||||
|
WHERE table_name='photos'
|
||||||
|
AND table_id=photos.id
|
||||||
|
)
|
||||||
|
WHERE photos.id = ?;
|
||||||
|
`, photoID)
|
||||||
|
return res.Error
|
||||||
|
}
|
||||||
|
|
||||||
// Save photo.
|
// Save photo.
|
||||||
func (p *Photo) Save() error {
|
func (p *Photo) Save() error {
|
||||||
result := DB.Save(p)
|
result := DB.Save(p)
|
||||||
|
|
|
@ -414,8 +414,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<option value="created_at desc"{{if eq .Sort "created_at desc"}} selected{{end}}>Most recent</option>
|
<option value="created_at desc"{{if eq .Sort "created_at desc"}} selected{{end}}>Most recent</option>
|
||||||
<option value="created_at asc"{{if eq .Sort "created_at asc"}} selected{{end}}>Oldest first</option>
|
<option value="created_at asc"{{if eq .Sort "created_at asc"}} selected{{end}}>Oldest first</option>
|
||||||
<option value="by_likes"{{if eq .Sort "by_likes"}} selected{{end}}>Most likes</option>
|
<option value="like_count desc"{{if eq .Sort "like_count desc"}} selected{{end}}>Most likes</option>
|
||||||
<option value="by_comments"{{if eq .Sort "by_comments"}} selected{{end}}>Most comments</option>
|
<option value="comment_count desc"{{if eq .Sort "comment_count desc"}} selected{{end}}>Most comments</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user