Skip to content
This repository has been archived by the owner on Oct 23, 2022. It is now read-only.

Transactional sled pinstore #442

Merged
merged 18 commits into from
Jan 8, 2021
Merged

Conversation

koivunej
Copy link
Collaborator

@koivunej koivunej commented Jan 7, 2021

This enables the pinstore to use transactions for almost all operations. Big ticket TODOs remaining:

  • handling duplicate indirect pins (missing test)
  • handling pin combinations (missing test)
  • cidv0 compatible cidv1 normalization (missing test)
  • the str representation could probably be changed?
  • sled usage still needs to be wrapped in spawn_blocking

In smaller changes:

  • makes PinMode Copy
  • extracts a PinModeRequirement out of the Option<PinMode> which is hopefully more readable (list, query)

CC: @fetchadd, got the PR I promised made. Do you have any ideas? Sadly the changeset is probably quite difficult to review. I could split it up to have just the small fixes followed by the transactional stuff, but the smaller changes can probably by diffing only up until some commits.

Copy link
Contributor

@fetchadd fetchadd 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, transaction is better than Batch, as it makes operations like read then write atomic, the batch can only guarantee the a squence of write things to be atomic 👍 .

.. I find a pontential risk that a transaction can block another transaction

#[async_trait]
impl PinStore for KvDataStore {
async fn is_pinned(&self, block: &Cid) -> Result<bool, Error> {
is_pinned(self, block)
async fn is_pinned(&self, cid: &Cid) -> Result<bool, Error> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this is the only place where transaction is not needed, as sled's single-key operations are atomic :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

however, this looking like it can be just an atomic read makes me think it must be the wrong abstraction.. but if I recall correctly, this is called when removing a block through /api/v0/block/rm so ... I guess it needs to stay for now.

match already_pinned {
Some(PinMode::Recursive) => return Ok(false),
Some(mode @ PinMode::Direct) | Some(mode @ PinMode::Indirect) => {
// FIXME: this is probably another lapse in tests that both direct and
Copy link
Contributor

Choose a reason for hiding this comment

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

I have tried it in go-ipfs, yes, when a cid is pinned as indirect pin, after another recursive pin, the cid will be marked as recursive pin. For existed direct pin, I cannot get a cid marked as direct, but in "go-ipfs-pinner/dspinner/pin.go", there is
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Whoops, forgot to reply to this one. Yeah, this is a good point.

I've implemented (or at least tried to) the mem and fs so that the pins would not replace other pins, and I am surprised you found this in go-ipfs. I might also misremember, I did go back and forth here a lot. But I guess I'll need to confirm my thinking in upcoming PRs with more test cases, following the addition of spawn_blocking.


_ if cid_str_with_prefix.starts_with("i") => {
PinMode::Indirect
// FIXME: this is still blocking ... might not be a way without a channel
Copy link
Contributor

@fetchadd fetchadd Jan 8, 2021

Choose a reason for hiding this comment

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

  1. If the pin listing is mainly used for GC, there will not be many concurrent call sto it1 , so may be a new thread can be spawned by something like threadpool::spawn to make the listing non-blocking.
  2. I wonder why the PinStore and DataStore should be coupled, if it must be, can pin data be seperately putted in a alone sled tree. Then the filter of key that it should start with "pin." can be removed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

mainly used for GC, there will not be many concurrent call to it

Since it's available from the http api, I'd consider it beign callable at all times. For GC... I think the transactions might be helping out with it a lot. However, I am not too sure if we should attempt to build a similar GC than what go-ipfs has (not sure what is the present implementation) but I was thinking of trying to go through more of the route of maintaining a collectable set at all times through reference counting.

The reference counting would of probably require serializing all of the documents as graphs inside the blockstore, paving a way for a more monolithic store instead of trying to go with a number of different stores which are pluggable. I can't really see an design of such composed store which would be anywhere near consistent or durable.

so maybe a new thread can be spawned by something like threadpool::spawn

tokio already has spawn_blocking so I'd rather not entangle a new threadpool implementation here. I did not yet use the spawn_blocking as that'd mess up the indentation at least and make this PR even less reviewable. I'll do this in a follow-up.

I wonder why the PinStore and DataStore should be coupled

There is no particular reason, this was more of a "convenient" quick decision I made to enable hacking the memory and fs based stores together, getting the "ball rolling" so to speak. However, I am still thinking that perhaps we should just put everything of Repo behind a single monolithic sled/database implementation and not even try to view these as separate. Perhaps things like non-local storage of blocks (for example in a s3) should be viewed as extensions to a monolithic database backend rather than "interchangeable blocks". Abstracting over databases is really difficult either way.

pin data be separatedly put in a standalone sled tree. Then the filter of key that is should start with "pin." can be removed.

My thoughts exactly! Tried to keep a lid on the number of changes which already spiraled out of control so kept the current way :) I tried to write some more ideas in TODOs of the get_pin_key(cid: &Cid, mode: &Mode) -> String fn.


// as we might loop over an over on the tx we might need this
for id in ids.iter() {
// FIXME: this is blocking ...
Copy link
Contributor

Choose a reason for hiding this comment

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

The same, considering no may calls at the same time, a new thread may be used to spawn the call.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

RE: blocking in general. Doing a lot of blocking stuff will be detrimental to any runtimes (such as tokio) performance as futures simply should not block, and we cannot rely on the database being cached at any times (re: sled docs on the topic). So will be following-up with a spawn_blocking linked in the above comment.

@koivunej
Copy link
Collaborator Author

koivunej commented Jan 8, 2021

.. I find a pontential risk that a transaction can block another transaction

My understanding is that sled will take care of this given the transaction scripts are Fn which will be just reinvoked if they return a conflict. I am thinking they should be FnMut but didn't yet look how exactly are they called inside sled; FnMut would make it clear that the query can for example only allocate the vec once, and keep trying to fill it over and over again.

@fetchadd
Copy link
Contributor

fetchadd commented Jan 8, 2021

.. I find a pontential risk that a transaction can block another transaction

My understanding is that sled will take care of this given the transaction scripts are Fn which will be just reinvoked if they return a conflict. I am thinking they should be FnMut but didn't yet look how exactly are they called inside sled; FnMut would make it clear that the query can for example only allocate the vec once, and keep trying to fill it over and over again.

There is a example(sorry, this cannot be runned on the playground as there is a sled external crate), when a transaction is not over, anther transaction will be blocked. So like query method of PinStore, if the transaction takes up a long time, all other requests using the same sled db will be blocked, so the transaction scope should be as smaller as possible. I have no idea how to slove this, maybe the blocking is reasonable that the db must be locked for the atomic operations.

@koivunej koivunej marked this pull request as ready for review January 8, 2021 10:48
@koivunej
Copy link
Collaborator Author

koivunej commented Jan 8, 2021

Thanks for the example. Yeah, using sleeps or external waits inside would of course make it worse, similarly to blocking inside an async runtime. I had been expecting that sled would support multiple writers but perhaps not for transactions...? I should look at it more deeply.

I have no idea how to slove this, maybe the blocking is reasonable that the db must be locked for the atomic operations.

Yeah I am guessing it's just one of those things which doesn't need solving. However it'd be a good idea to have some sort of mechanism to catch the slowing down, though running these in a spawn_blocking will already apply some queuing to it which might be enough to sort of side-step this issue. I hear the 2021 will be the year of observability so hopefully we'll get some advances on that front :)

RE: the PR I assume this would be 👍 from you?

Copy link
Contributor

@fetchadd fetchadd left a comment

Choose a reason for hiding this comment

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

Good jobs! 👍 Yes, 2021 will be the year of observability hope

@koivunej
Copy link
Collaborator Author

koivunej commented Jan 8, 2021

great, thanks!

bors r+

@bors
Copy link
Contributor

bors bot commented Jan 8, 2021

Build succeeded:

@bors bors bot merged commit 5d9cae5 into rs-ipfs:master Jan 8, 2021
@koivunej koivunej deleted the sled_pinstore_changes branch January 12, 2021 10:06
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants