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

Sqlite fixes #17874

Merged
merged 4 commits into from
Mar 22, 2023
Merged

Sqlite fixes #17874

merged 4 commits into from
Mar 22, 2023

Conversation

mheon
Copy link
Member

@mheon mheon commented Mar 21, 2023

Two fixes:

  • Ensure that SQLite schema migration happens before we init any part of the database, so we never access a database with a bad schema.
  • Fix a flake where we were getting database locked errors due to concurrent read/write of the same table.
NONE

mheon added 2 commits March 17, 2023 13:24
It now can safely run on bare databases, before any tables are
created.

Signed-off-by: Matthew Heon <[email protected]>
I was searching the SQLite docs for a fix, but apparently that
was the wrong place; it's a common enough error with the Go
frontend for SQLite that the fix is prominently listed in the API
docs for go-sqlite3. Setting cache mode to 'shared' and using a
maximum of 1 simultaneous open connection should fix.

Performance implications of this are unclear, but cache=shared
sounds like it will be a benefit, not a curse.

[NO NEW TESTS NEEDED] This fixes a flake with concurrent DB
access.

Signed-off-by: Matthew Heon <[email protected]>
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 21, 2023

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mheon

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 21, 2023
@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

@edsantiago This should get rid of the database locked flake, but I haven't been able to reproduce locally so I can't be 100% on that.

@@ -45,7 +45,7 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
return nil, fmt.Errorf("creating root directory: %w", err)
}

conn, err := sql.Open("sqlite3", filepath.Join(basePath, "db.sql?_loc=auto"))
conn, err := sql.Open("sqlite3", filepath.Join(basePath, "db.sql?_loc=auto&cache=shared"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to conflict with #17867

@vrothberg
Copy link
Member

Can we make a mix of #17867 and this PR?

I really think that setting the PRAGMAs as done in #17867 is 1) faster and 2) easier to read.

@@ -57,6 +57,9 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
}
}()

// Necessary to avoid database locked errors.
conn.SetMaxOpenConns(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that effectively excluding all other processes from opening a connection?

@edsantiago
Copy link
Member

FWIW, the string "UNIQUE" (case-sensitive, caps) does not appear in the CI logs, nor do any of the following case-insensitive strings:

  • "constraint fail"
  • "config row"
  • "adding db"
  • "dbconfig.id"

Timings, with a reminder that this is not equivalent to serious performance benchmarks

type distro user DB local remote container
int fedora-37 root 30:45 33:11 27:59
int fedora-37 root sqlite 30:59 33:33
int fedora-37 rootless 29:41
int fedora-37 rootless sqlite 28:50

@edsantiago
Copy link
Member

System test runs in #17831 are done. Saw lots of "database is locked", but no "unique etc" errors.

The symptoms in containers#17859 indicate that setting the PRAGMAs in individual
EXECs outside of a transaction can lead to concurrency issues and
failures when the DB is locked.  Hence set all PRAGMAs when opening
the connection.  Move them into individual constants to improve
documentation and readability.

Further make transactions exclusive as containers#17859 also mentions an error
that the DB is locked during a transaction.

[NO NEW TESTS NEEDED] - existing tests cover the code.

Fixes: containers#17859
Signed-off-by: Valentin Rothberg <[email protected]>

<MH: Cherry-picked on top of my branch>

Signed-off-by: Matthew Heon <[email protected]>
@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

I have pulled in #17867 from @vrothberg

@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

Marking as WIP: I'm going to perf test max connections vs transaction locking.

@mheon mheon changed the title Sqlite fixes [WIP] Sqlite fixes Mar 21, 2023
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 21, 2023
@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

@edsantiago Did you pull in Valentin's earlier PR, and if so did it resolve the database locked issue?

@edsantiago
Copy link
Member

@mheon yes I made an earlier run of #17831 using @vrothberg's fixes, and saw no instances of the "is locked" bug. I'm now rerebasing on this PR, and will resubmit in a few seconds.

@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

Benchmark 1: /home/mheon/code/podman/bin/podman --db-backend=sqlite ps -a
  Time (mean ± σ):      21.7 ms ±   1.0 ms    [User: 27.1 ms, System: 11.8 ms]
  Range (min … max):    19.1 ms …  24.1 ms    100 runs
 
Benchmark 2: /home/mheon/code/podman/bin/podman_maxcons --db-backend=sqlite ps -a
  Time (mean ± σ):      22.2 ms ±   1.2 ms    [User: 28.8 ms, System: 11.9 ms]
  Range (min … max):    19.5 ms …  26.4 ms    100 runs
 
Summary
  '/home/mheon/code/podman/bin/podman --db-backend=sqlite ps -a' ran
    1.03 ± 0.08 times faster than '/home/mheon/code/podman/bin/podman_maxcons --db-backend=sqlite ps -a'

@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

Maxcons is definitely slowing us down somewhat, at least at high container counts (that was 500)

@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

It goes down to ~1% slower at lower container counts

@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

Based on current numbers, the tx lock seems slightly faster. I'm going to remove the connection count limit.

The SQLite transaction lock Valentin found is (slightly) faster.
So let's go with that.

Signed-off-by: Matthew Heon <[email protected]>
@mheon
Copy link
Member Author

mheon commented Mar 21, 2023

Re-pushed. Max connections logic is gone. Removing WIP. Going to switch to sqlite vs bolt perf comparisons.

@mheon mheon changed the title [WIP] Sqlite fixes Sqlite fixes Mar 21, 2023
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 21, 2023
@edsantiago
Copy link
Member

Followup: I did not see UNIQUE constraint nor 'database is locked` in system tests, yay, but I do see one new failure I've never seen before, in f37 rootless sqlite:

  stop podman.service
  ...
  Execing /var/tmp/go/src/github.com/containers/podman/bin/podman --root /tmp/podman_test2489001336/server_root inspect --format={{.State.Running}} top_ccwvMutB
  Error: inspecting object: no such object: "top_ccwvMutB"

What's really curious is that this is an e2e test that says Execing, whereas usually they say "Running". And the "Execing" line does not include the --db-backend or any of the other long list of options usually shown in "Running" log lines. Is this some weird different way of running podman?

Worth pointing out that this PR is being run on a system with /etc/containers/containers.conf force-modified to use sqlite, so this is different from the current CI situation in which testing is happening via command-line --db-backend options.

@edsantiago
Copy link
Member

@mheon @vrothberg I'm sorry to report that f37 rootless sqlite had one failure in my PR:

$ podman [options] ps -q
Error: adding DB config row: UNIQUE constraint failed: DBConfig.ID

(where "my PR" is #17831 rebased against 0c313b6e5)

@mheon
Copy link
Member Author

mheon commented Mar 22, 2023

Damn. Alright, have to add even more validation there, I guess.

@vrothberg
Copy link
Member

Damn. Alright, have to add even more validation there, I guess.

I think there's a race condition in creating the table. Checking for the table existence should be part of the transaction:

diff --git a/libpod/sqlite_state.go b/libpod/sqlite_state.go
index e1007dbc3709..9df98dc4b465 100644
--- a/libpod/sqlite_state.go
+++ b/libpod/sqlite_state.go
@@ -336,23 +336,21 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
 		runtimeGraphDriver = storeOpts.GraphDriverName
 	}
 
-	row := s.conn.QueryRow("SELECT Os, StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;")
-
+	tx, err := s.conn.Begin()
+	if err != nil {
+		return fmt.Errorf("beginning DB config transaction: %w", err)
+	}
+	defer func() {
+		if defErr != nil {
+			if err := tx.Rollback(); err != nil {
+				logrus.Errorf("Rolling back transaction to create DB config: %v", err)
+			}
+		}
+	}()
+	row := tx.QueryRow("SELECT Os, StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;")
 	if err := row.Scan(&os, &staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumePath); err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
 			// Need to create runtime config info in DB
-			tx, err := s.conn.Begin()
-			if err != nil {
-				return fmt.Errorf("beginning DB config transaction: %w", err)
-			}
-			defer func() {
-				if defErr != nil {
-					if err := tx.Rollback(); err != nil {
-						logrus.Errorf("Rolling back transaction to create DB config: %v", err)
-					}
-				}
-			}()
-
 			if _, err := tx.Exec(createRow, 1, schemaVersion, runtimeOS,
 				runtimeStaticDir, runtimeTmpDir, runtimeGraphRoot,
 				runtimeRunRoot, runtimeGraphDriver, runtimeVolumePath); err != nil {

@vrothberg
Copy link
Member

@edsantiago, could you apply the upper diff to your PR and see whether it fixes the flake?

@edsantiago
Copy link
Member

@vrothberg done, run is in progress. Quick note that this flake is rare: it did not happen at all on my last CI rerun, and it did not happen in this PR's most recent run. (As in: I downloaded all the logs and grep'ed them for even single failures). I will of course continue to download-and-grep all logs for this and my PR and report my findings.

@edsantiago
Copy link
Member

Well, doesn't look good. Didn't even make it past the image fetch:

Caching quay.io/libpod/cirros:latest at /tmp/quay.io-libpod-cirros-latest.tar...
$ podman [options] pull quay.io/libpod/cirros:latest
Error: beginning refresh transaction: database is locked

Copy link
Member

@vrothberg vrothberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
I vote for merging this PR as is. It's already an improvement and we can tackle the remaining flake(s) separately.

@vrothberg
Copy link
Member

Well, doesn't look good. Didn't even make it past the image fetch:

Caching quay.io/libpod/cirros:latest at /tmp/quay.io-libpod-cirros-latest.tar...
$ podman [options] pull quay.io/libpod/cirros:latest
Error: beginning refresh transaction: database is locked

This error is really weird. @mheon, shouldn't Podman just busy-wait until the DB is unlocked by another thread/process?

@edsantiago
Copy link
Member

Incremental progress is totally fine with me.

/lgtm
/hold

@openshift-ci openshift-ci bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Mar 22, 2023
@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Mar 22, 2023
@baude
Copy link
Member

baude commented Mar 22, 2023

/hold cancel

@openshift-ci openshift-ci bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Mar 22, 2023
@openshift-merge-robot openshift-merge-robot merged commit 6b9f314 into containers:main Mar 22, 2023
@github-actions github-actions bot added the locked - please file new issue/PR Assist humans wanting to comment on an old issue or PR with locked comments. label Sep 4, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. locked - please file new issue/PR Assist humans wanting to comment on an old issue or PR with locked comments. release-note-none
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants