}
 
     // Note: possible to return an end iterator when is_exclusive is true
+    // insert_index can still be INDEX_LAST or INDEX_END
     template <bool is_exclusive>
     size_t seek_split_inserted(
         size_t start_size, size_t extra_size, size_t target_size,
     std::optional<iterator_t> iter;
   };
 
-  static void recursively_locate_split(
+  static bool recursively_locate_split(
       size_t& current_size, size_t extra_size,
       size_t target_size, StagedIterator& split_at) {
     assert(current_size <= target_size);
     iterator_t& split_iter = split_at.get();
     current_size = split_iter.seek_split(current_size, extra_size, target_size);
+    assert(current_size <= target_size);
     assert(!split_iter.is_end());
     if (split_iter.index() == 0) {
       extra_size += iterator_t::header_size();
     } else {
       extra_size = 0;
     }
+    bool locate_nxt;
     if constexpr (!IS_BOTTOM) {
-      NXT_STAGE_T::recursively_locate_split(
+      locate_nxt = NXT_STAGE_T::recursively_locate_split(
           current_size, extra_size + split_iter.size_to_nxt(),
           target_size, split_at.nxt());
+    } else { // IS_BOTTOM
+      // located upper_bound, fair split strategy
+      size_t nxt_size = split_iter.size() + extra_size;
+      assert(current_size + nxt_size > target_size);
+      if (current_size + nxt_size/2 < target_size) {
+        // include next
+        current_size += nxt_size;
+        locate_nxt = true;
+      } else {
+        // exclude next
+        locate_nxt = false;
+      }
+    }
+    if (locate_nxt) {
+      if (split_iter.is_last()) {
+        return true;
+      } else {
+        ++split_at;
+        return false;
+      }
+    } else {
+      return false;
     }
   }
 
-  static void recursively_locate_split_inserted(
+  static bool recursively_locate_split_inserted(
       size_t& current_size, size_t extra_size, size_t target_size,
       position_t& insert_pos, match_stage_t insert_stage, size_t insert_size,
       std::optional<bool>& is_insert_left, StagedIterator& split_at) {
           current_size, extra_size, target_size,
           insert_index, insert_size, is_insert_left);
       assert(is_insert_left.has_value());
+      assert(current_size <= target_size);
       if (split_iter.index() == 0) {
         extra_size += iterator_t::header_size();
       } else {
         // split_iter can be end
         // found the lower-bound of target_size
         // ...[s_index-1] |!| (i_index) [s_index]...
-        return;
+
+        // located upper-bound, fair split strategy
+        // look at the next slot (the insert item)
+        size_t nxt_size = insert_size + extra_size;
+        assert(current_size + nxt_size > target_size);
+        if (current_size + nxt_size/2 < target_size) {
+          // include next
+          *is_insert_left = true;
+          current_size += nxt_size;
+          if (split_iter.is_end()) {
+            // ...[s_index-1] (i_index) |!|
+            return true;
+          } else {
+            return false;
+          }
+        } else {
+          // exclude next
+          return false;
+        }
       } else {
         // Already considered insert effect in the current stage.
         // Look into the next stage to identify the target_size lower-bound w/o
         // insert effect.
         assert(!split_iter.is_end());
+        bool locate_nxt;
         if constexpr (!IS_BOTTOM) {
-          NXT_STAGE_T::recursively_locate_split(
+          locate_nxt = NXT_STAGE_T::recursively_locate_split(
               current_size, extra_size + split_iter.size_to_nxt(),
               target_size, split_at.nxt());
+        } else { // IS_BOTTOM
+          // located upper-bound, fair split strategy
+          // look at the next slot
+          size_t nxt_size = split_iter.size() + extra_size;
+          assert(current_size + nxt_size > target_size);
+          if (current_size + nxt_size/2 < target_size) {
+            // include next
+            current_size += nxt_size;
+            locate_nxt = true;
+          } else {
+            // exclude next
+            locate_nxt = false;
+          }
+        }
+        if (locate_nxt) {
+          if (split_iter.is_last()) {
+            auto end_index = split_iter.index() + 1;
+            if (insert_index == INDEX_END) {
+              insert_index = end_index;
+            }
+            assert(insert_index <= end_index);
+            if (insert_index == end_index) {
+              assert(*is_insert_left == false);
+              split_iter.set_end();
+              // ...[s_index-1] |!| (i_index)
+              return false;
+            } else {
+              assert(*is_insert_left == true);
+              return true;
+            }
+          } else {
+            ++split_at;
+            return false;
+          }
+        } else {
+          return false;
         }
       }
     } else {
             current_size, extra_size, target_size,
             insert_index, insert_size, is_insert_left);
         assert(!split_iter.is_end());
+        assert(current_size <= target_size);
         if (split_iter.index() == 0) {
           extra_size += iterator_t::header_size();
         } else {
           extra_size = 0;
         }
+        bool locate_nxt;
         if (!is_insert_left.has_value()) {
           // Considered insert effect in the current stage, and insert happens
           // in the lower stage.
           // Look into the next stage to identify the target_size lower-bound w/
           // insert effect.
           assert(split_iter.index() == insert_index);
-          NXT_STAGE_T::recursively_locate_split_inserted(
+          locate_nxt = NXT_STAGE_T::recursively_locate_split_inserted(
               current_size, extra_size + split_iter.size_to_nxt(), target_size,
               insert_pos.nxt, insert_stage, insert_size,
               is_insert_left, split_at.nxt());
           assert(is_insert_left.has_value());
-          return;
+#ifndef NDEBUG
+          if (locate_nxt) {
+            assert(*is_insert_left == true);
+          }
+#endif
         } else {
           // is_insert_left.has_value() == true
           // Insert will *not* happen in the lower stage.
           // Need to look into the next stage to identify the target_size
           // lower-bound w/ insert effect
-          NXT_STAGE_T::recursively_locate_split(
+          assert(split_iter.index() != insert_index);
+          locate_nxt = NXT_STAGE_T::recursively_locate_split(
               current_size, extra_size + split_iter.size_to_nxt(),
               target_size, split_at.nxt());
-          return;
+#ifndef NDEBUG
+          if (split_iter.index() < insert_index) {
+            assert(*is_insert_left == false);
+          } else {
+            assert(*is_insert_left == true);
+          }
+#endif
+        }
+        if (locate_nxt) {
+          if (split_iter.is_last()) {
+            return true;
+          } else {
+            ++split_at;
+            return false;
+          }
+        } else {
+          return false;
         }
       } else {
         assert(false && "impossible path");
-        return;
+        return false;;
       }
     }
   }
 
       TestTree test;
       test.build_tree({2, 5}, {2, 5}, {2, 5}, 120).get0();
 
-      auto& onode = test.create_onode(1084);
+      auto& onode = test.create_onode(1144);
       logger().info("\n---------------------------------------------"
                     "\nsplit at stage 2; insert to left front at stage 2, 1, 0\n");
       test.split(make_ghobj(1, 1, 1, "ns3", "oid3", 3, 3), onode).get0();
       test.split(make_ghobj(3, 3, 3, "ns1", "oid1", 3, 3), onode).get0();
       test.split(make_ghobj(3, 3, 3, "ns2", "oid2", 1, 1), onode).get0();
 
-      auto& onode0 = test.create_onode(1476);
+      auto& onode0 = test.create_onode(1416);
       logger().info("\n---------------------------------------------"
                     "\nsplit at stage 2; insert to right front at stage 0, 1, 2, 1, 0\n");
       test.split(make_ghobj(3, 3, 3, "ns4", "oid4", 5, 5), onode0).get0();
       test.split(make_ghobj(4, 4, 4, "ns1", "oid1", 3, 3), onode2).get0();
       test.split(make_ghobj(4, 4, 4, "ns2", "oid2", 1, 1), onode2).get0();
 
-      auto& onode3 = test.create_onode(964);
+      auto& onode3 = test.create_onode(834);
       logger().info("\n---------------------------------------------"
                     "\nsplit at stage 0; insert to right middle at stage 0, 1, 2, 1, 0\n");
       test.split(make_ghobj(3, 3, 3, "ns4", "oid4", 5, 5), onode3).get0();
                     "\nbefore internal node insert:\n");
       auto padding = std::string(250, '_');
       auto keys = build_key_set({2, 6}, {2, 5}, {2, 5}, padding, true);
+      keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 2, 2));
+      keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 3, 3));
+      keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 4, 4));
+      keys.erase(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 2, 2));
+      keys.erase(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 3, 3));
+      keys.erase(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 4, 4));
+      auto padding_s = std::string(257, '_');
+      keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 2, 2));
+      keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 3, 3));
+      keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 4, 4));
+      auto padding_e = std::string(248, '_');
+      keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 2, 2));
+      keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 3, 3));
+      keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 4, 4));
       pool.build_tree(keys).unsafe_get0();
 
       logger().info("\n---------------------------------------------"
       logger().info("\n---------------------------------------------"
                     "\nsplit at stage 2; insert to right back at stage 0, 1, 2\n");
       pool.test_split(
-          make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 5, 5), search_position_t::end()).get();
+          make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 5, 5), search_position_t::end()).get();
       pool.test_split(make_ghobj(5, 5, 5, "ns5", "oid5", 3, 3), search_position_t::end()).get();
       pool.test_split(make_ghobj(6, 6, 6, "ns3", "oid3", 3, 3), search_position_t::end()).get();
 
                     "\nsplit at stage 0; insert to left front at stage 2, 1, 0\n");
       pool.test_split(make_ghobj(1, 1, 1, "ns3", "oid3", 3, 3), {0, {0, {0}}}).get();
       pool.test_split(make_ghobj(2, 2, 2, "ns1", "oid1", 3, 3), {0, {0, {0}}}).get();
-      pool.test_split(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 1, 1), {0, {0, {0}}}).get();
+      pool.test_split(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 1, 1), {0, {0, {0}}}).get();
 
       logger().info("\n---------------------------------------------"
                     "\nsplit at stage 0/1; insert to left middle at stage 0, 1, 2, 1, 0\n");
     {
       logger().info("\n---------------------------------------------"
                     "\nbefore internal node insert (3):\n");
-      auto padding = std::string(417, '_');
+      auto padding = std::string(420, '_');
       auto keys = build_key_set({2, 5}, {2, 5}, {2, 5}, padding, true);
-      keys.insert(make_ghobj(4, 4, 4, "ns3", "oid3" + padding, 5, 5));
       keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 2, 2));
       keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 3, 3));
       keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 4, 4));
     {
       logger().info("\n---------------------------------------------"
                     "\nbefore internal node insert (4):\n");
-      auto padding = std::string(360, '_');
+      auto padding = std::string(361, '_');
       auto keys = build_key_set({2, 5}, {2, 5}, {2, 5}, padding, true);
-      keys.insert(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 5, 5));
+      keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 2, 2));
+      keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 3, 3));
+      keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 4, 4));
+      auto padding_s = std::string(387, '_');
+      keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 2, 2));
+      keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 3, 3));
+      keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 4, 4));
       pool.build_tree(keys).unsafe_get0();
 
       logger().info("\n---------------------------------------------"