Skip to content

init db and table, check health #55

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

tankyleo
Copy link

@tankyleo tankyleo commented Aug 15, 2025

Soft-push, seems to be working !

for now check health is just used internally to test the database on startup.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Aug 15, 2025

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tankyleo tankyleo requested a review from tnull August 15, 2025 07:42
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Took a first look and left some higher-level comments. Have yet to review the actual database statements and operations.

@tankyleo
Copy link
Author

A possible todo I thought of: right now our TABLE_CHECK_STMT SELECT 1 FROM vss_db WHERE false merely checks whether a vss_db table exists in the database. We could have a deeper check of all the columns in the table etc...

pub async fn new(postgres_endpoint: &str, db_name: &str, init_db: bool) -> Result<Self, Error> {
if init_db {
tokio::time::timeout(
tokio::time::Duration::from_secs(3),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we extract the timeout value(s) into appropriately named consts?

Copy link
Author

Choose a reason for hiding this comment

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

Yes thank you !

Ok(())
}

async fn check_health(&self) -> Result<(), Error> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need this? I think we should be able to trust that the CREATE TABLE statement does the right thing if it doesn't error out?

Copy link
Author

Choose a reason for hiding this comment

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

This mostly useful if the user does not set --init-db; in that case we want to make sure we can establish a connection to the database as part of startup checks before entering the main loop ?

If this passes, then we can log something like "Connected to PostgreSQL backend" in main as you suggested.

std::process::exit(1);
}
let init_db = args.iter().any(|arg| arg == "--init-db");
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this an optional flag to begin with?

Copy link
Author

Choose a reason for hiding this comment

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

  • When the flag is not set, I want the startup sequence to abort if the database is not present.
  • When the flag is set, I want the startup sequence to create the database if it is not present.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, just wondering when we'd ever want the first case?

If the intention is that we can initiate a VSS server instance even if we don't have / can establish a connection to the Postgres backend (which I don't fully buy), then we'd need to take additional steps. AFAIU, Pool::build(), which we call ~just after the init_db block would just as well fail if we can't connect to the pool:

"The Pool will not be returned until it has established its configured minimum number of connections, or it times out." (https://docs.rs/bb8/0.9.0/bb8/struct.Builder.html#method.build)

Copy link
Author

@tankyleo tankyleo Aug 19, 2025

Choose a reason for hiding this comment

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

Right, just wondering when we'd ever want the first case?

If the user does not intend to initialize a new database (which is most cases), I don't think we should silently create a new database if we don't find the one we are looking for. Instead, I think we should complain loudly, and make sure the user actually intends to create a new one.

If the intention is that we can initiate a VSS server instance even if we don't have / can establish a connection to the Postgres backend (which I don't fully buy), then we'd need to take additional steps. AFAIU, Pool::build(), which we call ~just after the init_db block would just as well fail if we can't connect to the pool:

Actually Pool::build returns Ok(()) even if it cannot establish a connection to the Postgres backend - see #52 (comment) . On the main branch, we then enter the main loop even though we have established no connections to the Postgres backend.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the user does not intend to initialize a new database (which is most cases), I don't think we should silently create a new database if we don't find the one we are looking for. Instead, I think we should complain loudly, and make sure the user actually intends to create a new one.

Hmm, I don't know. A binary that needs a special flag to init just on first start is a bit weird tbh. I can't think of another daemon that works that way.

Actually Pool::build returns Ok(()) even if it cannot establish a connection to the Postgres backend - see #52 (comment) . On the main branch, we then enter the main loop even though we have established no connections to the Postgres backend.

Huh, this API is very unexpected then. You'd expect it to return a TimedOut error if the connection attempts failed, just as get does. Might even be good to add a comment there in our code.

Copy link
Author

@tankyleo tankyleo Aug 20, 2025

Choose a reason for hiding this comment

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

Huh, this API is very unexpected then. You'd expect it to return a TimedOut error if the connection attempts failed, just as get does. Might even be good to add a comment there in our code.

Sounds good will add the comment. Agreed this is unexpected.

Copy link
Author

Choose a reason for hiding this comment

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

Hmm, I don't know. A binary that needs a special flag to init just on first start is a bit weird tbh. I can't think of another daemon that works that way.

I agree it is weird. FWIW, CLN, LDK-node automatically generates a new wallet, but LND requires you to explicitly run lncli createwallet instead of lncli unlock (lncli unlock aborts startup if the wallet does not exist).

If we always create a new database if it does not exist, when a user misconfigures the DB connection string to an existing database, we'll create a new database and complete startup, when instead we should(?) have aborted startup. See #52 (comment)

cc @TheBlueMatt

Choose a reason for hiding this comment

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

I don't have a super strong feeling. I've seen it done both ways, though it is generally the case for binaries like this that they'll auto-create the table by default, I can see why that might be surprising.

.await
.unwrap(),
);
println!("Loaded postgres!");
Copy link
Contributor

Choose a reason for hiding this comment

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

If I read that message I'd be a bit confused what's meant exactly. Maybe "Connected to PostgreSQL backend with address .."?

Copy link
Author

Choose a reason for hiding this comment

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

Yes that's clearer I agree thank you

let rest_svc_listener =
TcpListener::bind(&addr).await.expect("Failed to bind listening port");
println!("Bound to {}", addr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe "Listening for incoming connections on .."

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Let me know when this is ready for another round of review.

@tankyleo
Copy link
Author

Let me know when this is ready for another round of review.

Thanks for the review @tnull I've responded to your comments above would be good to see your responses before I write more code :)

const DB_INIT_CMD: &str = "CREATE DATABASE";
const TABLE_CHECK_STMT: &str = "SELECT 1 FROM vss_db WHERE false";
const TABLE_INIT_STMT: &str = "
CREATE TABLE IF NOT EXISTS vss_db (

Choose a reason for hiding this comment

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

In addition to the main table itself, we really need a "config" table that at least just stores the version of the schema we're currently using. That way we can easily check that the schema in use is compatible with this version and, in the future, we can upgrade the schema as needed.

Copy link
Contributor

@tnull tnull Aug 21, 2025

Choose a reason for hiding this comment

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

I think in Postgres migrations are usually done by sequentially applying the corresponding XXX_YYY.sql files. I agree it's probably good to track what version was last applied in a separate schema_version table or similar.

However, this probably also highlights that it would make sense to move the SQL statements to a dedicated migrations.rs, and rename TABLE_INIT_STMT in accordance to the .sql file to const V0_CREATE_VSS_DB, essentially to prepare for the eventual V1_.., V2_, .. migrations to follow.

FWIW, we could even consider to read in the sql files on startup. If we don't want to do that, we could consider dropping the sql files and just have the migrations.rs to avoid the risk of having them get out-of-sync at some point.

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.

4 participants