Skip to content

Add Postgres database#863

Open
benthecarman wants to merge 2 commits intolightningdevkit:mainfrom
benthecarman:postgres
Open

Add Postgres database#863
benthecarman wants to merge 2 commits intolightningdevkit:mainfrom
benthecarman:postgres

Conversation

@benthecarman
Copy link
Copy Markdown
Contributor

Add a PostgresStore implementation behind the postgres feature flag, mirroring the existing SqliteStore. Uses tokio-postgres (async-native) with an internal tokio runtime for the sync KVStoreSync trait, following the VssStore pattern.

Also gates sqlite behind a feature flag, since most tests relied on this, we ended up needing to gate a lot of them behind the flag. Curious on thoughts on how to handle this best

Make rusqlite an optional dependency behind the new "sqlite"
feature, which is enabled by default. This allows users who
use an alternative storage backend to avoid compiling the
bundled SQLite C library.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@benthecarman benthecarman requested a review from tnull April 1, 2026 19:55
@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Apr 1, 2026

👋 Thanks for assigning @tankyleo 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.

Add a PostgresStore implementation behind the "postgres" feature
flag, mirroring the existing SqliteStore. Uses tokio-postgres
(async-native) with an internal tokio runtime for the sync
KVStoreSync trait, following the VssStore pattern.

Includes unit tests, integration tests (channel full cycle and
node restart), and a CI workflow that runs both against a
PostgreSQL service container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@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 high-level look.

Also gates sqlite behind a feature flag, since most tests relied on this, we ended up needing to gate a lot of them behind the flag. Curious on thoughts on how to handle this best

Not sure I'm following? Why do we want to feature-gate tests based on SQLite?

Also tagging @tankyleo as a secondary reviewer here as he has a lot of recent experience with Postgres on VSS Server.


steps:
- name: Checkout code
uses: actions/checkout@v3
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think there are newer action versions available? Cf #861.


// An internal runtime we use to avoid any deadlocks we could hit when waiting on async
// operations to finish from a sync context.
internal_runtime: Option<tokio::runtime::Runtime>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we could avoid using an internal runtime for this and rather just reuse crate::runtime::Runtime, if we wanted. Though, when upstreaming to LDK we'll need to rethink all of this anyways (cf. VssStore upstreaming PR).


// tokio::sync::Mutex (used for the DB client) contains UnsafeCell which opts out of
// RefUnwindSafe. std::sync::Mutex (used by SqliteStore) doesn't have this issue because
// it poisons on panic. This impl is needed for do_read_write_remove_list_persist which
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Okay, but then we should probably cfg(test) this, right?

.build()
.unwrap();

let connection_string = connection_string.to_string();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not take a String if we'd allocate anyways? It would at least avoid a re-allocation if users held a String already.

let kv_table_name = kv_table_name.unwrap_or(DEFAULT_KV_TABLE_NAME.to_string());

let (client, connection) =
tokio_postgres::connect(connection_string, NoTls).await.map_err(|e| {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We probably should support TLS from the getgo?

Also, we'll want users to pick non-default databases and create them if they don't exist yet. Note for this to work you'll first need to create a connection with the default, to then create the database you'll have in mind (see lightningdevkit/vss-server#55 and follow-ups for reference).


// Spawn the connection task so it runs in the background.
tokio::spawn(async move {
if let Err(e) = connection.await {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How will we recover from connection errors? Should this somehow loop or similar?

) -> io::Result<()> {
check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write")?;

self.execute_locked_write(inner_lock_ref, locking_key, version, async move || {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Super annoying that we have that replicated three times (at least) by now. We should look to DRY this up, when upstreaming to lightning-persister at the very latest.

) -> io::Result<Vec<u8>> {
check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "read")?;

let locked_client = self.client.lock().await;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If all operations take a MutexGuard and await the operation anyways, do we really need the write-lock-versioning? Isn't this practically sync?


/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
#[cfg(feature = "sqlite")]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we suddenly introduce features? We so far held off and meant to do that in a dedicated PR at some point for this or next release. Is it necessary to make Postgres work?

@tnull tnull requested a review from tankyleo April 2, 2026 11:10
@benthecarman benthecarman self-assigned this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants