func (b *BitFlagStorage) GetChanges(fromSeq base.SequenceClock, toSeq base.SequenceClock) ([]*LogEntry, error) {

	// Determine which blocks have changed, and load those blocks
	blocksByKey, blocksByVb, err := b.calculateChangedBlocks(fromSeq, toSeq)
	if err != nil {
		return nil, err
	}
	b.bulkLoadBlocks(blocksByKey)

	// For each vbucket, create the entries from the blocks.  Create in reverse sequence order, for
	// deduplication once we've retrieved the full entry
	entries := make([]*LogEntry, 0)
	entryKeys := make([]string, 0)
	for blockSetIndex := len(blocksByVb) - 1; blockSetIndex >= 0; blockSetIndex-- {
		blockSet := blocksByVb[blockSetIndex]
		vbNo := blockSet.vbNo
		blocks := blockSet.blocks
		fromVbSeq := fromSeq.GetSequence(vbNo) + 1
		toVbSeq := toSeq.GetSequence(vbNo)

		for blockIndex := len(blocks) - 1; blockIndex >= 0; blockIndex-- {
			blockEntries, keys := blocks[blockIndex].GetEntries(vbNo, fromVbSeq, toVbSeq, true)
			entries = append(entries, blockEntries...)
			entryKeys = append(entryKeys, keys...)
		}
	}

	// Bulk retrieval of individual entries.  Performs deduplication, and reordering into ascending vb and sequence order
	results := b.bulkLoadEntries(entryKeys, entries)

	return results, nil

}
// Calculate the set of index blocks that need to be loaded.
//   blocksByVb stores which blocks need to be processed for each vbucket, in ascending vbucket order.  Multiple vb map
//   values can point to the same IndexBlock (i.e. when those vbs share a partition).
//   blocksByKey stores all IndexBlocks to be retrieved, indexed by block key - no duplicates
func (b *BitFlagStorage) calculateChangedBlocks(fromSeq base.SequenceClock, channelClock base.SequenceClock) (blocksByKey map[string]IndexBlock, blocksByVb []vbBlockSet, err error) {

	blocksByKey = make(map[string]IndexBlock, 1)
	blocksByVb = make([]vbBlockSet, 0)

	for vbNo, clockVbSeq := range channelClock.Value() {
		fromVbSeq := fromSeq.GetSequence(uint16(vbNo))
		// Verify that the requested from value is less than the channel clock sequence (there are
		// new entries for this vbucket in the channel)
		if fromVbSeq >= clockVbSeq {
			continue
		}

		blockSet := vbBlockSet{vbNo: uint16(vbNo)}
		partition := b.partitions.VbMap[uint16(vbNo)]
		for _, blockIndex := range generateBitFlagBlockIndexes(b.channelName, fromVbSeq, clockVbSeq, partition) {
			blockKey := getIndexBlockKey(b.channelName, blockIndex, partition)
			block, found := blocksByKey[blockKey]
			if !found {
				block = newBitFlagBufferBlockForKey(blockKey, b.channelName, blockIndex, partition, b.partitions.VbPositionMaps[partition])
				blocksByKey[blockKey] = block
			}
			blockSet.blocks = append(blockSet.blocks, block)
		}
		blocksByVb = append(blocksByVb, blockSet)

	}
	return blocksByKey, blocksByVb, nil
}
// calculateChangedPartitions identifies which vbuckets have changed after sinceClock until toClock, groups these
// by partition, and returns as a array of PartitionRanges, indexed by partition number.
func (ds *DenseStorageReader) calculateChanged(sinceClock, toClock base.SequenceClock) (changedVbs []uint16, changedPartitions []*PartitionRange) {

	changedVbs = make([]uint16, 0)
	changedPartitions = make([]*PartitionRange, ds.partitions.PartitionCount())
	for vbNoInt, toSeq := range toClock.Value() {
		vbNo := uint16(vbNoInt)
		sinceSeq := sinceClock.GetSequence(vbNo)
		if sinceSeq < toSeq {
			changedVbs = append(changedVbs, vbNo)
			partitionNo := ds.partitions.PartitionForVb(vbNo)
			if changedPartitions[partitionNo] == nil {
				partitionRange := NewPartitionRange()
				changedPartitions[partitionNo] = &partitionRange
			}
			changedPartitions[partitionNo].SetRange(vbNo, sinceSeq, toSeq)
		}
	}
	return changedVbs, changedPartitions
}