From 082ac36ac195b5fd503422db7152c2d6efbd857a Mon Sep 17 00:00:00 2001 From: Roman Gershman Date: Tue, 20 Sep 2022 10:57:41 -0700 Subject: [PATCH] fix(bug): dashtable split crashes when moving items from the old segment (#318) fix(bug): dashtable split crashes when moving items from the old segment. Segment's Insert function uses an opportunistic heuristic that chose a bucket with smaller items among two available. This creates a problem with split that goes from a smaller bucket to the biggest one and moves items to the new segment. In rare case, the heurstic fills up a the next bucket with items that could reside in earlier buckets and then that bucket does not have space for its own items. Eventually, items that had enough space in the old segment do not find space in the new one The fix is to adopt a conservative approach during split and try to use a home bucket first. Home bucket is usually a smaller one. This approach should be optimal because Split starts with smaller buckets first (can be proven iteratively). Signed-off-by: Roman Gershman Signed-off-by: Roman Gershman --- src/core/CMakeLists.txt | 4 +- src/core/dash_internal.h | 35 +- src/core/dash_test.cc | 28 ++ src/core/testdata/ids.txt | 746 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 803 insertions(+), 10 deletions(-) create mode 100644 src/core/testdata/ids.txt diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b28973d..3daa484 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -11,7 +11,7 @@ cxx_test(dfly_core_test dfly_core LABELS DFLY) cxx_test(compact_object_test dfly_core LABELS DFLY) cxx_test(extent_tree_test dfly_core LABELS DFLY) cxx_test(external_alloc_test dfly_core LABELS DFLY) -cxx_test(dash_test dfly_core LABELS DFLY) +cxx_test(dash_test dfly_core file DATA testdata/ids.txt LABELS DFLY) cxx_test(interpreter_test dfly_core LABELS DFLY) cxx_test(json_test dfly_core TRDP::jsoncons LABELS DFLY) -cxx_test(string_set_test dfly_core LABELS DFLY) \ No newline at end of file +cxx_test(string_set_test dfly_core LABELS DFLY) diff --git a/src/core/dash_internal.h b/src/core/dash_internal.h index 7aff1dc..96db5d5 100644 --- a/src/core/dash_internal.h +++ b/src/core/dash_internal.h @@ -492,7 +492,12 @@ template Iterator InsertUniq(U&& key, V&& value, Hash_t key_hash); + // if spread is true, tries to spread the load between neighbour and home buckets, + // otherwise chooses home bucket first. + // TODO: I am actually not sure if spread optimization is helpful. Worth checking + // whether we get higher occupancy rates when using it. + template + Iterator InsertUniq(U&& key, V&& value, Hash_t key_hash, bool spread); // capture version change in case of insert. // Returns ids of buckets whose version would cross ver_threshold upon insertion of key_hash @@ -526,7 +531,8 @@ template Iterator BumpUp(uint8_t bid, SlotId slot, Hash_t key_hash, const BumpPolicy& ev); + template + Iterator BumpUp(uint8_t bid, SlotId slot, Hash_t key_hash, const BumpPolicy& ev); // Tries to move stash entries back to their normal buckets (exact or neighbour). // Returns number of entries that succeeded to unload. @@ -1048,7 +1054,7 @@ auto Segment::Insert(U&& key, V&& value, Hash_t key_hash, Pr return std::make_pair(it, false); /* duplicate insert*/ } - it = InsertUniq(std::forward(key), std::forward(value), key_hash); + it = InsertUniq(std::forward(key), std::forward(value), key_hash, true); return std::make_pair(it, it.found()); } @@ -1181,8 +1187,13 @@ void Segment::Split(HFunc&& hfn, Segment* dest_right) { invalid_mask |= (1u << slot); auto it = dest_right->InsertUniq(std::forward(key), - std::forward(Value(i, slot)), hash); + std::forward(Value(i, slot)), hash, false); + + // we move items residing in a regular bucket to a new segment. + // I do not see a reason why it might overflow to a stash bucket. + assert(it.index < kNumBuckets); (void)it; + if constexpr (USE_VERSION) { // Maintaining consistent versioning. uint64_t ver = bucket_[i].GetVersion(); @@ -1218,7 +1229,7 @@ void Segment::Split(HFunc&& hfn, Segment* dest_right) { invalid_mask |= (1u << slot); auto it = dest_right->InsertUniq(std::forward(Key(bid, slot)), - std::forward(Value(bid, slot)), hash); + std::forward(Value(bid, slot)), hash, false); (void)it; assert(it.index != kNanBid); @@ -1283,7 +1294,8 @@ bool Segment::CheckIfMovesToOther(bool own_items, unsigned f template template -auto Segment::InsertUniq(U&& key, V&& value, Hash_t key_hash) -> Iterator { +auto Segment::InsertUniq(U&& key, V&& value, Hash_t key_hash, bool spread) + -> Iterator { const uint8_t bid = BucketIndex(key_hash); const uint8_t nid = NextBid(bid); @@ -1295,7 +1307,7 @@ auto Segment::InsertUniq(U&& key, V&& value, Hash_t key_hash unsigned ts = target.Size(), ns = neighbor.Size(); bool probe = false; - if (ts > ns) { + if (spread && ts > ns) { insert_first = &neighbor; probe = true; } @@ -1305,6 +1317,12 @@ auto Segment::InsertUniq(U&& key, V&& value, Hash_t key_hash insert_first->Insert(slot, std::forward(key), std::forward(value), meta_hash, probe); return Iterator{uint8_t(insert_first - bucket_), uint8_t(slot)}; + } else if (!spread) { + int slot = neighbor.FindEmptySlot(); + if (slot >= 0) { + neighbor.Insert(slot, std::forward(key), std::forward(value), meta_hash, true); + return Iterator{nid, uint8_t(slot)}; + } } int displace_index = MoveToOther(true, nid, NextBid(nid)); @@ -1551,7 +1569,8 @@ auto Segment::FindValidStartingFrom(unsigned bid, unsigned s template template -auto Segment::BumpUp(uint8_t bid, SlotId slot, Hash_t key_hash, const BumpPolicy& bp) -> Iterator { +auto Segment::BumpUp(uint8_t bid, SlotId slot, Hash_t key_hash, + const BumpPolicy& bp) -> Iterator { auto& from = bucket_[bid]; uint8_t target_bid = BucketIndex(key_hash); diff --git a/src/core/dash_test.cc b/src/core/dash_test.cc index fb9542e..07c865b 100644 --- a/src/core/dash_test.cc +++ b/src/core/dash_test.cc @@ -16,6 +16,8 @@ #include "base/hash.h" #include "base/logging.h" #include "base/zipf_gen.h" +#include "io/file.h" +#include "io/line_reader.h" extern "C" { #include "redis/dict.h" @@ -796,6 +798,32 @@ TEST_F(DashTest, Sds) { // dt.Insert(std::string_view{"bar"}, 1); } +struct BlankPolicy : public BasicDashPolicy { + static uint64_t HashFn(uint64_t v) { + return v; + } +}; + + +// The bug was that for very rare cases when during segment splitting we move all the items +// into a new segment, not every item finds a place. +TEST_F(DashTest, SplitBug) { + DashTable table; + + io::ReadonlyFileOrError fl_err = + io::OpenRead(base::ProgramRunfile("testdata/ids.txt"), io::ReadonlyFile::Options{}); + CHECK(fl_err); + io::FileSource fs(std::move(*fl_err)); + io::LineReader lr(&fs, DO_NOT_TAKE_OWNERSHIP); + string_view line; + uint64_t val; + while (lr.Next(&line)) { + CHECK(absl::SimpleHexAtoi(line, &val)); + table.Insert(val, 0); + } + EXPECT_EQ(746, table.size()); +} + /** ______ _ _ _ _______ _ | ____| (_) | | (_) |__ __| | | diff --git a/src/core/testdata/ids.txt b/src/core/testdata/ids.txt new file mode 100644 index 0000000..156e175 --- /dev/null +++ b/src/core/testdata/ids.txt @@ -0,0 +1,746 @@ +a2e24b073cf57fa4 +a2f1bbc7fc9600fb +a2f3684310f1e895 +a2e8d1df84366075 +a2e997d4134f4046 +a2f9824557d420ba +a2e2d9d90b3b1841 +a2f6f8031af068b8 +a2ea89709696b08f +a2e7bf0f03267014 +a2e3d46c5893efbb +a2e1c01c5e68c7dd +a2eaea6601b33fb0 +a2e250ca959bc7f4 +a2eb5e11044768fa +a2e3731bc400185c +a2f82888c8f868e2 +a2eb9bea7247c808 +a2f97d0065a380fd +a2f6940fd277d8f8 +a2e638b5fd214061 +a2f3b77dd4fdd85b +a2f61c0bb34320c4 +a2e6aa99dfee1854 +a2e9e267998b81e0 +a2e7d6c50702811b +a2eacadee3e49971 +a2f71490c5881072 +a2eba6f3148c8926 +a2eeb82230b8a96c +a2fa11351697219a +a2e470f5bdb82146 +a2e989a0a3add110 +a2f4e47496465138 +a2fbf81d9eb3a12c +a2fb473cb87281f6 +a2fac75352db492c +a2eecc5dd94671f1 +a2e977499664a15d +a2ea0c28000911e2 +a2f5fefbfa752226 +a2fb862ad9362abf +a2e0a3a0629953fe +a2ebfbaa189813ac +a2f40ead50f6aad5 +a2ffc642cc7e42d5 +a2e7a35701176a31 +a2f82c2ca98972e6 +a2e1209b19f0da73 +a2fb1a51b2cba2d9 +a2fa434b183cbaef +a2e5bffa40a61a4f +a2e80f94a2033249 +a2f768f7f5af23d3 +a2ef76f25e1093df +a2e97b60a53f7bdb +a2f7ec84b9611387 +a2f0f0a971a20be1 +a2e3b72061eba3c1 +a2e0e46833620bca +a2ee9ee8643b0bbd +a2f2e57deca70bb7 +a2e56aa5a32fa3b3 +a2e3337430edeb97 +a2e8942657a43b5d +a2e10aadbef4c3f6 +a2f7d759e4d30342 +a2eb04df10f92b42 +a2e17f2873509b6a +a2fc6e0b74e7835d +a2ea17280ed7f4e6 +a2e3b163ddaf946d +a2e6bdef56ddb400 +a2fdeabc7dd94c82 +a2ec11e354bf7c34 +a2efb83055c41405 +a2f26430a428e409 +a2f56566dbd2acbe +a2f21394b7be6c47 +a2e18c40653fcc3a +a2f06a8ff1219473 +a2f7e80c04eea4f9 +a2f980638e0a4456 +a2e57ee2be853440 +a2e984e074002dee +a2f88db3a2c0dd3e +a2fc31867ca29d4b +a2f36b82e35cbd66 +a2e11da0dbf24daf +a2e3145034660591 +a2f7f1db168655d8 +a2ea4d022ac46594 +a2f4f617d700de11 +a2e813b0d2542685 +a2ebf14ecc74bef1 +a2fa3e1591df7e26 +a2f49650186be648 +a2ee03aeb6d32eaf +a2fd7506c5f2865e +a2f204c1f965e6e4 +a2ffd3f0ac6bdea3 +a2f4bd6745756e66 +a2f79471989e360b +a2eec66780ad9655 +a2eb39c6c7134699 +a2f482d83d8596bb +a2eca4aa0f2a0efc +a2f7c2719d8e4e47 +a2fd49654604ee3c +a2e98788ed00ae99 +a2efb147b11097e8 +a2efe633d1a6f700 +a2ebbde450f1cf39 +a2f5b5b98a374742 +a2f2af0b92d60f3b +a2fd96138c4b8785 +a2e580f21c661f51 +a2e78eecf23ccf14 +a2fe52c33d66a71e +a2f934e1f2160f19 +a2fcf00ca8c5c7fb +a2fecfe355d0ff3f +a2f1f20d7a248f61 +a2fde1f40d1557ad +a2eaa243acac2064 +a2e1a3390cd0e847 +a2e1b4444a3e9075 +a2f8e0f41a8cd8e6 +a2ecbe972c3fd818 +a2eae4fe970398c0 +a2f95816742380c1 +a2e4b575972a9031 +a2eb15f66232b0f7 +a2e3a74c018398ac +a2eab2edf417d048 +a2e0440a262100cd +a2e7a4be7e0eb120 +a2f1c1a1a6ba79ea +a2e02a14348e412d +a2e646e5f49641a4 +a2fe6a1116d7490f +a2eccebbc172d926 +a2ef35ccb31659b6 +a2f6a08a2a8169ee +a2ebed8c7ba6f9fe +a2fc1bda0dcd9110 +a2fbf857cf11ba0e +a2fc69f892bd32b5 +a2e64615716f2296 +a2e015b79319ca99 +a2f7ead8ef9a5b24 +a2ea0807e5433370 +a2e4bba27feada4e +a2faa731689512a9 +a2e5c057e3a54a66 +a2eebeb3f91db2e1 +a2ed34fb7842d2a3 +a2fd5cb3baf74a09 +a2e68b11e41c129f +a2edfcc93f47aad1 +a2e4bc94b914aaf9 +a2ffed576e3de3af +a2f427529e103b56 +a2f730db92d69ba5 +a2fa74d0015873df +a2f651345a345b20 +a2ff3f79b100f360 +a2fff6c8cd44239b +a2f38e5e2b8e5ba4 +a2ea7cf369cb23fd +a2e09ca087e16b0a +a2f92a7f9076b32b +a2f740c5c746a3ac +a2e79530d5b8732f +a2e96e0f5f6593f4 +a2fd47a4b7d9c3b9 +a2f496d67891db58 +a2e4e801c29b7b3d +a2f2c7a0e276841b +a2fa105c331874e9 +a2f16f1aeedb347c +a2fa7762c5da2c2e +a2e671a0656e0cef +a2f3a6aac8066ce0 +a2fae23544e16cca +a2f98c94bcdac46f +a2e8c7054839ec68 +a2e15d7ed7be94ba +a2f60cd929b9fcc9 +a2fc5b87f07e7cb6 +a2f9d6ca31d22c01 +a2f0a2b020297cf7 +a2fc5fad51156d32 +a2f131243676452a +a2e96bb1b43eed98 +a2f25886d05715d0 +a2e543f6079035ce +a2e93a4f4e92bdc1 +a2fc3e6a34fa65bf +a2f5ef7570c03d56 +a2e96d854c086e09 +a2f5913bc1c2d6d0 +a2fb4c3bf5fc1e8a +a2f72380e8706e44 +a2f9670b15094e28 +a2f1ac1ce0a0fefb +a2e8a1050a5ccf7f +a2ed52457a46d6ab +a2fc970329f2f689 +a2e45d6cceced660 +a2ff5adb32a1b6ed +a2e106993f46c653 +a2fc27b50449aec2 +a2e4d60ff6f0ae72 +a2fd82c1befd06a9 +a2ec1038472e6e87 +a2e539611677de98 +a2eab474e7b85e55 +a2fd37c0aa996e2b +a2ea68b22962cf99 +a2f81a14b7870719 +a2ea048a126e4714 +a2ec0ba843a1efe8 +a2e61c86ef796791 +a2e34e57600227b2 +a2f6f7269c8e1f02 +a2e3d5f020635723 +a2f35a421d587f7c +a2e5ab56cfab67e7 +a2ec0c3570d0bf8d +a2fcddf06fd84fa0 +a2f868cbcaa8afd0 +a2e562ef52980fcb +a2f43e70872b2f08 +a2e3607908c3c0b1 +a2fdc1f4f8eae874 +a2f19564a576486a +a2e2607351536083 +a2edf68234e71899 +a2fb8e1d0a7f28f8 +a2f19a5f9f01d0a5 +a2e5b29bf006d832 +a2ef5edd44c788f4 +a2e43d4ba691784a +a2ea47046f8b0035 +a2f3d6d5ab0b303a +a2e32021129a79ab +a2e55b853fe259a4 +a2e9025193fe99ef +a2fc1f0dafbdc977 +a2e3b2e42592e101 +a2f8cdd05a1e5910 +a2e145c888ad41c0 +a2e08d7bc2bfc189 +a2ee8ecf6cb2d9e9 +a2e88dfb118b41a4 +a2f90d672b30e9fd +a2fb9c54a6a2811f +a2f42048ca7e0943 +a2f920472784018a +a2ecbf270467a911 +a2ff7d158d3bd916 +a2e531b5dcd3c2db +a2e80c2217186a30 +a2f523fbc87f5a59 +a2f45dd69efccaf0 +a2fccf99ff9f8200 +a2e855addee38af0 +a2f0b87b8efe7a6c +a2e1a3eb966b4299 +a2e1dcaa3a1cf231 +a2fa3a9b65f5cb98 +a2f5c40068b92bec +a2ea24325612d3d4 +a2e2327bb939039c +a2ff8501a9d03bda +a2f4a313bd5b24ab +a2e2ff1f75504cf7 +a2f9dc1107c69c48 +a2ed595d533e43c3 +a2faef811a758bd6 +a2f69492ea01038f +a2f41c3e98f313f1 +a2f0e00b07e65be4 +a2ed89b583ab0b32 +a2f86bcdc7c9eb83 +a2f8a6b519acb440 +a2fd1e6e95e4448a +a2f165ddd67814b7 +a2e3523130e06456 +a2e7d3f553497c9d +a2e13755c8070c96 +a2ea482faa39f467 +a2f7cb9d108444ee +a2e589c995a6f4cb +a2e85bbfd86d9401 +a2fde01274d2eca5 +a2f4c2edf89334a0 +a2e1ba5a2c364c8b +a2fa41568fb03478 +a2e515a2640fc405 +a2e7e2d9d21a0c43 +a2e0f3acc8da3c1a +a2e43723e42514d0 +a2e0d7fd9ba4ed4d +a2e4664fbf593df8 +a2e0027228701562 +a2e7126e07f93575 +a2e9de5bbb73cd3c +a2e2c7c68528654c +a2e00bfe0b60256b +a2f30bda09e8b524 +a2eb00bd3ae44511 +a2e487266e86fd0c +a2fb2f78c0ccadbc +a2ec864b10ec3df7 +a2e7dcee08e4ddd4 +a2f72fbb724f8538 +a2fece0d3926ae95 +a2f0ea55bf1a1645 +a2ea0263eadda653 +a2e80f62267a2e8e +a2f2fca72a6a9689 +a2f57e2111a976f1 +a2ee6ca220cf3693 +a2f435af0ba3de97 +a2eadbdcb61f3ec1 +a2f6c0e3ac48fe3a +a2f3014a228a0667 +a2f456492c3dc684 +a2f8aa8a22852ff3 +a2e9034ae041efac +a2fc35459b259758 +a2f1baa15c21a782 +a2f4631ef86ff7d1 +a2f4df5933549fcc +a2f3dc38be01ef25 +a2f94402530b6f4e +a2f8c1870e9597c8 +a2e6e629b35d376b +a2eff51434865f92 +a2ed2a74101ba7d2 +a2ebbdddbdcae702 +a2e1e88d9c08e787 +a2f107a097368fba +a2eda512bcf7d894 +a2e1f0e47c3b4074 +a2e1553b93c7b885 +a2f7408afa91089c +a2ef469573a40853 +a2f7e5ec92e83062 +a2fa525ff419f8da +a2ec5c2270bf8074 +a2f969dce46d8045 +a2ff058fa1c7f849 +a2e8dc2b0d7e884e +a2ef79f22ef23019 +a2ecac4e581d60bb +a2f8e4a4022998a4 +a2fb2d9a2a5bd81d +a2fbe59bbc46a120 +a2f94433f99449c5 +a2e4297a2c4a8909 +a2e415d9352729a8 +a2e579775dd439b6 +a2f71a3d5e76316e +a2e26083c03c397a +a2f4cb685617a98d +a2e8fcef3a8dd1f9 +a2fe5f1c4712d937 +a2eca0363d4a39bd +a2f186267d5dc984 +a2ff0423b71991e1 +a2efe05cf9d1f131 +a2ee979e3aa52231 +a2e4c672c0e34274 +a2efe2faef5cf200 +a2f3bc6b0df0ba11 +a2e7c24e8760dac8 +a2f6bdad16b28262 +a2e674493fba929f +a2f2ce2c99bd8a4f +a2efaa90ba8c4287 +a2eb910070495a97 +a2e7fb0711781a98 +a2ee6be18e269a42 +a2f26a2c852ee21a +a2f802d191914351 +a2fa54fdc2686bb3 +a2e8913c8bbebb87 +a2e4afaa1150f352 +a2eb56f6e0a08b44 +a2e03f1db8be7337 +a2ed438edc0b6b82 +a2ec68aef8e3e369 +a2f76b2836063b39 +a2f578df074c8bca +a2e83b255b849328 +a2ebf2e778d64bdd +a2ec3b482f92e393 +a2e6afc0027f3bfc +a2e9cd831a8013f4 +a2f9873c40bbbcc9 +a2fa4fc1ae568404 +a2e61034c6b114a4 +a2e2065358ba044b +a2e8c22cca77b4b1 +a2fa7189e2746c31 +a2fdcebc58381497 +a2f33d2fda05eceb +a2f8b8c078cb5cbf +a2ecab059aa2347b +a2fd442eeb969c80 +a2e7ce6a12311402 +a2fb2404576cdc89 +a2eace4903dd7c17 +a2fd5f4cd5ea1521 +a2fe4911fb419d5c +a2f9d11e5e7fad89 +a2e0b0550e5dfd2a +a2fd846ae0aa156d +a2ff72ef4a7005f4 +a2f7f2b3d0c48ded +a2f1b5835d3dddca +a2e2970132afe5c1 +a2e12b9c27b08d91 +a2ee684d66e535a6 +a2e58fd0f23c05cb +a2e031c1e4990d94 +a2f0869cf30c959b +a2fc5a64bf093679 +a2f030984c283fc1 +a2f42898c017e6cd +a2f1932dd7c7dec6 +a2ff96e30982c7cd +a2ef5af4547ca729 +a2ec87368d722722 +a2ed0dc40d605fec +a2e3dd2c63775f11 +a2eebf0fee6bdfc8 +a2e2ff041ddebfde +a2f7b72cbb5d4f44 +a2fd9e42dc771f2b +a2ece2265d312fe5 +a2fae195ad844736 +a2f174b077754749 +a2f9542f8c9a5784 +a2f1e2bc7f65575f +a2e4e759a180f774 +a2fc70604ef1cfe7 +a2e35115bb5a886d +a2e2b6ab20e6d8bd +a2e91359c45d088c +a2f2a5f85217e0e0 +a2fa6991423a4893 +a2ef22e1e2f73011 +a2f019d8933b38ec +a2f9bb484145b054 +a2e06255e5ccf0e8 +a2eb1383233f6048 +a2e6d8ace3c5483b +a2f8972dfe69f042 +a2eebb9f5938f894 +a2f96d8f87222026 +a2ffa5c12fce203a +a2f4c27944b218e0 +a2ff938ed21b3047 +a2f9047fed849919 +a2f95ce7dbd6d9e8 +a2ece4b2d08f79fe +a2f6b5de30112120 +a2ef3324936dc98f +a2e717388ed341e0 +a2f834ec000ac1e0 +a2f699be3750e9b1 +a2fee4c5061101fd +a2facc94153d511f +a2e8f3c15325d992 +a2ec154faba169a1 +a2fab6a3a4dff984 +a2e3536f5690f996 +a2fbce3edd31aaa3 +a2e0122ce0ff7b9b +a2e7c537d287328a +a2f58fb54541e2cc +a2e908a6c2b13a6f +a2ede01793f24295 +a2fea0b788c2caa7 +a2ffb055b95242f5 +a2f775e2f4416a37 +a2e6313b69adfadf +a2e04be7755de21d +a2fc95535c8622e1 +a2f46ea056e55a92 +a2e9abc1587f6aa9 +a2fffc5fd802eb46 +a2f9b1215f64eb5d +a2faf28cbe4ab3e6 +a2fe2b264e78ab7a +a2e26eb66626b38a +a2f774f93215d3b2 +a2e237623ca493f1 +a2eb0bbe1020e319 +a2fa646b2af40b7d +a2eac3bc22e36bf7 +a2fb798535223b15 +a2e90c9a4841131c +a2e5ca364fe35b3e +a2e9753a16f74bc7 +a2f612bdfe0a23d4 +a2ecc754d4eaacbf +a2f5eb4d957094db +a2e134fbfb7edc97 +a2f9cfb69e316c24 +a2eaa319c2b7345f +a2e5533632101cf5 +a2e987e1f0dc84fc +a2e5e7124e0a5448 +a2f19fc570147c2a +a2eb17b5b547e49b +a2eb881a6dd814b2 +a2e09686c9c40c62 +a2e78eaade894dd6 +a2ffb38fe47bfd12 +a2fa4f2dd18645da +a2e18717b6a89d50 +a2ee3562a5f53d99 +a2e3f9d35da5f524 +a2f35c32cb437521 +a2ea91299c794590 +a2ed2e26d510058e +a2e776d236bfc501 +a2e5e5f1ec6bed7b +a2ffb6c598b215d7 +a2e16b794cc9b5e5 +a2f29186cb564518 +a2e9961dd9902d70 +a2f91d28d7382649 +a2f9b66914dd86eb +a2f1ce555134a674 +a2f662021c24169d +a2fbf4d80a744e77 +a2e9c9950d9dbedd +a2e3a505af411e17 +a2ecd74fab909e9d +a2edd8044f8ee654 +a2e1f48cd7ebe62a +a2fbf3b75e4f2e8b +a2eaf54355e1b6a1 +a2e3f09a2d49a635 +a2f0730319f7c606 +a2ee3ba45f700f82 +a2fb711057106f26 +a2ee1e5a21945794 +a2e5989a1a71ef14 +a2e4ffd3add27703 +a2f61e002b01efb1 +a2ed683b30746ff8 +a2e676a6a7e6ff55 +a2fa5b1fcbe2c74f +a2f0f876ae8a689c +a2f74cde1518b0f0 +a2e38eeb11e388e1 +a2f76f98b321d8aa +a2f20d2d69a1580a +a2fb088ad7c9108e +a2e591dfbc8cd072 +a2f50c4402a5e82f +a2e370bad3e14893 +a2e02ca82aed382d +a2f19934437d10b6 +a2eede4c2278581e +a2e87e59b63ee971 +a2e19ed90ff4816b +a2efede047d3e183 +a2ed2b64d2c589b1 +a2fdd8acb1eae12e +a2f59ea35abfe971 +a2e3a78353af312f +a2ee35a132b63956 +a2f04c5c1f5111b5 +a2ef1e30adeae997 +a2e6cd371d37e9c7 +a2e16f13387ba164 +a2fe66ea7dfbe936 +a2e8201dda5d1afe +a2e048ceb41daa84 +a2ee5cd3bc21ba4d +a2fc2cdd2e852a17 +a2e5516e6d87da50 +a2f8fe9259c48277 +a2e3e0faf4882246 +a2f84a382d584a21 +a2f8991bfdf2da1f +a2e8d34bd3fd4291 +a2e037dfa0a4dae0 +a2f3c751bda4b241 +a2f7325c0ade229a +a2fd1b1e2bbd825b +a2f5b4084e90e274 +a2e3945f970212fc +a2ec82618c199a76 +a2e59ce100eea3c9 +a2f807a98fb54b12 +a2f42b4b974b5b53 +a2ec0481f8894bd5 +a2e3ad44ca667b7d +a2e49b7b786cf3b5 +a2f525d85ebe43b4 +a2fff5ee055873e8 +a2e9fc0f87a14bc2 +a2e67a4e79915b1a +a2f88adbd6728337 +a2e188b762e75411 +a2fdb8f06761acb9 +a2f18e4f1b0954b0 +a2f479aaeca8e4ce +a2f6c153af074468 +a2e9b53491d1f467 +a2e83fe2e24bccdc +a2efffb234feb4e3 +a2ee2582f5e3ec6d +a2f3404ee195ac08 +a2f1d86c0c1d6ce4 +a2ff5d7f552ce447 +a2e3f24086c82cd6 +a2fe5944422394bc +a2fb1d654bc53475 +a2e3e2843eae5c4d +a2e0e32d11bb64ce +a2e25acae5c87510 +a2fcdaa5ede615a6 +a2fc0fedd0c3bda4 +a2e4c42cb23ea593 +a2ee65c55801ad2a +a2efca5b7a11bd9e +a2e89ff043ea1576 +a2f35be56eb1a566 +a2ef4588e1c63d39 +a2f292df378a257c +a2f33d4c603d458a +a2f0918470249db8 +a2f66dd28662d5c1 +a2f440ee4af7adbd +a2e5dd2f0837c6e1 +a2f5318bb32166d4 +a2f9cf8e8f96f665 +a2f505c4a80a76f3 +a2f17c5887b70ec1 +a2f13734f3f6a651 +a2e0e647e6c806c0 +a2e4b5522bd7b684 +a2f00aec31f21686 +a2fff6888604b609 +a2ec33c9219c2e06 +a2e0cb33ac0b6ea2 +a2f20fa9469c96f1 +a2e3871b6ecf3f51 +a2ed15b7ef5297e3 +a2e34b9f541eaf26 +a2f66ecf90d65781 +a2ef280a7e3b4f3f +a2e59a139b6557d7 +a2e4433736ab4749 +a2f520ec7e42df69 +a2e0e08899be6f48 +a2e7b0444f0e673f +a2e9052ac87387a3 +a2e37c7a27869fbe +a2eb056597e8c7fc +a2ef57fd79034782 +a2fc21a583692fcf +a2f774c59af21800 +a2febe8fb4404055 +a2efe426b922c02f +a2e83b7fc5691086 +a2f4391672e14050 +a2e4f5959dad58e5 +a2eab8ad446b184b +a2e021421a92c882 +a2e3410d3867004a +a2e8bde92620386f +a2f619fe93d9d82e +a2ec9490efcb3119 +a2ec715014e25161 +a2e5f2968062713c +a2ee1268e80b719a +a2e35129faa181d8 +a2eab74cd9775171 +a2e272cc9732b10b +a2e455088c1b890e +a2edac772b10b976 +a2f99310a52309a2 +a2f378eca29fa17d +a2f2114cab96f9f5 +a2f6c25232c0a1c5 +a2e27f9f8665a1d6 +a2f41b90a79039c2 +a2e863fa943a2a71 +a2fc5dae53fa7abb +a2fdec6f45d9aad7 +a2f3858279058262 +a2e2d4417109121c +a2ecfc626aeaf203 +a2f0bd7bfcf44221 +a2f4164c02e7aacd +a2eece3d40284253 +a2ed3d4bec643220 +a2f64de0314182d3 +a2f94ba2e2a7926f +a2f6c3d5e83a42e0 +a2f8277a3651526b +a2fe57fc685cb224 +a2ed02bd908d7a1e +a2fff6a0788d9352 +a2fe035d23754bad +a2f3549797fc23c6 +a2efaee73fe34bd4 +a2ff8cdf29bae3d1 +a2ff51a8d988a301 +a2e90f9490fafb5a +a2ea041eed989be5 +a2ed6a86b67ef3b4 +a2ecac768ce07b86 +a2e9226bc128c33d +a2f27a53dcb093b2 +a2f08559540bb3f4 +a2edd9e67a3ab305 +a2fdc1d295ba14b4 +a2fdbfc163ca04cb +a2ff88184e3f543a +a2ff67b65385f4a7 +a2e3d3c68b53a4b5 +a2e7f478e29c5491 +a2e152cc78f49c38 +a2e247a7961454e6 +a2f18aeb05972c4b +a2f4fa41e73524fe +a2f60a03b23934a2 +a2eeee72984e8c1b +a2eb9091eda16cf9 +a2f6ce56507ecce2 +a2eaeddb13a16546 +a2f09f88f993d568 +a2eaf08c93a475fe +a2eb31b03cdd054b