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

Change to native pgx/v5 driver with scany #244

Merged
merged 2 commits into from
Oct 5, 2022
Merged

Conversation

lzap
Copy link
Member

@lzap lzap commented Oct 3, 2022

Replaces database/sql and sqlx with pgx driver and scany library. There are multiple reasons why I want to do this.

  • The code is cleaner and more simple since pgx driver automatically prepares SQL statements and cache them.
  • Better errors (error messages do appear to be more detailed).
  • Native logging via zerolog and tracing via opentelemetry.
  • The driver natively supports many custom types, including JSONB which we use (which should be faster to encode/decode).
  • The driver of choice for image builder, this allows us to directly use jobqueue and we could drop our copy from the codebase.

This is a WIP branch, the way I want to approach this is to show how it looks like and go through review first, once the code is okay I will proceed with the all DAO facades. The only implemented facade is: Accounts (integration tests are passing for this DAO).

This uses very recent version of pgx (v5) and scanny (v2) and tern migration library (v2). While pgx/v5 has been out for few weeks, both mentioned libraries are currently in v2-beta stage. The replace statement in go.mod is temporary, I hope the pgx author will merge my small patch that enables to use proper Go mod: jackc/tern#69

Done:

  • Simplify DAO errors
  • Remove NameForError functions from DAO
  • Remove Value/Scan from reservation model (comment // TODO: Use pgx native driver with scany library instead of sqlx which does not)

Things I want to add as separate commits after review is done:

  • Get rid of error from GetXXXDao methods

Things to be done in other PRs:

@@ -54,7 +54,7 @@ func main() {
config.Initialize()

// initialize stdout logging and AWS clients first
log.Logger = logging.InitializeStdout()
logging.InitializeStdout()
Copy link
Member Author

Choose a reason for hiding this comment

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

The log.Logger is now set by the function itself.

if err != nil {
log.Fatal().Err(err).Msg("Error initializing database")
}
defer db.Close()
Copy link
Member Author

Choose a reason for hiding this comment

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

Connection pool is now properly closed.

@@ -76,10 +76,11 @@ func main() {
defer tel.Close(ctx)

// initialize the rest
err = db.Initialize("public")
err = db.Initialize(ctx, "public")
Copy link
Member Author

Choose a reason for hiding this comment

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

Database initialization and migration now requires Context.

// go get github.com/jackc/tern/migrate@v2.0.0-beta.1

replace github.com/jackc/tern => github.com/lzap/tern2 v0.0.0-20220813151204-54e00e2a8f9d

Copy link
Member Author

Choose a reason for hiding this comment

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

This will be removed once tern v2 is properly tagged, the two go get commands are required until stable versions are released tho.

I suggest to go ahead and merge this so we can start testing this on stage.

Copy link
Member

Choose a reason for hiding this comment

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

hump, we will need to convince our linter tho.
Also do we have any timelines for the releases.
I'm fine being early adopter, but only if we know the releases are likely to happen in timely manner.

Copy link
Member Author

Choose a reason for hiding this comment

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

The replace statement is not needed anymore since tern v2.beta.2 was just tagged, so it's not necessary now.

parser.Viper.SetDefault("database.minConn", 2)
parser.Viper.SetDefault("database.maxConn", 100)
parser.Viper.SetDefault("database.maxLifetime", 2*time.Hour)
parser.Viper.SetDefault("database.maxIdleTime", 15*time.Minute)
Copy link
Member Author

Choose a reason for hiding this comment

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

FYI the default values from the pgx driver are:

  • Minimum conn: 0
  • Max conn: 4
  • Lifetime: 1 hour
  • Idletime: 30 minutes

Our values are wild guesses, we need to talk with SRE about this in more detail.

// TODO let's vastly simplify error handling in DAO, let's just create aliases for native PGX errors
// and that should be enough:
// var NoRowsError = pgx.ErrNoRows

Copy link
Member Author

Choose a reason for hiding this comment

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

Once I am done with DAO implementations, I would like to vastly simplify interface handling in our DAO interface. I am hoping to have just aliases/copies of all errors DAO callers need to work with, thus utilizing native PGX errors.

// TODO DAO getters should not return error anymore, we use it everywhere
func getAccountPGX(ctx context.Context) (dao.AccountDao, error) {
return &accountDao{name: "account"}, nil
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Once I am done with this, getXXXDao will not return an error because there will be no statement prepare calls. This is a huge deal as we call this function everywhere, so we will save a lot of error handling. I would like to do this change in a separate commit/PR tho as it will be a big bang.

@@ -108,7 +108,7 @@ func TestGetByOrgId(t *testing.T) {
assert.Equal(t, "1", account.AccountNumber.String)
}

func TestGetOrCreateByIdentityGet(t *testing.T) {
func TestGetOrCreateByIdentityGetAccount(t *testing.T) {
accDao, ctx := setupAccount(t)
defer teardownAccount(t)
account, err := accDao.GetOrCreateByIdentity(ctx, "1", "1")
Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed these so I can test against "Account" pattern.

Copy link
Member

Choose a reason for hiding this comment

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

What is "Account" pattern? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

You can run tests against a regexp patter, so I was running all tests starting with Account, then with Pubkey etc. This allowed me to refactor and test DAO after DAO.

// PGX is the main connection pool for the whole application
var PGX *pgxpool.Pool

// TODO remove after all DAO packages are rewritten
var DB *sqlx.DB
Copy link
Member Author

Choose a reason for hiding this comment

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

This will be removed once done with the refactoring.

poolConfig.ConnConfig.Tracer = &tracelog.TraceLog{
Logger: zeroLogger,
LogLevel: logLevel,
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Tracing and logging with full context support and spans, yay! :-)

import "embed"

//go:embed *.sql
var EmbeddedSeeds embed.FS
Copy link
Member Author

Choose a reason for hiding this comment

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

Had to move both migrations and seeds into the directory because the way the Tern API is designed today, it does not allow callers to locate files from subdirectories.

@lzap
Copy link
Member Author

lzap commented Oct 3, 2022

Pushed new changes:

  • Updated go.mod it no longer requires replace
  • Pubkeys pgx implementation
  • More pubkey tests - all are green now

Last thing to solve is reservations implementation, then I can remove sqlx completely. Tests will be failing until this is fixed.

I am thinking renaming db.PGX to db.Pool which is a better name and also PGX is kinda hard to type.

Copy link
Member

@ezr-ondrej ezr-ondrej left a comment

Choose a reason for hiding this comment

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

So if I understand correctly the pgx implementatios are tested, but not used in prod, right? :)
Just making sure, I agree it's a correct approach 👍
I have just one nit and I'd love to hear your opinion on keeping the naming convention for test methods 1:1 with tested methods

@@ -74,6 +75,13 @@ func TestGetResourceByProviderType(t *testing.T) {
assert.Equal(t, resource, createdResource)
}

func TestGetPubkeyResourceByProviderTypeNoRows(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather see more test in one function by t.Run() if those are for the same function, I've tried to start this, but we do not have it everywhere, so up to you, but I'd love to have test function per function mapping 1:1 if possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure.

Comment on lines 17 to 18
var ErrNoRows = pgx.ErrNoRows
var ErrAffectedMismatch = errors.New("unexpected affected rows")
Copy link
Member

Choose a reason for hiding this comment

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

I guess linter wants to group the var statements, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah.

@lzap
Copy link
Member Author

lzap commented Oct 4, 2022

So if I understand correctly the pgx implementatios are tested, but not used in prod, right? :)

Yeah, this is only during the review tho, at the end of this PR, I will remove sqlx completely. Having two implementations for even a week is a dangerous thing (we could accidentally change only one and not the other).

@lzap
Copy link
Member Author

lzap commented Oct 4, 2022

The work is done, app is working properly locally.

This is now ready for formal review. After this is confirmed to be working, I want to add two more commits to further simplify and rename some DAO interface functions. These would be pure refactoring, no code changes.

@lzap
Copy link
Member Author

lzap commented Oct 4, 2022

(The linter fails because it sees we use unreleased version of the tern library. Let's ignore that for a moment.)

@ezr-ondrej
Copy link
Member

These would be pure refactoring, no code changes

Can these be done in separate PR then?

@lzap
Copy link
Member Author

lzap commented Oct 5, 2022

Can these be done in separate PR then?

Sure, then consider this as done, feel free to review. I will disable the linters complain about use of unreleased versions, both project authors confirmed they intend to release soon.

@lzap lzap force-pushed the pgx5 branch 2 times, most recently from 30c3e96 to b958e71 Compare October 5, 2022 12:32
@lzap
Copy link
Member Author

lzap commented Oct 5, 2022

Rebased because tests were failing. I also renamed PGX to Conn which is better.

@@ -71,6 +73,7 @@ type ReservationDao interface {
List(ctx context.Context, limit, offset int64) ([]*models.Reservation, error)

// ListInstances returns instances associated to a reservation. UNSCOPED.
// It currently lists all instances and not instances for a reservation, this is a TODO.
ListInstances(ctx context.Context, limit, offset int64) ([]*models.ReservationInstance, error)
Copy link
Member Author

Choose a reason for hiding this comment

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

require.ErrorIs(t, err, dao.ErrAffectedMismatch)
})

// TODO validators
Copy link
Member Author

Choose a reason for hiding this comment

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

This TODO is for me to add even more pubkey tests (validators).

I can add it either during rebase of this PR or later.

Copy link
Member

@ezr-ondrej ezr-ondrej left a comment

Choose a reason for hiding this comment

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

I've got two filosophical comments, but nothing major, I'm all for getting this in. Looks as great improvement :)

Context context.Context
}

var WrongTenantError = errors.New("trying to manipulate data of different tenant")
Copy link
Member

Choose a reason for hiding this comment

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

I did like throwing the same errors from stubs as from the real implementations, it gave stub methods another layer of real. If we are getting rid of these errors, shall we get rid of them from stubs as well?

}

func (x *accountDao) GetById(ctx context.Context, id int64) (*models.Account, error) {
query := `SELECT * FROM accounts WHERE id = $1 LIMIT 1`
Copy link
Member

Choose a reason for hiding this comment

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

I kinda liked the queries being together, I get that it's not necessary now, but won't it still be a benefit to have these as package variables (or constants) on top of each DAO file?

Copy link
Member

Choose a reason for hiding this comment

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

I've gotten convinced these are better directly in the functions, as it's usually changed together.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah indeed, we can get to a point that some queries would be shared across functions, in that case let's totally make constants. But so far it's trivial.

@lzap
Copy link
Member Author

lzap commented Oct 5, 2022

Merged DAO and DAO stubs errors into a single package, it's now consistent and clean. Added extra validation tests to the pubkey dao tests suite. Rebased.

Copy link
Member

@ezr-ondrej ezr-ondrej left a comment

Choose a reason for hiding this comment

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

Great job @lzap ! let's hope the libraries will release soon, but our code looks much healthier now! 🎉

@ezr-ondrej ezr-ondrej merged commit ee76648 into RHEnVision:main Oct 5, 2022
@lzap lzap deleted the pgx5 branch October 5, 2022 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants