private Pair<HashMapSegmentLayout, HashMapSegmentLayout> split(@NotNull HashMapSegmentLayout segmentToSplit) throws IOException {
    int oldHashSuffix = segmentToSplit.hashSuffix();
    byte oldHashSuffixDepth = segmentToSplit.hashSuffixDepth();

    byte newHashSuffixDepth = (byte)(oldHashSuffixDepth + 1);
    int highestSuffixBit = 1 << (newHashSuffixDepth - 1);
    int hashSuffix0 = oldHashSuffix;                     // ...000[oldHashSuffix]
    int hashSuffix1 = oldHashSuffix | highestSuffixBit;  // ...001[oldHashSuffix]
    assert hashSuffix0 != hashSuffix1
      : "hashSuffixes must be different for splitting segments, but " + hashSuffix0 + " == " + hashSuffix1;

    HashMapSegmentLayout newSegment = allocateSegment(hashSuffix1, newHashSuffixDepth);
    segmentToSplit.updateHashSuffix(hashSuffix0, newHashSuffixDepth);

    //Transfer entries that are not belonged to segmentToSplit with new hashSuffix0/newHashSuffixDepth
    // delete those entries from segmentToSplit and move them to newSegment:
    int hashSuffixMask = segmentToSplit.hashSuffixMask(); // ...001_00... -> ...000_11...
    int entriesCount = segmentToSplit.entriesCount();
    for (int i = 0; i < entriesCount; i++) {
      int key = segmentToSplit.entryKey(i);
      if (hashMapAlgo.isSlotOccupied(key)) {
        int hash = hash(key);
        if ((hash & hashSuffixMask) != hashSuffix0) {
          int value = segmentToSplit.entryValue(i);

          hashMapAlgo.markEntryAsDeleted(segmentToSplit, i);

          hashMapAlgo.put(newSegment, key, value);
        }
      }
      //MAYBE RC: possible performance issue here: each time we split segments, a lot of tombstones are
      // accumulated in the old segment -- for every entry moved to a new segment we leave tombstone behind.
      // So after the split old segment contain ~25% of total entries as tombstones (50% fill is a split
      // trigger, 1/2 entries moved to new segment on split). Next split adds another 25% tombstones, and
      // so on. This is not strictly additive process -- it is not that 100% entries will be tombstones
      // after 4 splits -- tombstones get reused, but still accumulation is possible, and abundance of
      // tombstones could significantly increase probing length even for low-load-factor tables.
      // In a regular (in-memory) hashtables this is not an issue, since we get rid of all tombstones on
      // each resize, while copying only alive values into a new table. Here this mechanism is not exists,
      // because instead of _recreate_ the table on each resize we just add new segment, leaving old segment
      // as-is.
      // Solution is a regular pruning: keep track of segment tombstone count, and re-hash segment if there
      // are too many tombstones. Since segment size is fixed and not too big, we could copy alive entries
      // in memory buffer, clean the segment entries table, and insert values back into it -- simple and fast.
    }

    return Pair.pair(segmentToSplit, newSegment);
  }
private Pair<HashMapSegmentLayout, HashMapSegmentLayout> split(@NotNull HashMapSegmentLayout segmentToSplit)