Add vacuums

This commit is contained in:
2025-09-28 17:47:08 +09:00
parent 1bd7761053
commit 6a68320195
6 changed files with 133 additions and 16 deletions

View File

@@ -10,6 +10,11 @@ database:
name: "himewiki"
sslmode: "disable"
vacuum:
check-every: 250
threshold: 68719476736
image-threshold: 68719476736
site:
base: "https://wiki.example.org/"
name: "HimeWiki"

View File

@@ -80,7 +80,7 @@ func Edit(cfg *config.Config, w http.ResponseWriter, r *http.Request, params *Pa
title, normalized, _, rendered := format.Apply(cfg, params.DbName, filtered)
if previewed && save != "" {
if err := data.Save(params.DbName, normalized, revisionID); err != nil {
if err := data.Save(cfg, params.DbName, normalized, revisionID); err != nil {
http.Error(w, "Failed to save", http.StatusInternalServerError)
return
}

View File

@@ -78,7 +78,7 @@ func Upload(cfg *config.Config, w http.ResponseWriter, r *http.Request, params *
return
}
if err := data.SaveImage(name, filtered); err != nil {
if err := data.SaveImage(cfg, name, filtered); err != nil {
http.Error(w, "Failed to save", http.StatusInternalServerError)
return
}

View File

@@ -22,6 +22,12 @@ type Config struct {
SSLMode string `yaml:"sslmode"`
} `yaml:"database"`
Vacuum struct {
CheckEvery int `yaml:"check-every"`
Threshold int64 `yaml:"threshold"`
ImageThreshold int64 `yaml:"image-threshold"`
} `yaml:"vacuum"`
Site struct {
Base string `yaml:"base"`
Name string `yaml:"name"`

View File

@@ -199,7 +199,7 @@ func diff(oldText, newText string) string {
return text
}
func Save(name, content string, baseRevID int) error {
func Save(cfg *config.Config, name, content string, baseRevID int) error {
ctx := context.Background()
tx, err := db.Begin(ctx)
if err != nil {
@@ -240,16 +240,68 @@ func Save(name, content string, baseRevID int) error {
return err
}
_, err = tx.Exec(ctx,
`UPDATE state
SET page_counter = page_counter + 1
WHERE id = 1`,
)
var pageCount int64
err = tx.QueryRow(ctx, `
UPDATE state
SET page_counter = page_counter + 1
WHERE id = 1
RETURNING page_counter
`).Scan(&pageCount)
if err != nil {
return err
}
return tx.Commit(ctx)
err = tx.Commit(ctx)
if err != nil {
return err
}
if pageCount % int64(cfg.Vacuum.CheckEvery) == 0 {
var sizeBytes int64
err = db.QueryRow(ctx, `
SELECT pg_total_relation_size('pages') +
pg_total_relation_size('revisions')
`).Scan(&sizeBytes)
if err != nil {
return err
}
if sizeBytes >= cfg.Vacuum.Threshold {
_, err = db.Exec(ctx, "VACUUM FULL pages")
if err != nil {
return err
}
_, err = db.Exec(ctx, `
WITH cutoff AS (
SELECT COUNT(*) / 2 AS limit_count
FROM revisions
GROUP BY name
ORDER BY COUNT(*) DESC
LIMIT 1
)
DELETE FROM revisions r
USING cutoff
WHERE r.id IN (
SELECT id
FROM revisions r2, cutoff
WHERE r2.name = r.name
ORDER BY r2.created_at ASC
OFFSET cutoff.limit_count
)
`)
if err != nil {
return err
}
_, err = db.Exec(ctx, "VACUUM FULL revisions")
if err != nil {
return err
}
}
}
return nil
}
func LoadAll(page int, perPage int) ([]string, error) {

View File

@@ -2,6 +2,8 @@ package data
import (
"context"
"github.com/akikareha/himewiki/internal/config"
)
func LoadImage(name string) (int, []byte, error) {
@@ -17,7 +19,7 @@ func LoadImage(name string) (int, []byte, error) {
return id, content, nil
}
func SaveImage(name string, content []byte) error {
func SaveImage(cfg *config.Config, name string, content []byte) error {
ctx := context.Background()
tx, err := db.Begin(ctx)
if err != nil {
@@ -47,14 +49,66 @@ func SaveImage(name string, content []byte) error {
return err
}
_, err = tx.Exec(ctx,
`UPDATE state
SET image_counter = image_counter + 1
WHERE id = 1`,
)
var imageCount int64
err = tx.QueryRow(ctx, `
UPDATE state
SET image_counter = image_counter + 1
WHERE id = 1
RETURNING image_counter
`).Scan(&imageCount)
if err != nil {
return err
}
return tx.Commit(ctx)
err = tx.Commit(ctx)
if err != nil {
return err
}
if imageCount % int64(cfg.Vacuum.CheckEvery) == 0 {
var sizeBytes int64
err = db.QueryRow(ctx, `
SELECT pg_total_relation_size('images') +
pg_total_relation_size('image_revisions')
`).Scan(&sizeBytes)
if err != nil {
return err
}
if sizeBytes >= cfg.Vacuum.ImageThreshold {
_, err = db.Exec(ctx, "VACUUM FULL images")
if err != nil {
return err
}
_, err = db.Exec(ctx, `
WITH cutoff AS (
SELECT COUNT(*) / 2 AS limit_count
FROM image_revisions
GROUP BY name
ORDER BY COUNT(*) DESC
LIMIT 1
)
DELETE FROM image_revisions r
USING cutoff
WHERE r.id IN (
SELECT id
FROM image_revisions r2, cutoff
WHERE r2.name = r.name
ORDER BY r2.created_at ASC
OFFSET cutoff.limit_count
)
`)
if err != nil {
return err
}
_, err = db.Exec(ctx, "VACUUM FULL image_revisions")
if err != nil {
return err
}
}
}
return nil
}