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

Migrate old logs to new database schema #1828

Merged
merged 29 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7c42d9e
migration extracted from #1802
6543 Jun 5, 2023
ca4842f
Merge branch 'master' into fix-log_migration
6543 Jun 6, 2023
b0b4be5
Merge branch 'master' into fix-log_migration
6543 Jun 8, 2023
d5d8bb4
adjust
6543 Jun 8, 2023
0f8b6ba
init table first
6543 Jun 8, 2023
8c746ef
less verbose
6543 Jun 8, 2023
f7252ba
remove hack
6543 Jun 8, 2023
140c5ea
show "still running" info if a migration task takes more than 20 seconds
6543 Jun 8, 2023
7e633dd
tune pervormance
6543 Jun 9, 2023
461845c
tune pervormance (2)
6543 Jun 9, 2023
97aaa07
migration tests need more time to run
6543 Jun 9, 2023
0ca02f8
improve migration
6543 Jun 9, 2023
e0be228
Merge branch 'master' into fix-log_migration
6543 Jun 9, 2023
fbc0b3b
Update server/store/datastore/migration/019_alter_logs_table.go
6543 Jun 10, 2023
711e84a
Merge branch 'master' into fix-log_migration
6543 Jun 11, 2023
e468430
clean and make migration optional
6543 Jun 11, 2023
e0b928c
reuse alocated slice
6543 Jun 11, 2023
3320763
code format
6543 Jun 11, 2023
bb5ffd3
add option to let migrations handle there own sessions
6543 Jun 11, 2023
abb0946
add migration note
6543 Jun 11, 2023
29d49be
make it able to gracefully abort this migration
6543 Jun 11, 2023
a74f54c
fix lint
6543 Jun 11, 2023
16ebc8d
wording
6543 Jun 11, 2023
337f251
update docs
6543 Jun 11, 2023
582c1db
unregister sigterm listener on migration exit
6543 Jun 12, 2023
1adc5c2
Update server/store/datastore/migration/019_alter_logs_table.go
6543 Jun 12, 2023
533eb3d
Merge branch 'master' into fix-log_migration
6543 Jun 12, 2023
4457695
make it a flag
6543 Jun 12, 2023
f9acacc
rm json anotation
6543 Jun 12, 2023
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: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ test-cli: ## Test cli code
go test -race -cover -coverprofile cli-coverage.out -timeout 30s github.com/woodpecker-ci/woodpecker/cmd/cli github.com/woodpecker-ci/woodpecker/cli/...

test-server-datastore: ## Test server datastore
go test -timeout 30s -run TestMigrate github.com/woodpecker-ci/woodpecker/server/store/...
go test -timeout 60s -run TestMigrate github.com/woodpecker-ci/woodpecker/server/store/...
go test -race -timeout 30s -skip TestMigrate github.com/woodpecker-ci/woodpecker/server/store/...

test-server-datastore-coverage: ## Test server datastore with coverage report
go test -race -cover -coverprofile datastore-coverage.out -timeout 30s github.com/woodpecker-ci/woodpecker/server/store/...
go test -race -cover -coverprofile datastore-coverage.out -timeout 60s github.com/woodpecker-ci/woodpecker/server/store/...

test-ui: ui-dependencies ## Test UI code
(cd web/; pnpm run lint)
Expand Down
5 changes: 5 additions & 0 deletions cmd/server/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ var flags = []cli.Flag{
Usage: "status context format",
Value: "{{ .context }}/{{ .event }}/{{ .pipeline }}",
},
&cli.BoolFlag{
EnvVars: []string{"WOODPECKER_MIGRATIONS_ALLOW_LONG"},
Name: "migrations-allow-long",
Value: false,
},
//
// resource limit parameters
//
Expand Down
1 change: 1 addition & 0 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ func setupEvilGlobals(c *cli.Context, v store.Store, f forge.Forge) {
server.Config.Pipeline.Networks = c.StringSlice("network")
server.Config.Pipeline.Volumes = c.StringSlice("volume")
server.Config.Pipeline.Privileged = c.StringSlice("escalate")
server.Config.Server.Migrations.AllowLong = c.Bool("migrations-allow-long")

// prometheus
server.Config.Prometheus.AuthToken = c.String("prometheus-auth-token")
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/30-administration/00-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Below are resources requirements for Woodpecker components itself:

| Component | Memory | CPU |
| --------- | ------ | --- |
| Server | 32 MB | 1 |
| Agent | 32 MB | 1 |
| Server | 200 MB | 1 |
| Agent | 32 MB | 1 |

Note, that those values do not include the operating system or workload (pipelines execution) resources consumption.

Expand Down
1 change: 1 addition & 0 deletions docs/docs/91-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Some versions need some changes to the server configuration or the pipeline conf
- Dropped support for [Coding](https://coding.net/) and [Gogs](https://gogs.io).
- `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST`
- rename `pipeline:` key in your workflow config to `steps:`
- If you want to migrate old logs to the new format, watch the error messages on start. If there are none we are good to go, else you have to plan a migration that can take hours. Set `WOODPECKER_ALLOW_LONG_MIGRATION` to true and let it run.

## 0.15.0

Expand Down
3 changes: 3 additions & 0 deletions server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ var Config = struct {
StatusContextFormat string
SessionExpires time.Duration
RootURL string
Migrations struct {
AllowLong bool
}
// Open bool
// Orgs map[string]struct{}
// Admins map[string]struct{}
Expand Down
175 changes: 175 additions & 0 deletions server/store/datastore/migration/019_alter_logs_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package migration

import (
"context"
"encoding/json"
"fmt"
"runtime"

"github.com/rs/zerolog/log"
"github.com/tevino/abool"
"xorm.io/xorm"

"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/shared/utils"
)

// maxDefaultSqliteItems set the threshold at witch point the migration will fail by default
var maxDefaultSqliteItems019 = 5000

// perPage019 set the size of the slice to read per page
var perPage019 = 100

type oldLogs019 struct {
ID int64 `xorm:"pk autoincr 'log_id'"`
StepID int64 `xorm:"UNIQUE 'log_step_id'"`
Data []byte `xorm:"LONGBLOB 'log_data'"`
}

func (oldLogs019) TableName() string {
return "logs"
}

type oldLogEntry019 struct {
Step string `json:"step,omitempty"`
Time int64 `json:"time,omitempty"`
Type int `json:"type,omitempty"`
Pos int `json:"pos,omitempty"`
Out string `json:"out,omitempty"`
}

type newLogEntry019 struct {
ID int64 `xorm:"pk autoincr 'id'"`
StepID int64 `xorm:"'step_id'"`
Time int64
Line int
Data []byte `xorm:"LONGBLOB"`
Created int64 `xorm:"created"`
Type int
}

func (newLogEntry019) TableName() string {
return "log_entries"
}

var initLogsEntriesTable = task{
name: "init-log_entries",
required: true,
fn: func(sess *xorm.Session) error {
return sess.Sync(new(newLogEntry019))
},
}

var migrateLogs2LogEntries = task{
name: "migrate-logs-to-log_entries",
required: false,
engineFn: func(e *xorm.Engine) error {
// make sure old logs table exists
if exist, err := e.IsTableExist(new(oldLogs019)); !exist || err != nil {
return err
}

// first we check if we have just 1000 entries to migrate
toMigrate, err := e.Count(new(oldLogs019))
if err != nil {
return err
}

if toMigrate > int64(maxDefaultSqliteItems019) && !server.Config.Server.Migrations.AllowLong {
return fmt.Errorf("Migrating logs to log_entries is skipped, as we have %d entries to convert. Set 'WOODPECKER_MIGRATIONS_ALLOW_LONG' to 'true' to migrate anyway", toMigrate)
}

if err := e.Sync(new(oldLogs019)); err != nil {
return err
}

page := 0
logs := make([]*oldLogs019, 0, perPage019)
logEntries := make([]*oldLogEntry019, 0, 50)
sigterm := abool.New()
ctx, cancelCtx := context.WithCancelCause(context.Background())
defer cancelCtx(nil)
_ = utils.WithContextSigtermCallback(ctx, func() {
log.Info().Msg("ctrl+c received, stopping current migration")
sigterm.Set()
})

for {
if sigterm.IsSet() {
return fmt.Errorf("migration 'migrate-logs-to-log_entries' gracefully aborted")
}

sess := e.NewSession().NoCache()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
logs = logs[:0]

err := sess.Limit(perPage019).Find(&logs)
if err != nil {
return err
}

log.Trace().Msgf("migrate-logs-to-log_entries: process page %d", page)

for _, l := range logs {
logEntries = logEntries[:0]
if err := json.Unmarshal(l.Data, &logEntries); err != nil {
return err
}

time := int64(0)
for _, logEntry := range logEntries {

if logEntry.Time > time {
time = logEntry.Time
}

log := &newLogEntry019{
StepID: l.StepID,
Data: []byte(logEntry.Out),
Line: logEntry.Pos,
Time: time,
Type: logEntry.Type,
}

if _, err := sess.Insert(log); err != nil {
return err
}
}

if _, err := sess.Delete(l); err != nil {
return err
}
}

if err := sess.Commit(); err != nil {
return err
}

if len(logs) < perPage019 {
break
}

runtime.GC()
page++
}

return e.DropTables("logs")
},
}
52 changes: 32 additions & 20 deletions server/store/datastore/migration/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ var migrationTasks = []*task{
&dropFiles,
&removeMachineCol,
&dropOldCols,
&initLogsEntriesTable,
&migrateLogs2LogEntries,
}

var allBeans = []interface{}{
Expand Down Expand Up @@ -78,6 +80,8 @@ type task struct {
name string
required bool
fn func(sess *xorm.Session) error
// engineFn does manage session on it's own. only use it if you really need to
engineFn func(e *xorm.Engine) error
}

// initNew create tables for new instance
Expand Down Expand Up @@ -153,36 +157,44 @@ func runTasks(e *xorm.Engine, tasks []*task) error {
}

log.Trace().Msgf("start migration task '%s'", task.name)
sess := e.NewSession().NoCache()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

aliveMsgCancel := showBeAliveSign(task.name)
defer aliveMsgCancel(nil)
var taskErr error
if task.fn != nil {
aliveMsgCancel := showBeAliveSign(task.name)
if err := task.fn(sess); err != nil {
sess := e.NewSession().NoCache()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

if taskErr = task.fn(sess); taskErr != nil {
aliveMsgCancel(nil)
if err2 := sess.Rollback(); err2 != nil {
err = errors.Join(err, err2)
}

if task.required {
return err
taskErr = errors.Join(taskErr, err2)
}
log.Error().Err(err).Msgf("migration task '%s' failed but is not required", task.name)
continue
}
aliveMsgCancel(nil)
log.Debug().Msgf("migration task '%s' done", task.name)
if err := sess.Commit(); err != nil {
return err
}
} else if task.engineFn != nil {
taskErr = task.engineFn(e)
} else {
log.Trace().Msgf("skip migration task '%s'", task.name)
aliveMsgCancel(nil)
continue
}

if _, err := sess.Insert(&migrations{task.name}); err != nil {
return err
aliveMsgCancel(nil)
if taskErr != nil {
if task.required {
return taskErr
}
log.Error().Err(taskErr).Msgf("migration task '%s' failed but is not required", task.name)
continue
}
if err := sess.Commit(); err != nil {
log.Debug().Msgf("migration task '%s' done", task.name)

if _, err := e.Insert(&migrations{task.name}); err != nil {
return err
}

Expand Down