func (c *DefaultPlanner) Plan(lastWrite time.Time) []CompactionGroup {
	generations := c.findGenerations(true)
	c.mu.RLock()
	forceFull := c.forceFull
	c.mu.RUnlock()
	if forceFull || c.compactFullWriteColdDuration > 0 && time.Since(lastWrite) > c.compactFullWriteColdDuration && len(generations) > 1 {
		if forceFull {
			c.mu.Lock()
			c.forceFull = false
			c.mu.Unlock()
		}
		var tsmFiles []string
		var genCount int
		for i, group := range generations {
			var skip bool
			if len(generations) > 2 && group.size() > uint64(maxTSMFileSize) && c.FileStore.BlockCount(group.files[0].Path, 1) == tsdb.DefaultMaxPointsPerBlock && !group.hasTombstones() {
				skip = true
			}
			if i < len(generations)-1 {
				if generations[i+1].level() <= 3 {
					skip = false
				}
			}
			if skip {
				continue
			}
			for _, f := range group.files {
				tsmFiles = append(tsmFiles, f.Path)
			}
			genCount += 1
		}
		sort.Strings(tsmFiles)
		if len(tsmFiles) <= 1 || genCount <= 1 {
			return nil
		}
		group := []CompactionGroup{tsmFiles}
		if !c.acquire(group) {
			return nil
		}
		return group
	}
	if c.lastPlanCheck.After(c.FileStore.LastModified()) && !generations.hasTombstones() {
		return nil
	}
	c.lastPlanCheck = time.Now()
	if len(generations) <= 1 && !generations.hasTombstones() {
		return nil
	}
	end := 0
	start := 0
	for i, g := range generations {
		if g.level() <= 3 {
			break
		}
		end = i + 1
	}
	var hasTombstones bool
	for i, g := range generations[:end] {
		if g.hasTombstones() {
			hasTombstones = true
		}
		if hasTombstones {
			continue
		}
		if g.size() > uint64(maxTSMFileSize) && c.FileStore.BlockCount(g.files[0].Path, 1) == tsdb.DefaultMaxPointsPerBlock {
			start = i + 1
		}
		if i > 0 {
			if g.size()*2 < generations[i-1].size() {
				start = i
				break
			}
		}
	}
	step := 4
	if step > end {
		step = end
	}
	generations = generations[start:end]
	groups := []tsmGenerations{}
	for i := 0; i < len(generations); i += step {
		var skipGroup bool
		startIndex := i
		for j := i; j < i+step && j < len(generations); j++ {
			gen := generations[j]
			lvl := gen.level()
			if lvl <= 3 {
				skipGroup = true
				break
			}
			if gen.size() >= uint64(maxTSMFileSize) && c.FileStore.BlockCount(gen.files[0].Path, 1) == tsdb.DefaultMaxPointsPerBlock && !gen.hasTombstones() {
				startIndex++
				continue
			}
		}
		if skipGroup {
			continue
		}
		endIndex := i + step
		if endIndex > len(generations) {
			endIndex = len(generations)
		}
		if endIndex-startIndex > 0 {
			groups = append(groups, generations[startIndex:endIndex])
		}
	}
	if len(groups) == 0 {
		return nil
	}
	compactable := []tsmGenerations{}
	for _, group := range groups {
		if len(group) < 4 && !group.hasTombstones() {
			continue
		}
		compactable = append(compactable, group)
	}
	var tsmFiles []CompactionGroup
	for _, c := range compactable {
		var cGroup CompactionGroup
		for _, group := range c {
			for _, f := range group.files {
				cGroup = append(cGroup, f.Path)
			}
		}
		sort.Strings(cGroup)
		tsmFiles = append(tsmFiles, cGroup)
	}
	if !c.acquire(tsmFiles) {
		return nil
	}
	return tsmFiles
}
Plan returns a set of TSM files to rewrite for level 4 or higher.  The planning returns
multiple groups if possible to allow compactions to run concurrently.
package tsm1
import (
	"bytes"
	"fmt"
	"io"
	"math"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"sync"
	"sync/atomic"
	"time"
	"github.com/influxdata/influxdb/pkg/limiter"
	"github.com/influxdata/influxdb/tsdb"
)
const maxTSMFileSize = uint32(2048 * 1024 * 1024)  
const (
	CompactionTempExtension = "tmp"
	TSMFileExtension = "tsm"
)
var (
	errMaxFileExceeded     = fmt.Errorf("max file exceeded")
	errSnapshotsDisabled   = fmt.Errorf("snapshots disabled")
	errCompactionsDisabled = fmt.Errorf("compactions disabled")
)
type errCompactionInProgress struct {
	err error
}
func (e errCompactionInProgress) Error() string {
	if e.err != nil {
		return fmt.Sprintf("compaction in progress: %s", e.err)
	}
	return "compaction in progress"
}
type errCompactionAborted struct {
	err error
}
func (e errCompactionAborted) Error() string {
	if e.err != nil {
		return fmt.Sprintf("compaction aborted: %s", e.err)
	}
	return "compaction aborted"
}
type CompactionGroup []string
type CompactionPlanner interface {
	Plan(lastWrite time.Time) []CompactionGroup
	PlanLevel(level int) []CompactionGroup
	PlanOptimize() []CompactionGroup
	Release(group []CompactionGroup)
	FullyCompacted() bool
	ForceFull()
}
type DefaultPlanner struct {
	FileStore fileStore
	compactFullWriteColdDuration time.Duration
	lastPlanCheck time.Time
	mu sync.RWMutex
	lastFindGenerations time.Time
	lastGenerations tsmGenerations
	forceFull bool
	filesInUse map[string]struct{}
}
type fileStore interface {
	Stats() []FileStat
	LastModified() time.Time
	BlockCount(path string, idx int) int
}
func NewDefaultPlanner(fs fileStore, writeColdDuration time.Duration) *DefaultPlanner {
	return &DefaultPlanner{
		FileStore:                    fs,
		compactFullWriteColdDuration: writeColdDuration,
		filesInUse:                   make(map[string]struct{}),
	}
}
type tsmGeneration struct {
	id    int
	files []FileStat
}
func (t *tsmGeneration) size() uint64 {
	var n uint64
	for _, f := range t.files {
		n += uint64(f.Size)
	}
	return n
}
func (t *tsmGeneration) level() int {
	_, seq, _ := ParseTSMFileName(t.files[0].Path)
	if seq < 4 {
		return seq
	}
	return 4
}
func (t *tsmGeneration) count() int {
	return len(t.files)
}
func (t *tsmGeneration) hasTombstones() bool {
	for _, f := range t.files {
		if f.HasTombstone {
			return true
		}
	}
	return false
}
func (c *DefaultPlanner) FullyCompacted() bool {
	gens := c.findGenerations(false)
	return len(gens) <= 1 && !gens.hasTombstones()
}
func (c *DefaultPlanner) ForceFull() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.forceFull = true
}
func (c *DefaultPlanner) PlanLevel(level int) []CompactionGroup {
	c.mu.RLock()
	if c.forceFull {
		c.mu.RUnlock()
		return nil
	}
	c.mu.RUnlock()
	generations := c.findGenerations(true)
	if len(generations) <= 1 && !generations.hasTombstones() {
		return nil
	}
	var currentGen tsmGenerations
	var groups []tsmGenerations
	for i := 0; i < len(generations); i++ {
		cur := generations[i]
		if i < len(generations)-1 {
			if cur.level() < generations[i+1].level() {
				currentGen = append(currentGen, cur)
				continue
			}
		}
		if len(currentGen) == 0 || currentGen.level() == cur.level() {
			currentGen = append(currentGen, cur)
			continue
		}
		groups = append(groups, currentGen)
		currentGen = tsmGenerations{}
		currentGen = append(currentGen, cur)
	}
	if len(currentGen) > 0 {
		groups = append(groups, currentGen)
	}
	var levelGroups []tsmGenerations
	for _, cur := range groups {
		if cur.level() == level {
			levelGroups = append(levelGroups, cur)
		}
	}
	minGenerations := 4
	if level == 1 {
		minGenerations = 8
	}
	var cGroups []CompactionGroup
	for _, group := range levelGroups {
		for _, chunk := range group.chunk(minGenerations) {
			var cGroup CompactionGroup
			var hasTombstones bool
			for _, gen := range chunk {
				if gen.hasTombstones() {
					hasTombstones = true
				}
				for _, file := range gen.files {
					cGroup = append(cGroup, file.Path)
				}
			}
			if len(chunk) < minGenerations && !hasTombstones {
				continue
			}
			cGroups = append(cGroups, cGroup)
		}
	}
	if !c.acquire(cGroups) {
		return nil
	}
	return cGroups
}
func (c *DefaultPlanner) PlanOptimize() []CompactionGroup {
	c.mu.RLock()
	if c.forceFull {
		c.mu.RUnlock()
		return nil
	}
	c.mu.RUnlock()
	generations := c.findGenerations(true)
	if len(generations) <= 1 && !generations.hasTombstones() {
		return nil
	}
	var currentGen tsmGenerations
	var groups []tsmGenerations
	for i := 0; i < len(generations); i++ {
		cur := generations[i]
		if cur.count() > 2 && cur.size() > uint64(maxTSMFileSize) && c.FileStore.BlockCount(cur.files[0].Path, 1) == tsdb.DefaultMaxPointsPerBlock && !cur.hasTombstones() {
			continue
		}
		if i < len(generations)-1 {
			if cur.level() < generations[i+1].level() {
				currentGen = append(currentGen, cur)
				continue
			}
		}
		if len(currentGen) == 0 || currentGen.level() == cur.level() {
			currentGen = append(currentGen, cur)
			continue
		}
		groups = append(groups, currentGen)
		currentGen = tsmGenerations{}
		currentGen = append(currentGen, cur)
	}
	if len(currentGen) > 0 {
		groups = append(groups, currentGen)
	}
	var levelGroups []tsmGenerations
	for _, cur := range groups {
		if cur.level() == 4 {
			levelGroups = append(levelGroups, cur)
		}
	}
	var cGroups []CompactionGroup
	for _, group := range levelGroups {
		if len(group) < 4 && !group.hasTombstones() {
			continue
		}
		var cGroup CompactionGroup
		for _, gen := range group {
			for _, file := range gen.files {
				cGroup = append(cGroup, file.Path)
			}
		}
		cGroups = append(cGroups, cGroup)
	}
	if !c.acquire(cGroups) {
		return nil
	}
	return cGroups
}