chore: store vacuum and clean (#2293)

* Move all vacuum code into driver

* Remove db from Store
This commit is contained in:
Athurg Gooth
2023-09-27 09:27:31 +08:00
committed by GitHub
parent 9abf294eed
commit ca98367a0a
23 changed files with 239 additions and 238 deletions

View File

@ -56,8 +56,7 @@ var (
} }
driver := sqlite.NewDriver(db.DBInstance) driver := sqlite.NewDriver(db.DBInstance)
store := store.New(driver, profile)
store := store.New(db.DBInstance, driver, profile)
s, err := server.NewServer(ctx, profile, store) s, err := server.NewServer(ctx, profile, store)
if err != nil { if err != nil {
cancel() cancel()

View File

@ -51,8 +51,7 @@ var (
} }
driver := sqlite.NewDriver(db.DBInstance) driver := sqlite.NewDriver(db.DBInstance)
s := store.New(driver, profile)
s := store.New(db.DBInstance, driver, profile)
resources, err := s.ListResources(ctx, &store.FindResource{}) resources, err := s.ListResources(ctx, &store.FindResource{})
if err != nil { if err != nil {
fmt.Printf("failed to list resources, error: %+v\n", err) fmt.Printf("failed to list resources, error: %+v\n", err)

View File

@ -48,8 +48,7 @@ var (
} }
driver := sqlite.NewDriver(db.DBInstance) driver := sqlite.NewDriver(db.DBInstance)
store := store.New(driver, profile)
store := store.New(db.DBInstance, driver, profile)
if err := ExecuteSetup(ctx, store, hostUsername, hostPassword); err != nil { if err := ExecuteSetup(ctx, store, hostUsername, hostPassword); err != nil {
fmt.Printf("failed to setup, error: %+v\n", err) fmt.Printf("failed to setup, error: %+v\n", err)
return return

View File

@ -162,7 +162,7 @@ func (s *Server) Shutdown(ctx context.Context) {
} }
// Close database connection // Close database connection
if err := s.Store.GetDB().Close(); err != nil { if err := s.Store.Close(); err != nil {
fmt.Printf("failed to close database, error: %v\n", err) fmt.Printf("failed to close database, error: %v\n", err)
} }

View File

@ -7,6 +7,10 @@ import (
) )
type Driver interface { type Driver interface {
Vacuum(ctx context.Context) error
BackupTo(ctx context.Context, filename string) error
Close() error
CreateActivity(ctx context.Context, create *Activity) (*Activity, error) CreateActivity(ctx context.Context, create *Activity) (*Activity, error)
CreateResource(ctx context.Context, create *Resource) (*Resource, error) CreateResource(ctx context.Context, create *Resource) (*Resource, error)

View File

@ -2,7 +2,6 @@ package store
import ( import (
"context" "context"
"database/sql"
) )
// Visibility is the type of a visibility. // Visibility is the type of a visibility.
@ -107,35 +106,9 @@ func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemo) error {
} }
func (s *Store) DeleteMemo(ctx context.Context, delete *DeleteMemo) error { func (s *Store) DeleteMemo(ctx context.Context, delete *DeleteMemo) error {
if err := s.driver.DeleteMemo(ctx, delete); err != nil { return s.driver.DeleteMemo(ctx, delete)
return err
}
if err := s.Vacuum(ctx); err != nil {
// Prevent linter warning.
return err
}
return nil
} }
func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int32) ([]Visibility, error) { func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int32) ([]Visibility, error) {
return s.driver.FindMemosVisibilityList(ctx, memoIDs) return s.driver.FindMemosVisibilityList(ctx, memoIDs)
} }
func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
memo
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -2,7 +2,6 @@ package store
import ( import (
"context" "context"
"database/sql"
) )
type MemoOrganizer struct { type MemoOrganizer struct {
@ -32,28 +31,3 @@ func (s *Store) GetMemoOrganizer(ctx context.Context, find *FindMemoOrganizer) (
func (s *Store) DeleteMemoOrganizer(ctx context.Context, delete *DeleteMemoOrganizer) error { func (s *Store) DeleteMemoOrganizer(ctx context.Context, delete *DeleteMemoOrganizer) error {
return s.driver.DeleteMemoOrganizer(ctx, delete) return s.driver.DeleteMemoOrganizer(ctx, delete)
} }
func vacuumMemoOrganizer(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
memo_organizer
WHERE
memo_id NOT IN (
SELECT
id
FROM
memo
)
OR user_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -2,7 +2,6 @@ package store
import ( import (
"context" "context"
"database/sql"
) )
type MemoRelationType string type MemoRelationType string
@ -54,13 +53,3 @@ func (s *Store) GetMemoRelation(ctx context.Context, find *FindMemoRelation) (*M
func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelation) error { func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelation) error {
return s.driver.DeleteMemoRelation(ctx, delete) return s.driver.DeleteMemoRelation(ctx, delete)
} }
func vacuumMemoRelations(ctx context.Context, tx *sql.Tx) error {
if _, err := tx.ExecContext(ctx, `
DELETE FROM memo_relation
WHERE memo_id NOT IN (SELECT id FROM memo) OR related_memo_id NOT IN (SELECT id FROM memo)
`); err != nil {
return err
}
return nil
}

View File

@ -2,7 +2,6 @@ package store
import ( import (
"context" "context"
"database/sql"
) )
type Resource struct { type Resource struct {
@ -75,33 +74,5 @@ func (s *Store) UpdateResource(ctx context.Context, update *UpdateResource) (*Re
} }
func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) error { func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) error {
err := s.driver.DeleteResource(ctx, delete) return s.driver.DeleteResource(ctx, delete)
if err != nil {
return err
}
if err := s.Vacuum(ctx); err != nil {
// Prevent linter warning.
return err
}
return nil
}
func vacuumResource(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
resource
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
} }

View File

@ -234,6 +234,10 @@ func (d *Driver) DeleteMemo(ctx context.Context, delete *store.DeleteMemo) error
return err return err
} }
if err := d.Vacuum(ctx); err != nil {
// Prevent linter warning.
return err
}
return nil return nil
} }
@ -268,3 +272,22 @@ func (d *Driver) FindMemosVisibilityList(ctx context.Context, memoIDs []int32) (
return visibilityList, nil return visibilityList, nil
} }
func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
memo
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"strings" "strings"
@ -80,3 +81,28 @@ func (d *Driver) DeleteMemoOrganizer(ctx context.Context, delete *store.DeleteMe
} }
return nil return nil
} }
func vacuumMemoOrganizer(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
memo_organizer
WHERE
memo_id NOT IN (
SELECT
id
FROM
memo
)
OR user_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"database/sql"
"strings" "strings"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
@ -104,3 +105,13 @@ func (d *Driver) DeleteMemoRelation(ctx context.Context, delete *store.DeleteMem
} }
return nil return nil
} }
func vacuumMemoRelations(ctx context.Context, tx *sql.Tx) error {
if _, err := tx.ExecContext(ctx, `
DELETE FROM memo_relation
WHERE memo_id NOT IN (SELECT id FROM memo) OR related_memo_id NOT IN (SELECT id FROM memo)
`); err != nil {
return err
}
return nil
}

View File

@ -181,5 +181,29 @@ func (d *Driver) DeleteResource(ctx context.Context, delete *store.DeleteResourc
return err return err
} }
if err := d.Vacuum(ctx); err != nil {
// Prevent linter warning.
return err
}
return nil
}
func vacuumResource(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
resource
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil return nil
} }

View File

@ -1,8 +1,12 @@
package sqlite package sqlite
import ( import (
"context"
"database/sql" "database/sql"
"github.com/pkg/errors"
"modernc.org/sqlite"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
@ -15,3 +19,91 @@ func NewDriver(db *sql.DB) store.Driver {
db: db, db: db,
} }
} }
func (d *Driver) Vacuum(ctx context.Context) error {
tx, err := d.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
if err := vacuumImpl(ctx, tx); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
// Vacuum sqlite database file size after deleting resource.
if _, err := d.db.Exec("VACUUM"); err != nil {
return err
}
return nil
}
func vacuumImpl(ctx context.Context, tx *sql.Tx) error {
if err := vacuumMemo(ctx, tx); err != nil {
return err
}
if err := vacuumResource(ctx, tx); err != nil {
return err
}
if err := vacuumUserSetting(ctx, tx); err != nil {
return err
}
if err := vacuumMemoOrganizer(ctx, tx); err != nil {
return err
}
if err := vacuumMemoRelations(ctx, tx); err != nil {
return err
}
if err := vacuumTag(ctx, tx); err != nil {
// Prevent revive warning.
return err
}
return nil
}
func (d *Driver) BackupTo(ctx context.Context, filename string) error {
conn, err := d.db.Conn(ctx)
if err != nil {
return errors.Errorf("fail to get conn %s", err)
}
defer conn.Close()
err = conn.Raw(func(driverConn any) error {
type backuper interface {
NewBackup(string) (*sqlite.Backup, error)
}
backupConn, ok := driverConn.(backuper)
if !ok {
return errors.Errorf("db connection is not a sqlite backuper")
}
bck, err := backupConn.NewBackup(filename)
if err != nil {
return errors.Errorf("fail to create sqlite backup %s", err)
}
for more := true; more; {
more, err = bck.Step(-1)
if err != nil {
return errors.Errorf("fail to execute sqlite backup %s", err)
}
}
return bck.Finish()
})
if err != nil {
return errors.Errorf("fail to backup %s", err)
}
return nil
}
func (d *Driver) Close() error {
return d.db.Close()
}

View File

@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"database/sql"
"strings" "strings"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
@ -73,3 +74,22 @@ func (d *Driver) DeleteTag(ctx context.Context, delete *store.DeleteTag) error {
} }
return nil return nil
} }
func vacuumTag(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
tag
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -168,5 +168,11 @@ func (d *Driver) DeleteUser(ctx context.Context, delete *store.DeleteUser) error
if _, err := result.RowsAffected(); err != nil { if _, err := result.RowsAffected(); err != nil {
return err return err
} }
if err := d.Vacuum(ctx); err != nil {
// Prevent linter warning.
return err
}
return nil return nil
} }

View File

@ -2,6 +2,7 @@ package sqlite
import ( import (
"context" "context"
"database/sql"
"errors" "errors"
"strings" "strings"
@ -153,3 +154,22 @@ func (d *Driver) ListUserSettingsV1(ctx context.Context, find *store.FindUserSet
return userSettingList, nil return userSettingList, nil
} }
func vacuumUserSetting(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
user_setting
WHERE
user_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -2,20 +2,14 @@ package store
import ( import (
"context" "context"
"database/sql"
"sync" "sync"
"modernc.org/sqlite"
"github.com/pkg/errors"
"github.com/usememos/memos/server/profile" "github.com/usememos/memos/server/profile"
) )
// Store provides database access to all raw objects. // Store provides database access to all raw objects.
type Store struct { type Store struct {
Profile *profile.Profile Profile *profile.Profile
db *sql.DB
driver Driver driver Driver
systemSettingCache sync.Map // map[string]*SystemSetting systemSettingCache sync.Map // map[string]*SystemSetting
userCache sync.Map // map[int]*User userCache sync.Map // map[int]*User
@ -24,98 +18,21 @@ type Store struct {
} }
// New creates a new instance of Store. // New creates a new instance of Store.
func New(db *sql.DB, driver Driver, profile *profile.Profile) *Store { func New(driver Driver, profile *profile.Profile) *Store {
return &Store{ return &Store{
Profile: profile, Profile: profile,
db: db,
driver: driver, driver: driver,
} }
} }
func (s *Store) GetDB() *sql.DB {
return s.db
}
func (s *Store) BackupTo(ctx context.Context, filename string) error { func (s *Store) BackupTo(ctx context.Context, filename string) error {
conn, err := s.db.Conn(ctx) return s.driver.BackupTo(ctx, filename)
if err != nil {
return errors.Errorf("fail to get conn %s", err)
}
defer conn.Close()
err = conn.Raw(func(driverConn any) error {
type backuper interface {
NewBackup(string) (*sqlite.Backup, error)
}
backupConn, ok := driverConn.(backuper)
if !ok {
return errors.Errorf("db connection is not a sqlite backuper")
}
bck, err := backupConn.NewBackup(filename)
if err != nil {
return errors.Errorf("fail to create sqlite backup %s", err)
}
for more := true; more; {
more, err = bck.Step(-1)
if err != nil {
return errors.Errorf("fail to execute sqlite backup %s", err)
}
}
return bck.Finish()
})
if err != nil {
return errors.Errorf("fail to backup %s", err)
}
return nil
} }
func (s *Store) Vacuum(ctx context.Context) error { func (s *Store) Vacuum(ctx context.Context) error {
tx, err := s.db.BeginTx(ctx, nil) return s.driver.Vacuum(ctx)
if err != nil {
return err
}
defer tx.Rollback()
if err := s.vacuumImpl(ctx, tx); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
// Vacuum sqlite database file size after deleting resource.
if _, err := s.db.Exec("VACUUM"); err != nil {
return err
}
return nil
} }
func (*Store) vacuumImpl(ctx context.Context, tx *sql.Tx) error { func (s *Store) Close() error {
if err := vacuumMemo(ctx, tx); err != nil { return s.driver.Close()
return err
}
if err := vacuumResource(ctx, tx); err != nil {
return err
}
if err := vacuumUserSetting(ctx, tx); err != nil {
return err
}
if err := vacuumMemoOrganizer(ctx, tx); err != nil {
return err
}
if err := vacuumMemoRelations(ctx, tx); err != nil {
return err
}
if err := vacuumTag(ctx, tx); err != nil {
// Prevent revive warning.
return err
}
return nil
} }

View File

@ -2,7 +2,6 @@ package store
import ( import (
"context" "context"
"database/sql"
) )
type Tag struct { type Tag struct {
@ -30,22 +29,3 @@ func (s *Store) ListTags(ctx context.Context, find *FindTag) ([]*Tag, error) {
func (s *Store) DeleteTag(ctx context.Context, delete *DeleteTag) error { func (s *Store) DeleteTag(ctx context.Context, delete *DeleteTag) error {
return s.driver.DeleteTag(ctx, delete) return s.driver.DeleteTag(ctx, delete)
} }
func vacuumTag(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
tag
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -130,10 +130,6 @@ func (s *Store) DeleteUser(ctx context.Context, delete *DeleteUser) error {
return err return err
} }
if err := s.Vacuum(ctx); err != nil {
// Prevent linter warning.
return err
}
s.userCache.Delete(delete.ID) s.userCache.Delete(delete.ID)
return nil return nil
} }

View File

@ -2,7 +2,6 @@ package store
import ( import (
"context" "context"
"database/sql"
storepb "github.com/usememos/memos/proto/gen/store" storepb "github.com/usememos/memos/proto/gen/store"
) )
@ -125,22 +124,3 @@ func (s *Store) GetUserAccessTokens(ctx context.Context, userID int32) ([]*store
accessTokensUserSetting := userSetting.GetAccessTokens() accessTokensUserSetting := userSetting.GetAccessTokens()
return accessTokensUserSetting.AccessTokens, nil return accessTokensUserSetting.AccessTokens, nil
} }
func vacuumUserSetting(ctx context.Context, tx *sql.Tx) error {
stmt := `
DELETE FROM
user_setting
WHERE
user_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return err
}
return nil
}

View File

@ -41,8 +41,7 @@ func NewTestingServer(ctx context.Context, t *testing.T) (*TestingServer, error)
} }
driver := sqlite.NewDriver(db.DBInstance) driver := sqlite.NewDriver(db.DBInstance)
store := store.New(driver, profile)
store := store.New(db.DBInstance, driver, profile)
server, err := server.NewServer(ctx, profile, store) server, err := server.NewServer(ctx, profile, store)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to create server") return nil, errors.Wrap(err, "failed to create server")

View File

@ -25,7 +25,6 @@ func NewTestingStore(ctx context.Context, t *testing.T) *store.Store {
} }
driver := sqlite.NewDriver(db.DBInstance) driver := sqlite.NewDriver(db.DBInstance)
store := store.New(driver, profile)
store := store.New(db.DBInstance, driver, profile)
return store return store
} }