-
Notifications
You must be signed in to change notification settings - Fork 419
Async FilesystemStore #3931
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
base: main
Are you sure you want to change the base?
Async FilesystemStore #3931
Conversation
👋 I see @tankyleo was un-assigned. |
29b8bcf
to
81ad668
Compare
let this = Arc::clone(&self.inner); | ||
|
||
Box::pin(async move { | ||
tokio::task::spawn_blocking(move || { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh, so I'm not sure if spawning blocking tasks for every IO call is the way to go (see for example https://docs.rs/tokio/latest/tokio/fs/index.html#tuning-your-file-io: "To get good performance with file IO on Tokio, it is recommended to batch your operations into as few spawn_blocking calls as possible."). Maybe there are other designs that we should at least consider before moving forward with this approach. For example, we could create a dedicated pool of longer-lived worker task(s) that process a queue?
If we use spawn_blocking
, can we give the user control over which runtime this exactly will be spawned on? Also, rather than just doing wrapping, should we be using tokio::fs
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh, so I'm not sure if spawning blocking tasks for every IO call is the way to go (see for example https://docs.rs/tokio/latest/tokio/fs/index.html#tuning-your-file-io: "To get good performance with file IO on Tokio, it is recommended to batch your operations into as few spawn_blocking calls as possible.").
If we should batch operations, I think the current approach is better than using tokio::fs? Because it already batches the various operations inside kvstoresync::write.
Further batching probably needs to happen at a higher level in LDK, and might be a bigger change. Not sure if that is worth it just for FIlesystemStore, especially when that store is not the preferred store for real world usage?
For example, we could create a dedicated pool of longer-lived worker task(s) that process a queue?
Isn't Tokio doing that already when a task is spawned?
If we use spawn_blocking, can we give the user control over which runtime this exactly will be spawned on? Also, rather than just doing wrapping, should we be using tokio::fs?
With tokio::fs, the current runtime is used. I'd think that that is then also sufficient if we spawn ourselves, without a need to specifiy which runtime exactly?
More generally, I think the main purpose of this PR is to show how an async kvstore could be implemented, and to have something for testing potentially. Additionally if there are users that really want to use this type of store in production, they could. But I don't think it is something to spend too much time on. A remote database is probably the more important target to design for.
lightning/src/util/persist.rs
Outdated
} | ||
|
||
/// Provides additional interface methods that are required for [`KVStore`]-to-[`KVStore`] | ||
/// data migration. | ||
pub trait MigratableKVStore: KVStore { | ||
pub trait MigratableKVStore: KVStoreSync { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will we solve this for an KVStore
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment belongs in #3905?
We might not need to solve it now, as long as we still require a sync implementation alongside an async one? If we support async-only kvstores, then we can create an async version of this trait?
81ad668
to
e462bce
Compare
Removed garbage collector, because we need to keep the last written version. |
97d6b3f
to
02dce94
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3931 +/- ##
========================================
Coverage 88.93% 88.94%
========================================
Files 174 174
Lines 123880 123991 +111
Branches 123880 123991 +111
========================================
+ Hits 110169 110279 +110
+ Misses 11260 11259 -1
- Partials 2451 2453 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
c061fcd
to
2492508
Compare
9938dfe
to
7d98528
Compare
38ab949
to
dd9e1b5
Compare
Updated code to not use an async wrapper, but conditionally expose the async I didn't yet update the |
🔔 1st Reminder Hey @tnull! This PR has been waiting for your review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach ACK I think, but I find it really concerning that we'd double our memory footprint while writing here. I don't think that is really acceptable if the async KVStore
is going to be our default. I think we'll need to find a solution before moving on.
This also needs a rebase now that #3799 landed.
lightning-persister/src/fs_store.rs
Outdated
let this = Arc::clone(&self.inner); | ||
|
||
// Obtain a version number to retain the call sequence. | ||
let version = self.version_counter.fetch_add(1, Ordering::SeqCst); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very, very unlikely, but should we still panic if we'd ever hit u64::MAX
to avoid we'd ever silently start skipping all writes? Also, I'm not sure we really need SeqCst
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relaxed
and added overflow panic.
let primary_namespace = primary_namespace.to_string(); | ||
let secondary_namespace = secondary_namespace.to_string(); | ||
let key = key.to_string(); | ||
let buf = buf.to_vec(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, all these reallocations are not great, in particular not for buf
, which we then give as a reference to write_version
. So we'll then hold the full encoded data in memory at least twice. If we don't find a way to deal with the references, I wonder if we should switch the async version of KVStore
to take owned primary_namespace
, secondary_namespace
, key
, buf
args.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem is that those args should then have static lifetime, because it isn't clear when they will be used by the runtime? Open to suggestions how to do that.
Agreed that copy is not ideal. Not sure if it actually matters if we also need to hit the disk with the same data.
👋 The first review has been submitted! Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer. |
dd9e1b5
to
05a4bb4
Compare
Rebased. Only diff is the counter overflow change |
Try out for an async store with eventually consistent writes. It is just using tokio's
spawn_blocking
, because that is whattokio::fs
would otherwise do as well. Usingtokio::fs
would make it complicated to reuse the sync code.ldk-node try out: lightningdevkit/ldk-node@main...joostjager:ldk-node:async-fsstore