Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add database optimise task #3929

Merged
merged 5 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions graphql/documents/mutations/metadata.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ mutation BackupDatabase($input: BackupDatabaseInput!) {
mutation AnonymiseDatabase($input: AnonymiseDatabaseInput!) {
anonymiseDatabase(input: $input)
}

mutation OptimiseDatabase {
optimiseDatabase
}
3 changes: 3 additions & 0 deletions graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,9 @@ type Mutation {
"Anonymise the database in a separate file. Optionally returns a link to download the database file"
anonymiseDatabase(input: AnonymiseDatabaseInput!): String

"Optimises the database. Returns the job ID"
optimiseDatabase: ID!

"Reload scrapers"
reloadScrapers: Boolean!

Expand Down
5 changes: 5 additions & 0 deletions internal/api/resolver_mutation_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,8 @@ func (r *mutationResolver) AnonymiseDatabase(ctx context.Context, input Anonymis

return nil, nil
}

func (r *mutationResolver) OptimiseDatabase(ctx context.Context) (string, error) {
jobID := manager.GetInstance().OptimiseDatabase(ctx)
return strconv.Itoa(jobID), nil
}
8 changes: 8 additions & 0 deletions internal/manager/manager_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@ func (s *Manager) Clean(ctx context.Context, input CleanMetadataInput) int {
return s.JobManager.Add(ctx, "Cleaning...", &j)
}

func (s *Manager) OptimiseDatabase(ctx context.Context) int {
j := OptimiseDatabaseJob{
Optimiser: s.Database,
}

return s.JobManager.Add(ctx, "Optimising database...", &j)
}

func (s *Manager) MigrateHash(ctx context.Context) int {
j := job.MakeJobExec(func(ctx context.Context, progress *job.Progress) {
fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm()
Expand Down
56 changes: 56 additions & 0 deletions internal/manager/task_optimise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package manager

import (
"context"
"time"

"github.com/stashapp/stash/pkg/job"
"github.com/stashapp/stash/pkg/logger"
)

type Optimiser interface {
Analyze(ctx context.Context) error
Vacuum(ctx context.Context) error
}

type OptimiseDatabaseJob struct {
Optimiser Optimiser
}

func (j *OptimiseDatabaseJob) Execute(ctx context.Context, progress *job.Progress) {
logger.Info("Optimising database")
progress.SetTotal(2)

start := time.Now()

var err error

progress.ExecuteTask("Analyzing database", func() {
err = j.Optimiser.Analyze(ctx)
progress.Increment()
})
if job.IsCancelled(ctx) {
logger.Info("Stopping due to user request")
return
}
if err != nil {
logger.Errorf("Error analyzing database: %v", err)
return
}

progress.ExecuteTask("Vacuuming database", func() {
err = j.Optimiser.Vacuum(ctx)
progress.Increment()
})
if job.IsCancelled(ctx) {
logger.Info("Stopping due to user request")
return
}
if err != nil {
logger.Errorf("Error vacuuming database: %v", err)
return
}

elapsed := time.Since(start)
logger.Infof("Finished optimising database after %s", elapsed)
}
2 changes: 1 addition & 1 deletion pkg/sqlite/anonymise.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (db *Anonymiser) Anonymise(ctx context.Context) error {
func() error { return db.anonymiseStudios(ctx) },
func() error { return db.anonymiseTags(ctx) },
func() error { return db.anonymiseMovies(ctx) },
func() error { db.optimise(); return nil },
func() error { return db.Optimise(ctx) },
})
}(); err != nil {
// delete the database
Expand Down
27 changes: 20 additions & 7 deletions pkg/sqlite/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,21 +436,28 @@ func (db *Database) RunMigrations() error {
}

// optimize database after migration
db.optimise()
err = db.Optimise(ctx)
if err != nil {
logger.Warnf("error while performing post-migration optimisation: %v", err)
}

return nil
}

func (db *Database) optimise() {
logger.Info("Optimizing database")
_, err := db.db.Exec("ANALYZE")
func (db *Database) Optimise(ctx context.Context) error {
logger.Info("Optimising database")

err := db.Analyze(ctx)
if err != nil {
logger.Warnf("error while performing post-migration optimization: %v", err)
return fmt.Errorf("performing optimization: %w", err)
}
_, err = db.db.Exec("VACUUM")

err = db.Vacuum(ctx)
if err != nil {
logger.Warnf("error while performing post-migration vacuum: %v", err)
return fmt.Errorf("performing vacuum: %w", err)
}

return nil
}

// Vacuum runs a VACUUM on the database, rebuilding the database file into a minimal amount of disk space.
Expand All @@ -459,6 +466,12 @@ func (db *Database) Vacuum(ctx context.Context) error {
return err
}

// Analyze runs an ANALYZE on the database to improve query performance.
func (db *Database) Analyze(ctx context.Context) error {
_, err := db.db.ExecContext(ctx, "ANALYZE")
return err
}

func (db *Database) ExecSQL(ctx context.Context, query string, args []interface{}) (*int64, *int64, error) {
wrapper := dbWrapper{}

Expand Down
42 changes: 40 additions & 2 deletions ui/v2.5/src/components/Settings/Tasks/DataManagementTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
mutateAnonymiseDatabase,
mutateMigrateSceneScreenshots,
mutateMigrateBlobs,
mutateOptimiseDatabase,
} from "src/core/StashService";
import { useToast } from "src/hooks/Toast";
import downloadFile from "src/utils/download";
Expand Down Expand Up @@ -338,6 +339,24 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
}
}

async function onOptimiseDatabase() {
try {
await mutateOptimiseDatabase();
Toast.success({
content: intl.formatMessage(
{ id: "config.tasks.added_job_to_queue" },
{
operation_name: intl.formatMessage({
id: "actions.optimise_database",
}),
}
),
});
} catch (e) {
Toast.error(e);
}
}

async function onAnonymise(download?: boolean) {
try {
setIsAnonymiseRunning(true);
Expand Down Expand Up @@ -419,6 +438,25 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
setOptions={(o) => setCleanOptions(o)}
/>
</div>

<Setting
headingID="actions.optimise_database"
subHeading={
<>
<FormattedMessage id="config.tasks.optimise_database" />
<br />
<FormattedMessage id="config.tasks.optimise_database_warning" />
</>
}
>
<Button
id="optimiseDatabase"
variant="danger"
onClick={() => onOptimiseDatabase()}
>
<FormattedMessage id="actions.optimise_database" />
</Button>
</Setting>
</SettingSection>

<SettingSection headingID="metadata">
Expand Down Expand Up @@ -519,7 +557,7 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
)}
>
<Button
id="backup"
id="anonymise"
variant="secondary"
type="submit"
onClick={() => onAnonymise()}
Expand All @@ -533,7 +571,7 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
subHeadingID="config.tasks.anonymise_and_download"
>
<Button
id="anonymousDownload"
id="anonymiseDownload"
variant="secondary"
type="submit"
onClick={() => onAnonymise(true)}
Expand Down
5 changes: 5 additions & 0 deletions ui/v2.5/src/core/StashService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,11 @@ export const mutateAnonymiseDatabase = (input: GQL.AnonymiseDatabaseInput) =>
variables: { input },
});

export const mutateOptimiseDatabase = () =>
client.mutate<GQL.OptimiseDatabaseMutation>({
mutation: GQL.OptimiseDatabaseDocument,
});

export const mutateMigrateHashNaming = () =>
client.mutate<GQL.MigrateHashNamingMutation>({
mutation: GQL.MigrateHashNamingDocument,
Expand Down
3 changes: 3 additions & 0 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"not_running": "not running",
"open_in_external_player": "Open in external player",
"open_random": "Open Random",
"optimise_database": "Optimise Database",
"overwrite": "Overwrite",
"play_random": "Play Random",
"play_selected": "Play selected",
Expand Down Expand Up @@ -484,6 +485,8 @@
},
"migrations": "Migrations",
"only_dry_run": "Only perform a dry run. Don't remove anything",
"optimise_database": "Attempt to improve performance by analysing and then rebuilding the entire database file.",
"optimise_database_warning": "Warning: while this task is running, any operations that modify the database will fail, and depending on your database size, it could take several minutes to complete. It also requires at the very minimum as much free disk space as your database is large, but 1.5x is recommended.",
"plugin_tasks": "Plugin Tasks",
"scan": {
"scanning_all_paths": "Scanning all paths",
Expand Down
1 change: 1 addition & 0 deletions ui/v2.5/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
}
}
},
"optimise_database": "Optimize Database",
"performer_favorite": "Performer Favorited"
}