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

Removes the need for pg client tools #942

Merged
merged 3 commits into from
Jun 24, 2023
Merged

Removes the need for pg client tools #942

merged 3 commits into from
Jun 24, 2023

Conversation

jwoertink
Copy link
Member

@jwoertink jwoertink commented May 15, 2023

Fixes #926

This doesn't fully remove the need for it, but it does remove the need for it when doing db.create and db.drop tasks.

The two main areas still remaining are db.schema.dump and db.schema.restore tasks which currently require psql and pg_dump to exist. I would like to somehow catch those and raise some sort of error before you run them. Right now if you don't have pg client tools installed and try to run either of those, you'll just end up with some exceptions saying it couldn't find those commands. Not horrible, but not great...

After doing this, there was some weird things that came up. Previously you could do this:

SomeDatabase.run do |db|
  db.exec "SELECT some_sql"
end

DB::Drop.run_task

That seemed to be ok maybe because it was running dropdb from the CLI? 🤷‍♂️ However, now, this raises an exception saying the that DB is currently in use. I guess this is a good thing, but it required adding a way to ensure all connections and the DB were fully closed. I'll add some more comments on this PR

Avram.temp_config(database_to_migrate: SampleBackupDatabase) do
Avram::Migrator::Runner.create_db(quiet?: true)

DB.open(SampleBackupDatabase.credentials.url) do |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 fully closes the DB connection when the block ends allowing me to drop the DB.

Comment on lines -37 to -40
class SampleBackupDatabase < Avram::Database
end

SampleBackupDatabase.configure do |settings|
Copy link
Member Author

Choose a reason for hiding this comment

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

I moved this to config/database.cr where all the others are. It should have been there in the first place anyway 😂

Comment on lines 8 to 15
def open : DB::Database
try_connection!
@db = try_connection!
end

def close
@db.try(&.close)
@db = nil
end
Copy link
Member Author

Choose a reason for hiding this comment

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

I don't really understand this. We've never had a db.close before, but it feels like there should have been... I think we just open a connection, then let it sit open in the connection pool forever. If you redeploy your app, that kills all of the connections which may explain why we always get IO errors randomly... But what's a better way to handle this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like a plausible explanation. That would solve the bulk of the use cases and the stuff that did generate errors would just get dismissed by restarting the container.

I think this is 👍

Comment on lines +85 to +86
def connection_string : String
String.build do |io|
Copy link
Member Author

Choose a reason for hiding this comment

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

In order to run SQL before a database exists, we need the connection string postgres://user:pass@host:5432

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I was just saying that I moved this logic out so I can differentiate between

postgres://user:pass@host:5432

and

postgres://user:pass@host:5432/some_db

Copy link
Contributor

Choose a reason for hiding this comment

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

oh I get it. I thought you were saying that you were filling in some sample connection string details for type safety .... o_O I was way off...

src/avram/database.cr Outdated Show resolved Hide resolved
puts "Done creating #{Avram::Migrator::Runner.db_name.colorize(:green)}"
puts "Done creating #{db_name.colorize(:green)}"
end
rescue e : DB::ConnectionRefused
Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what level the issue is at, but when you can't connect, a PQ::PQError is raised with a nice helpful error... then that gets swallowed somewhere and a DB::ConnectionRefused is raised in it's place with no error message... so e.message is nil. They just expect you to read through the stack trace.

Comment on lines +79 to 80
elsif message.includes?("Cannot establish connection")
raise PGNotRunningError.new(message)
Copy link
Member Author

Choose a reason for hiding this comment

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

I actually think this won't ever get here now... I'll have to double check, but I believe this is the same as above. It raises a DB::ConnectionRefused error with no message, and then in the stack trace there's a "Caused by..."

Copy link
Contributor

Choose a reason for hiding this comment

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

This whole exception logic could use a refactor. For example, matching on exception message text is probably not necessary at all if quiet? is set. And I feel like matching on exception message text isn't a good idea anyway because I wouldn't ever consider that a stable API -- if I were to raise an exception I expect the message to be read by a human.

Copy link
Member Author

Choose a reason for hiding this comment

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

Totally. This definitely needs a refactor. The main idea here is that the error coming form postgres is generally not too helpful. I find myself usually having to google why I'm getting "could not connect to template1" and other weird errors. So in this case, we catch those, and re-raise a new helpful error that gives you a list of things to look for as to why it failed.

I'll think about this a bit more and circle back.

message << " ▸ Check connection settings in 'config/database.cr'\n"
message << " ▸ Be sure the database exists (lucky db.create)\n"
message << " ▸ Check that you have access to connect to #{connection_details.host} on port #{connection_details.port || DEFAULT_PG_PORT}\n"
if connection_details.password.blank?
message << " ▸ You didn't supply a password, did you mean to?\n"
end

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, there's no reason it needs to be addressed in your pull here, it's out of scope.

@jwoertink
Copy link
Member Author

The ameba error will be fixed on the next release (crystal-ameba/ameba#372). But I need to look in to shard edge issue.

@jkthorne
Copy link
Contributor

jkthorne commented May 15, 2023

This dependency has caused me problems in the past. I ❤️ this change!

Copy link
Contributor

@robacarp robacarp left a comment

Choose a reason for hiding this comment

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

This is 👍 . I don't think it's unreasonable to depend on pg_dump and pg_restore -- I think those are more widely available with any postgres tools install. Whereas createdb and friends aren't as widely distributed scripts.

Comment on lines 8 to 15
def open : DB::Database
try_connection!
@db = try_connection!
end

def close
@db.try(&.close)
@db = nil
end
Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like a plausible explanation. That would solve the bulk of the use cases and the stuff that did generate errors would just get dismissed by restarting the container.

I think this is 👍

Comment on lines +85 to +86
def connection_string : String
String.build do |io|
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean?

src/avram/database.cr Outdated Show resolved Hide resolved
Comment on lines +79 to 80
elsif message.includes?("Cannot establish connection")
raise PGNotRunningError.new(message)
Copy link
Contributor

Choose a reason for hiding this comment

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

This whole exception logic could use a refactor. For example, matching on exception message text is probably not necessary at all if quiet? is set. And I feel like matching on exception message text isn't a good idea anyway because I wouldn't ever consider that a stable API -- if I were to raise an exception I expect the message to be read by a human.

@jwoertink
Copy link
Member Author

Alright, had to tell create and drop DB that it has to connect to some database... not sure why ONLY that one instance required it, but my guess is that you'll always have a DB named the same as your runner's name. If not, then we'd have to make that configurable as "maintenance_database_name" or something weird...

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.

Investigate removing pg client requirement
3 participants