Skip to content

Commit a42144d

Browse files
kris1319facebook-github-bot
authored andcommitted
wal multiplexedblob: implement is_present
Summary: Is_present on multiplexed blobstore: - return `Present` as soon as any of the underlying `is_present` successfully returns `Present` - return `Absent` when at least read-quorum blobstores returned `Absent` (we don't wait on the other `is_present`s) - return `ProbablyNotPresent` if some blobstores returned `Absent` (but not enough for quorum), the rest failed - fail if all of the blobstores failed. It is possible for multiplexed `is_present` to return inconsistent results if the previous put failed. For example, the put operation successfully logged itself to the WAL, but 2 out of 3 underlying puts failed. It means only 1 blobstore has the blob. Now multiplexed `is_present` tries to read from the blobstores: first 2 underlying `is_present` returned `Absent` -> quorum achieved, multiplexed returns `Absent`. But if first returns the blobstore that actually has the blob, multiplexed `is_present` will return `Present`. This is fine because the only guarantee we have: if the put succeeded, the blob will be readable from the storage. But in case like above, the blob will be either eventually replicated to all the blobstores by healer or will be rewritten by another put (because the first put failed). Reviewed By: farnz Differential Revision: D37342528 fbshipit-source-id: 21c62a9ecd2c2d117ee0b73d073f6ecd03f383c1
1 parent 4fd01ad commit a42144d

File tree

2 files changed

+302
-6
lines changed

2 files changed

+302
-6
lines changed

eden/mononoke/blobstore/multiplexedblob_wal/src/multiplex.rs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub enum ErrorKind {
4444
SomePutsFailed(Arc<BlobstoresReturnedError>),
4545
#[error("Failures on get in underlying single blobstores: {0:?}")]
4646
SomeGetsFailed(Arc<BlobstoresReturnedError>),
47+
#[error("Failures on is_present in underlying single blobstores: {0:?}")]
48+
SomeIsPresentsFailed(Arc<BlobstoresReturnedError>),
4749
}
4850

4951
#[derive(Clone, Debug)]
@@ -238,7 +240,7 @@ impl WalMultiplexedBlobstore {
238240
Ok(None) => {
239241
quorum = quorum.saturating_sub(1);
240242
if quorum == 0 {
241-
// quorum blobstores couldn't find the given key in the blobstoers
243+
// quorum blobstores couldn't find the given key in the blobstores
242244
// let's trust them
243245
return Ok(None);
244246
}
@@ -266,6 +268,61 @@ impl WalMultiplexedBlobstore {
266268

267269
Err(result_err.into())
268270
}
271+
272+
// TODO(aida): comprehensive lookup (D30839608)
273+
async fn is_present_impl<'a>(
274+
&'a self,
275+
ctx: &'a CoreContext,
276+
key: &'a str,
277+
) -> Result<BlobstoreIsPresent> {
278+
let mut futs = inner_multi_is_present(ctx, self.blobstores.clone(), key);
279+
280+
// Wait for the quorum successful "Not Found" reads before
281+
// returning Ok(None).
282+
let mut quorum: usize = self.quorum.read.get();
283+
let mut errors = HashMap::with_capacity(futs.len());
284+
while let Some(result) = futs.next().await {
285+
match result {
286+
(_, Ok(BlobstoreIsPresent::Present)) => {
287+
return Ok(BlobstoreIsPresent::Present);
288+
}
289+
(_, Ok(BlobstoreIsPresent::Absent)) => {
290+
quorum = quorum.saturating_sub(1);
291+
if quorum == 0 {
292+
// quorum blobstores couldn't find the given key in the blobstores
293+
// let's trust them
294+
return Ok(BlobstoreIsPresent::Absent);
295+
}
296+
}
297+
(bs_id, Ok(BlobstoreIsPresent::ProbablyNotPresent(err))) => {
298+
// Treat this like an error from the underlying blobstore.
299+
// In reality, this won't happen as multiplexed operates over sinle
300+
// standard blobstores, which always can answer if the blob is present.
301+
errors.insert(bs_id, err);
302+
}
303+
(bs_id, Err(err)) => {
304+
errors.insert(bs_id, err);
305+
}
306+
}
307+
}
308+
309+
// TODO(aida): read from write-mostly blobstores once in a while, but don't use
310+
// those in the quorum.
311+
312+
// At this point the multiplexed is_present either failed or cannot say for sure
313+
// if the blob is present:
314+
// - no blob was found, but some of the blobstore `is_present` calls failed
315+
// - there was no read quorum on "not found" result
316+
let errors = Arc::new(errors);
317+
if errors.len() == self.blobstores.len() {
318+
// all main reads failed -> is_present failed
319+
return Err(ErrorKind::AllFailed(errors).into());
320+
}
321+
322+
Ok(BlobstoreIsPresent::ProbablyNotPresent(
323+
ErrorKind::SomeIsPresentsFailed(errors).into(),
324+
))
325+
}
269326
}
270327

271328
#[async_trait]
@@ -280,10 +337,10 @@ impl Blobstore for WalMultiplexedBlobstore {
280337

281338
async fn is_present<'a>(
282339
&'a self,
283-
_ctx: &'a CoreContext,
284-
_key: &'a str,
340+
ctx: &'a CoreContext,
341+
key: &'a str,
285342
) -> Result<BlobstoreIsPresent> {
286-
unimplemented!();
343+
self.is_present_impl(ctx, key).await
287344
}
288345

289346
async fn put<'a>(
@@ -374,3 +431,18 @@ fn inner_multi_get<'a>(
374431
.collect();
375432
get_futs
376433
}
434+
435+
fn inner_multi_is_present<'a>(
436+
ctx: &'a CoreContext,
437+
blobstores: Arc<[(BlobstoreId, Arc<dyn BlobstorePutOps>)]>,
438+
key: &'a str,
439+
) -> FuturesUnordered<impl Future<Output = (BlobstoreId, Result<BlobstoreIsPresent, Error>)> + 'a> {
440+
let futs: FuturesUnordered<_> = blobstores
441+
.iter()
442+
.map(|(bs_id, bs)| {
443+
cloned!(bs_id, bs, ctx);
444+
async move { (bs_id, bs.is_present(&ctx, key).await) }
445+
})
446+
.collect();
447+
futs
448+
}

eden/mononoke/blobstore/multiplexedblob_wal/src/test.rs

Lines changed: 226 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
* GNU General Public License version 2.
66
*/
77

8+
use anyhow::anyhow;
89
use anyhow::Result;
910
use blobstore::Blobstore;
1011
use blobstore::BlobstoreGetData;
12+
use blobstore::BlobstoreIsPresent;
1113
use blobstore::BlobstorePutOps;
1214
use blobstore_sync_queue::BlobstoreWal;
1315
use blobstore_sync_queue::OperationKey;
@@ -558,8 +560,214 @@ async fn test_get_on_existing(fb: FacebookInit) -> Result<()> {
558560
Ok(())
559561
}
560562

561-
async fn assert_pending<T: PartialEq + Debug>(fut: &mut (impl Future<Output = T> + Unpin)) {
562-
assert_eq!(PollOnce::new(fut).await, Poll::Pending);
563+
#[fbinit::test]
564+
async fn test_is_present_missing(fb: FacebookInit) -> Result<()> {
565+
let ctx = CoreContext::test_mock(fb);
566+
567+
let (_tickable_queue, wal_queue) = setup_queue();
568+
let (tickable_blobstores, blobstores) = setup_blobstores(3);
569+
570+
let quorum = 2;
571+
let multiplex =
572+
WalMultiplexedBlobstore::new(MultiplexId::new(1), wal_queue, blobstores, vec![], quorum)?;
573+
574+
// No blobstores have the key
575+
let k = "k1";
576+
577+
// all `is_present` succeed, multiplexed returns `Absent`
578+
{
579+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
580+
assert_pending(&mut fut).await;
581+
582+
tickable_blobstores[0].1.tick(None);
583+
assert_pending(&mut fut).await;
584+
tickable_blobstores[1].1.tick(None);
585+
586+
// the read-quorum on `None` achieved, multiplexed returns `Absent`
587+
assert_is_present_ok(fut.await, BlobstoreIsPresent::Absent);
588+
tickable_blobstores[2].1.drain(1);
589+
}
590+
591+
// two `is_present`s succeed, multiplexed returns `Absent`
592+
{
593+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
594+
assert_pending(&mut fut).await;
595+
596+
tickable_blobstores[0].1.tick(None);
597+
tickable_blobstores[1].1.tick(Some("bs1 failed"));
598+
// muliplexed is_present waits on the third
599+
assert_pending(&mut fut).await;
600+
tickable_blobstores[2].1.tick(None);
601+
602+
// the read-quorum achieved, multiplexed returns `Absent`
603+
assert_is_present_ok(fut.await, BlobstoreIsPresent::Absent);
604+
}
605+
606+
// two `is_present`s fail, multiplexed returns `ProbablyNotPresent`
607+
{
608+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
609+
assert_pending(&mut fut).await;
610+
611+
tickable_blobstores[0].1.tick(Some("bs0 failed"));
612+
tickable_blobstores[1].1.tick(None);
613+
// muliplexed is_present waits on the third
614+
assert_pending(&mut fut).await;
615+
tickable_blobstores[2].1.tick(Some("bs2 failed"));
616+
617+
assert_is_present_ok(
618+
fut.await,
619+
BlobstoreIsPresent::ProbablyNotPresent(anyhow!("some failed!")),
620+
);
621+
}
622+
623+
// all `is_present`s fail, multiplexed fails
624+
{
625+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
626+
assert_pending(&mut fut).await;
627+
628+
for (id, store) in tickable_blobstores {
629+
store.tick(Some(format!("bs{} failed!", id).as_str()));
630+
}
631+
assert!(fut.await.is_err());
632+
}
633+
634+
Ok(())
635+
}
636+
637+
#[fbinit::test]
638+
async fn test_is_present_existing(fb: FacebookInit) -> Result<()> {
639+
let ctx = CoreContext::test_mock(fb);
640+
641+
let (tickable_queue, wal_queue) = setup_queue();
642+
let (tickable_blobstores, blobstores) = setup_blobstores(3);
643+
644+
let quorum = 2;
645+
let multiplex =
646+
WalMultiplexedBlobstore::new(MultiplexId::new(1), wal_queue, blobstores, vec![], quorum)?;
647+
648+
// Two blobstores have the key, one failed to write: [ ] [x] [ ]
649+
{
650+
let v = make_value("v1");
651+
let k = "k1";
652+
653+
let mut put_fut = multiplex
654+
.put(&ctx, k.to_owned(), v.clone())
655+
.map_err(|_| ())
656+
.boxed();
657+
assert_pending(&mut put_fut).await;
658+
659+
// wal queue write succeeds
660+
tickable_queue.tick(None);
661+
assert_pending(&mut put_fut).await;
662+
663+
tickable_blobstores[0].1.tick(None);
664+
tickable_blobstores[1].1.tick(Some("bs1 failed"));
665+
tickable_blobstores[2].1.tick(None);
666+
667+
// multiplexed put succeeds: write quorum achieved
668+
assert!(put_fut.await.is_ok());
669+
670+
// first `is_present` succeed with `Present`, multiplexed returns `Present`
671+
{
672+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
673+
assert_pending(&mut fut).await;
674+
675+
tickable_blobstores[0].1.tick(None);
676+
assert_is_present_ok(fut.await, BlobstoreIsPresent::Present);
677+
678+
for (_id, store) in &tickable_blobstores[1..] {
679+
store.drain(1);
680+
}
681+
}
682+
683+
// first `is_present` fails, second succeed with `Absent`, third returns `Present`
684+
// multiplexed returns `Present`
685+
{
686+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
687+
assert_pending(&mut fut).await;
688+
689+
tickable_blobstores[0].1.tick(Some("bs0 failed"));
690+
tickable_blobstores[1].1.tick(None);
691+
assert_pending(&mut fut).await;
692+
693+
tickable_blobstores[2].1.tick(None);
694+
assert_is_present_ok(fut.await, BlobstoreIsPresent::Present);
695+
}
696+
}
697+
698+
// Two blobstores failed to write, one succeeded: [x] [ ] [x]
699+
{
700+
let v = make_value("v2");
701+
let k = "k2";
702+
703+
let mut put_fut = multiplex
704+
.put(&ctx, k.to_owned(), v.clone())
705+
.map_err(|_| ())
706+
.boxed();
707+
assert_pending(&mut put_fut).await;
708+
709+
// wal queue write succeeds
710+
tickable_queue.tick(None);
711+
assert_pending(&mut put_fut).await;
712+
713+
tickable_blobstores[0].1.tick(Some("bs0 failed"));
714+
tickable_blobstores[1].1.tick(None);
715+
tickable_blobstores[2].1.tick(Some("bs2 failed"));
716+
717+
// multiplexed put failed: no write quorum
718+
assert!(put_fut.await.is_err());
719+
720+
// the first `is_present` to succeed returns `Present`, multiplexed returns `Present`
721+
{
722+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
723+
assert_pending(&mut fut).await;
724+
725+
tickable_blobstores[1].1.tick(None);
726+
assert_is_present_ok(fut.await, BlobstoreIsPresent::Present);
727+
728+
tickable_blobstores[0].1.drain(1);
729+
tickable_blobstores[2].1.drain(1);
730+
}
731+
732+
// if the first two `is_present` calls return `Absent`, multiplexed returns `Absent`
733+
{
734+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
735+
assert_pending(&mut fut).await;
736+
737+
tickable_blobstores[0].1.tick(None);
738+
tickable_blobstores[2].1.tick(None);
739+
740+
assert_is_present_ok(fut.await, BlobstoreIsPresent::Absent);
741+
tickable_blobstores[1].1.drain(1);
742+
}
743+
744+
// if one `is_present` returns `Absent`, another 2 fail, multiplexed is unsure
745+
{
746+
let mut fut = multiplex.is_present(&ctx, k).map_err(|_| ()).boxed();
747+
assert_pending(&mut fut).await;
748+
749+
tickable_blobstores[0].1.tick(None);
750+
for (id, store) in &tickable_blobstores[1..] {
751+
store.tick(Some(format!("bs{} failed", id).as_str()));
752+
}
753+
754+
assert_is_present_ok(
755+
fut.await,
756+
BlobstoreIsPresent::ProbablyNotPresent(anyhow!("some failed!")),
757+
);
758+
}
759+
}
760+
761+
Ok(())
762+
}
763+
764+
async fn assert_pending<T: Debug>(fut: &mut (impl Future<Output = T> + Unpin)) {
765+
match PollOnce::new(fut).await {
766+
Poll::Pending => {}
767+
state => {
768+
panic!("future must be pending, received: {:?}", state);
769+
}
770+
}
563771
}
564772

565773
type TickableBytes = Tickable<(BlobstoreBytes, u64)>;
@@ -601,3 +809,19 @@ fn validate_blob(
601809
assert_eq!(get_data.as_ref(), expected);
602810
}
603811
}
812+
813+
fn assert_is_present_ok(result: Result<BlobstoreIsPresent, ()>, expected: BlobstoreIsPresent) {
814+
assert!(result.is_ok());
815+
match (result.unwrap(), expected) {
816+
(BlobstoreIsPresent::Absent, BlobstoreIsPresent::Absent)
817+
| (BlobstoreIsPresent::Present, BlobstoreIsPresent::Present)
818+
| (BlobstoreIsPresent::ProbablyNotPresent(_), BlobstoreIsPresent::ProbablyNotPresent(_)) => {
819+
}
820+
(res, exp) => {
821+
panic!(
822+
"`is_present` call must return {:?}, received: {:?}",
823+
exp, res
824+
);
825+
}
826+
}
827+
}

0 commit comments

Comments
 (0)