public long writeIndexBlocks(FSDataOutputStream out) throws IOException {
      if (curInlineChunk != null && curInlineChunk.getNumEntries() != 0) {
        throw new IOException("Trying to write a multi-level block index, " + "but are "
          + curInlineChunk.getNumEntries() + " entries in the " + "last inline chunk.");
      }

      // We need to get mid-key metadata before we create intermediate
      // indexes and overwrite the root chunk.
      byte[] midKeyMetadata = numLevels > 1 ? rootChunk.getMidKeyMetadata() : null;

      if (curInlineChunk != null) {
        while (
          rootChunk.getRootSize() > maxChunkSize
            // HBASE-16288: if firstKey is larger than maxChunkSize we will loop indefinitely
            && rootChunk.getNumEntries() > minIndexNumEntries
            // Sanity check. We will not hit this (minIndexNumEntries ^ 16) blocks can be addressed
            && numLevels < 16
        ) {
          rootChunk = writeIntermediateLevel(out, rootChunk);
          numLevels += 1;
        }
      }

      // write the root level
      long rootLevelIndexPos = out.getPos();

      {
        DataOutput blockStream = blockWriter.startWriting(BlockType.ROOT_INDEX);
        indexBlockEncoder.encode(rootChunk, true, blockStream);
        if (midKeyMetadata != null) blockStream.write(midKeyMetadata);
        blockWriter.writeHeaderAndData(out);
        if (cacheConf != null) {
          cacheConf.getBlockCache().ifPresent(cache -> {
            HFileBlock blockForCaching = blockWriter.getBlockForCaching(cacheConf);
            cache.cacheBlock(new BlockCacheKey(nameForCaching, rootLevelIndexPos, true,
              blockForCaching.getBlockType()), blockForCaching);
            // Index blocks always go to LRU, which then converts any off-heap buffer to on-heap,
            // so we need to release any off-heap buffers now to avoid leak
            blockForCaching.release();
          });
        }
      }

      // Add root index block size
      totalBlockOnDiskSize += blockWriter.getOnDiskSizeWithoutHeader();
      totalBlockUncompressedSize += blockWriter.getUncompressedSizeWithoutHeader();

      if (LOG.isTraceEnabled()) {
        LOG.trace("Wrote a " + numLevels + "-level index with root level at pos "
          + rootLevelIndexPos + ", " + rootChunk.getNumEntries() + " root-level entries, "
          + totalNumEntries + " total entries, "
          + Strings.humanReadableInt(this.totalBlockOnDiskSize) + " on-disk size, "
          + Strings.humanReadableInt(totalBlockUncompressedSize) + " total uncompressed size.");
      }
      return rootLevelIndexPos;
    }
Writes the root level and intermediate levels of the block index into the output stream,
generating the tree from bottom up. Assumes that the leaf level has been inline-written to
the disk if there is enough data for more than one leaf block. We iterate by breaking the
current level of the block index, starting with the index of all leaf-level blocks, into
chunks small enough to be written to disk, and generate its parent level, until we end up
with a level small enough to become the root level. If the leaf level is not large enough,
there is no inline block index anymore, so we only write that level of block index to disk as
the root level.
@param out FSDataOutputStream
@return position at which we entered the root-level index.
hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileBlockIndex.java