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

HA: Limit max open database connections to 1 #828

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

yhabteab
Copy link
Member

@yhabteab yhabteab commented Oct 23, 2024

Previously, the HA feature was allowed to open max_connections database connection in parallel to other Icinga DB components. Meaning, Icinga DB wasn't limited to the configured max_connections, but effectively to 2 * max_connections. Additionally, this masked a serious bug in the HA#realize() method, where we start a new transaction after each retry without rolling back in case of an error, leading to connections not being released before exceeding the ctx deadline.

@yhabteab yhabteab added the bug Something isn't working label Oct 23, 2024
@yhabteab yhabteab added this to the 1.3.0 milestone Oct 23, 2024
@cla-bot cla-bot bot added the cla/signed label Oct 23, 2024
@yhabteab
Copy link
Member Author

The HA transaction is rolled back correctly with:

@yhabteab yhabteab changed the title fix(HA): Limit max open database connections to 1 HA: Limit max open database connections to 1 Oct 23, 2024
@yhabteab
Copy link
Member Author

yhabteab commented Oct 23, 2024

This is the proof that we actually leak database connections but only for as long as the context is not cancelled.

package main

import (
	"context"
	"github.com/icinga/icinga-go-library/logging"
	"github.com/icinga/icingadb/internal/command"
	"go.uber.org/zap"
	"log"
	"time"
)

func main() {
	cmd := command.New()

	db, err := cmd.Database(logging.NewLogger(zap.NewNop().Sugar(), time.Second))
	if err != nil {
		log.Fatal(err)
	}
	db.SetMaxOpenConns(3)

	go func() {
		time.Sleep(1 * time.Second)

		ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
		_, _ = db.BeginTxx(ctx, nil)
		return
	}()
	go func() {
		time.Sleep(1 * time.Second)

		ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
		_, _ = db.BeginTxx(ctx, nil)
		return
	}()

	time.Sleep(3 * time.Second)

	ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second))
	defer cancelFunc()
	_ = db.PingContext(ctx)
	log.Printf("OpenConnections: %v", db.Stats().OpenConnections)
	log.Printf("InUse: %v", db.Stats().InUse)
}

~/Workspace/go/icingadb (set-ha-conn-limit ✗) go run ./cmd/decoder/decoder.go --config config.example.yml
2024/10/23 16:19:59 OpenConnections: 3
2024/10/23 16:19:59 InUse: 2

@yhabteab yhabteab requested review from lippserd and removed request for lippserd October 23, 2024 14:25
@@ -63,6 +63,12 @@ do not need any manual adjustments.
| max_rows_per_transaction | **Optional.** Maximum number of rows Icinga DB is allowed to `SELECT`,`DELETE`,`UPDATE` or `INSERT` in a single transaction. Defaults to `8192`. |
| wsrep_sync_wait | **Optional.** Enforce [Galera cluster](#galera-cluster) nodes to perform strict cluster-wide causality checks. Defaults to `7`. |

!!! info
Copy link
Member

Choose a reason for hiding this comment

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

I would remove this information as it is an implementation detail. Sure, users might be surprised if there are 17 database connections, but I doubt anyone will ever count them.

Instead, we could reduce the size of the main pool by one. But I wouldn't attach too much importance to that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Since the size of the main pool is initialized in the library, we don't want to adjust it just because of Icinga DB's HA feature. Thus, I removed the highlighted info box.

Copy link
Member

Choose a reason for hiding this comment

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

The example configuration would also have to be adjusted. But please just remove it completely. It is irrelevant.

Copy link
Member Author

Choose a reason for hiding this comment

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

As we discussed offline, I have completely removed the extra info.

Copy link
Member

Choose a reason for hiding this comment

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

I want to summarize our discussion: With this PR, we currently have only one connection that isn't tied to max_connections. However, there's a chance more could be added or changed, leaving it up to the user to determine and maintain the correct max_connections value if the goal is to truly limit Icinga DB database connections to that number. Therefore, if max_connections is intended to represent the actual maximum connections, it should be implemented accordingly, rather than documented that some connections are unaffected by that setting.

Previously, the HA feature was allowed to open `max_connections`
database connection in parallel to other Icinga DB components. Meaning,
Icinga DB wasn't limited to the configured `max_connections`, but
effectively to `2 * max_connections`. Additionally, this masked a
serious bug in the `HA#realize()` method, where we start a new
transaction after each retry without rolling back in case of an error,
leading to connections not being released before exceeding the ctx
deadline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working cla/signed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants