mirror of https://github.com/jlelse/GoBlog
Cache blog stats for better performance
This commit is contained in:
parent
59e491ee0a
commit
4653e4480c
82
blogstats.go
82
blogstats.go
|
@ -3,8 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
servertiming "github.com/mitchellh/go-server-timing"
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
func serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -19,10 +20,33 @@ func serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var blogStatsCacheGroup singleflight.Group
|
||||||
|
var blogStatsCache = map[string]map[string]interface{}{}
|
||||||
|
var blogStatsCacheMutex sync.RWMutex
|
||||||
|
|
||||||
func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
// Start timing
|
data, err, _ := blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
|
||||||
t := servertiming.FromContext(r.Context()).NewMetric("sq").Start()
|
return getBlogStats(blog)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Render
|
||||||
|
render(w, r, templateBlogStatsTable, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlogStats(blog string) (data map[string]interface{}, err error) {
|
||||||
|
blogStatsCacheMutex.RLock()
|
||||||
|
if data, ok := blogStatsCache[blog]; ok && data != nil {
|
||||||
|
blogStatsCacheMutex.RUnlock()
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
blogStatsCacheMutex.RUnlock()
|
||||||
// Build query
|
// Build query
|
||||||
prq := &postsRequestConfig{
|
prq := &postsRequestConfig{
|
||||||
blog: blog,
|
blog: blog,
|
||||||
|
@ -40,19 +64,16 @@ func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
||||||
// Count total posts
|
// Count total posts
|
||||||
row, err := appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+"))", params...)
|
row, err := appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+"))", params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
total := statsTableType{}
|
total := statsTableType{}
|
||||||
if err = row.Scan(&total.Posts, &total.Chars, &total.Words, &total.WordsPerPost); err != nil {
|
if err = row.Scan(&total.Posts, &total.Chars, &total.Words, &total.WordsPerPost); err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// Count posts per year
|
// Count posts per year
|
||||||
rows, err := appDbQuery("select *, "+wordsPerPost+" from (select year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...)
|
rows, err := appDbQuery("select *, "+wordsPerPost+" from (select year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
var years []statsTableType
|
var years []statsTableType
|
||||||
year := statsTableType{}
|
year := statsTableType{}
|
||||||
|
@ -60,20 +81,17 @@ func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
||||||
if err = rows.Scan(&year.Name, &year.Posts, &year.Chars, &year.Words, &year.WordsPerPost); err == nil {
|
if err = rows.Scan(&year.Name, &year.Posts, &year.Chars, &year.Words, &year.WordsPerPost); err == nil {
|
||||||
years = append(years, year)
|
years = append(years, year)
|
||||||
} else {
|
} else {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Count posts without date
|
// Count posts without date
|
||||||
row, err = appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published = '')", params...)
|
row, err = appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published = '')", params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
noDate := statsTableType{}
|
noDate := statsTableType{}
|
||||||
if err = row.Scan(&noDate.Posts, &noDate.Chars, &noDate.Words, &noDate.WordsPerPost); err != nil {
|
if err = row.Scan(&noDate.Posts, &noDate.Chars, &noDate.Words, &noDate.WordsPerPost); err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// Count posts per month per year
|
// Count posts per month per year
|
||||||
months := map[string][]statsTableType{}
|
months := map[string][]statsTableType{}
|
||||||
|
@ -81,28 +99,30 @@ func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, year := range years {
|
for _, year := range years {
|
||||||
rows, err = appDbQuery("select *, "+wordsPerPost+" from (select month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and year = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...)
|
rows, err = appDbQuery("select *, "+wordsPerPost+" from (select month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and year = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err = rows.Scan(&month.Name, &month.Posts, &month.Chars, &month.Words, &month.WordsPerPost); err == nil {
|
if err = rows.Scan(&month.Name, &month.Posts, &month.Chars, &month.Words, &month.WordsPerPost); err == nil {
|
||||||
months[year.Name] = append(months[year.Name], month)
|
months[year.Name] = append(months[year.Name], month)
|
||||||
} else {
|
} else {
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Stop timing
|
blogStatsCacheMutex.Lock()
|
||||||
t.Stop()
|
blogStatsCache[blog] = map[string]interface{}{
|
||||||
// Render
|
"total": total,
|
||||||
render(w, r, templateBlogStatsTable, &renderData{
|
"years": years,
|
||||||
BlogString: blog,
|
"withoutdate": noDate,
|
||||||
Data: map[string]interface{}{
|
"months": months,
|
||||||
"total": total,
|
}
|
||||||
"years": years,
|
data = blogStatsCache[blog]
|
||||||
"withoutdate": noDate,
|
blogStatsCacheMutex.Unlock()
|
||||||
"months": months,
|
return data, nil
|
||||||
},
|
}
|
||||||
})
|
|
||||||
|
func clearBlogStatsCache() {
|
||||||
|
blogStatsCacheMutex.Lock()
|
||||||
|
blogStatsCache = map[string]map[string]interface{}{}
|
||||||
|
blogStatsCacheMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
2
cache.go
2
cache.go
|
@ -210,6 +210,8 @@ func getCache(key string, next http.Handler, r *http.Request) (item *cacheItem)
|
||||||
|
|
||||||
func purgeCache() {
|
func purgeCache() {
|
||||||
cacheR.Clear()
|
cacheR.Clear()
|
||||||
|
// Clear blog stats as well
|
||||||
|
clearBlogStatsCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setInternalCacheExpirationHeader(w http.ResponseWriter, r *http.Request, expiration int) {
|
func setInternalCacheExpirationHeader(w http.ResponseWriter, r *http.Request, expiration int) {
|
||||||
|
|
Loading…
Reference in New Issue