前言
~~ 这是一个分析inlfuxdb源码的系列。在此之前,已经分析了数据的基本模型,以及写入流程。在上一章,分析了数据写入的一些细节,例如解析point的逻辑,元数据校验和更新等。在元数据的更新和校验时,有个方法是validateSeriesAndFields,这个方法会校验当前写入的点,并且把新的series创建出来。不过我们没有自信的分析创建的过程,因为涉及到sereis模块,只是一笔带过。这一章就专门来分析series。
- influxdb安装和使用
- influxdb概念详解1
- influxdb概念详解2
- influxdb源码编译
- influxdb启动分析
- influxdb源码分析-meta部分
- infludb源码分析-数据写入
- influxdb数据写入细节
提示
还是温馨提示,这部分内容其实算是元数据部分。如果不了解什么叫做series,那么回到概念详解部分,看看series的解答。series在influxdb官方的文档中被描述为measurement+tags+field的组合,其实在代码实现的过程中,series是measurement+tags,没有考虑到field。
Series的基本结构
~~~
series信息,被保存在了seriesFile结构,seriesFile又分为多个partition,每个partition叫做SeriesPartition。SeriesPartition是Series的一个逻辑上的分桶,用来避免全局锁。减小竞争。SeriesPartition下面分为多个SeriesSegment,每个SeriesSegment就是一个保存了Series信息的文件。使用mmap映射到内存里面。
~~~
这里的结构有点像kafka 的设计。具体的逻辑如下。那么接下来分别分析一下这些结构,从最简单的开始。
SeriesSegment
上面提到了,seriesSegment对应的就是一个文件。
// SeriesSegment represents a log of series entries.
type SeriesSegment struct {
// 映射一个磁盘文件
id uint16
path string
data []byte // mmap file
file *os.File // write file handle
w *bufio.Writer // bufferred file handle
size uint32 // current file size
}
从注释里可以看出来,SeriesSegment是series entry log的集合。这些文件,路径是:
其中00-07代表seriesPartition,0000代表一个seriesSegment。seriesSegment保存了一条条的serie log,这个和WAL的设计类似。
CreateSeriesSegment
创建一个SeriesSegment的逻辑在influxdb/tsdb/series_segment.go中
func CreateSeriesSegment(id uint16, path string) (*SeriesSegment, error) {
f, err := os.Create(path + ".initializing")
if err != nil {
return nil, err
}
defer f.Close()
// Write header to file and close.
hdr := NewSeriesSegmentHeader()
if _, err := hdr.WriteTo(f); err != nil {
return nil, err
} else if err := f.Truncate(int64(SeriesSegmentSize(id))); err != nil {
return nil, err
} else if err := f.Sync(); err != nil {
return nil, err
} else if err := f.Close(); err != nil {
return nil, err
}
// Swap with target path.
if err := os.Rename(f.Name(), path); err != nil {
return nil, err
}
// Open segment at new location.
segment := NewSeriesSegment(id, path)
if err := segment.Open(); err != nil {
return nil, err
}
return segment, nil
}
逻辑闭件简单,首先是创建文件,然后写入Header信息,这里有个函数Truncate,可能有些人不熟悉,这个函数的作用是把文件大小resize一下,但是不会改变文件指针的位置。这里其实是一个比较重要的逻辑:
func SeriesSegmentSize(id uint16) uint32 {
const min = 22 // 4MB
const max = 28 // 256MB
shift := id + min
if shift >= max {
shift = max
}
return 1 << shift
}
这里可以看出来,series的文件大小是和id有关系的,最小是4MB,最大是28MB。在我们上面展示的series文件列表中,也可以看到,确实是4MB大小。新建之后,会调用series的Open函数。
// Open memory maps the data file at the file's path.
func (s *SeriesSegment) Open() error {
if err := func() (err error) {
// Memory map file data.
if s.data, err = mmap.Map(s.path, int64(SeriesSegmentSize(s.id))); err != nil {
return err
}
// Read header.
hdr, err := ReadSeriesSegmentHeader(s.data)
if err != nil {
return err
} else if hdr.Version != SeriesSegmentVersion {
return ErrInvalidSeriesSegmentVersion
}
return nil
}(); err != nil {
s.Close()
return err
}
return nil
}
这块逻辑也比较简单,其实就是把data做个mmap映射,把数据映射到内存里。
offset
本来接下来就该分析怎么写入一个series log entry,但是这里先介绍另外一个概念:offset.
一个seriesSegment对应的文件如上所示,每一个series entry都代表了一个series。为了寻找他们,就需要知道他们在文件中的偏移,例如series entry 2在文件的偏移等于series entry 1的大小+1。但是offset就指的是series在文件中的偏移嘛?其实不全是,因为series文件可能有多个,每个series 文件的嘛名字都是这样生成的:
var id uint16
if len(p.segments) > 0 {
id = p.segments[len(p.segments)-1].ID() + 1
}
filename := fmt.Sprintf("%04x", id)
也就是下一个series 文件的名字是0001,接着是0002…,所以offset还要带有文件名字的信息。
为了达到这个目的,offset采用如下方式生成:
func JoinSeriesOffset(segmentID uint16, pos uint32) int64 {
return (int64(segmentID) << 32) | int64(pos)
}
使用高32位存储文件名字,也就是segmentId,第32位存储文件偏移量。这样给定一个offset,就能很快找到这个offset对应的series entry。
~~~
明白了这些,那么接下来看看写入的流程。
func (s *SeriesSegment) WriteLogEntry(data []byte) (offset int64, err error) {
if !s.CanWrite(data) {
return 0, ErrSeriesSegmentNotWritable
}
offset = JoinSeriesOffset(s.id, s.size)
if _, err := s.w.Write(data); err != nil {
return 0, err
}
s.size += uint32(len(data))
return offset, nil
}
这里的写入就很清晰了,计算offset,然后写入到文件中。
两个flag
在series_segment开始部分,有一些常量,其中有两个Flag标记。
SeriesEntryInsertFlag = 0x01
SeriesEntryTombstoneFlag = 0x02
第一个叫做InsertFlag,第二个是TombstoneFlag,这两个flag干啥用的呢?
~~~
众所周知,influxdb是支持删除一个series的,但是在LSM tree base的存储中,真实删除都是在compact的过程总完成,前期只是插入一个删除标记。influxdb 对series删除的支持,也是这样做的。当要删除一个series时,并不是直接立刻删除,而是插入删除标记,等到做compact再删除。这两个flag就是标记当前series是不是被删除。
compactToPath
这个函数从注释上就能看出来,是为了remove掉被删除的series。核心逻辑在forEach里面,如果被删除了,那么久跳过,否则重新append到里面。
// CompactToPath rewrites the segment to a new file and removes tombstoned entries.
func (s *SeriesSegment) CompactToPath(path string, index *SeriesIndex) error {
dst, err := CreateSeriesSegment(s.id, path)
if err != nil {
return err
}
defer dst.Close()
if err = dst.InitForWrite(); err != nil {
return err
}
// Iterate through the segment and write any entries to a new segment
// that exist in the index.
var buf []byte
if err = s.ForEachEntry(func(flag uint8, id uint64, _ int64, key []byte) error {
if index.IsDeleted(id) {
return nil // series id has been deleted from index
} else if flag == SeriesEntryTombstoneFlag {
return fmt.Errorf("[series id %d]: tombstone entry but exists in index", id)
}
// copy entry over to new segment
buf = AppendSeriesEntry(buf[:0], flag, id, key)
if _, err := dst.WriteLogEntry(buf); err != nil {
return err
}
return err
}); err != nil {
return err
}
// Close the segment and truncate it to its maximum size.
size := dst.size
if err := dst.Close(); err != nil {
return err
} else if err := os.Truncate(dst.path, int64(size)); err != nil {
return err
}
return nil
}
这段代码出现了一个新的东西,seriesIndex,是用来索引series内部的相关信息的。这里要搞清楚,因为后面还有个index,是用来索引其他模块和series信息的。compact之后,才会真正删除掉被标记位tomb的series。
ReadSeriesEntry和AppendSeriesEntry
这两个函数可以理解为读取series和写入series,但是这里却不是seriesSegment提供的能力。这两个函数属于是一层包装函数,因为series log是有格式的。但是seriesSegment不感知这series entry log的格式,它只关心最后的数据,也就是一个byte 数组。所以这两个函数的作用就相当于是序列化和反序列化操作。
~~~
既然是序列化和反序列化,那么肯定就要有协议。也就是serie log entry的格式。这个格式从AppendSeriesEntry也能窥得一二。
func AppendSeriesEntry(dst []byte, flag uint8, id uint64, key []byte) []byte {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, id)
dst = append(dst, flag)
dst = append(dst, buf...)
switch flag {
case SeriesEntryInsertFlag:
dst = append(dst, key...)
case SeriesEntryTombstoneFlag:
default:
panic(fmt.Sprintf("unreachable: invalid flag: %d", flag))
}
return dst
}
注意看dst的append逻辑,dst首先append了flag,这是个uint8,然后是append了buf,最后是append了key。所以series log entry的格式就是:
key的格式就不再讲了,key=measurement+tags这些信息在key里面是依次平铺的。在之前的代码中也能看到。Read和Append的相反的操作,就不再赘述。
~~~
到这里seriesSegment的基本逻辑算是结束了,接下来是seriesPartition。
SeriesPartition
SeriesPartition代表了一组serieSegment的集合。具体结构如下:
// SeriesPartition represents a subset of series file data.
type SeriesPartition struct {
mu sync.RWMutex
wg sync.WaitGroup
id int
path string
closed bool
closing chan struct{}
once sync.Once
segments []*SeriesSegment
index *SeriesIndex
seq uint64 // series id sequence
compacting bool
compactionLimiter limiter.Fixed
compactionsDisabled int
CompactThreshold int
Logger *zap.Logger
}
从这个结构中大致可以看到三个重要信息:
- SeriesPartition包含了一组SeriesSegment
- SeriesPartition使用了SeriesIndex,来索引这个partition下面的所有seriesSegment信息。
- SeriesPartition会控制SeriesIndex的compact
除此之外,SeriesPartition还有一个重要的成员:seq。这个字段是用来生成当前SeriesPartition下面所有Series的SeriesId的。至于怎么生成的,后面会分析。
New和Open
首先还是老规矩,看看SeriesPartition的NewSeriesPartition和Open函数。New很简洁,就是一些基本字段的赋值。
func NewSeriesPartition(id int, path string, compactionLimiter limiter.Fixed) *SeriesPartition {
return &SeriesPartition{
id: id,
path: path,
closing: make(chan struct{}),
compactionLimiter: compactionLimiter,
CompactThreshold: DefaultSeriesPartitionCompactThreshold,
Logger: zap.NewNop(),
seq: uint64(id) + 1,
}
}
这里有个细节就是,上面提到了seq,可以发现,在初始化SeriesPartition是,seq=SeriesPartitionId+1.然后是Open函数
// Open memory maps the data file at the partition's path.
func (p *SeriesPartition) Open() error {
if p.closed {
return errors.New("tsdb: cannot reopen series partition")
}
// Create path if it doesn't exist.
if err := os.MkdirAll(filepath.Join(p.path), 0777); err != nil {
return err
}
// Open components.
if err := func() (err error) {
if err := p.openSegments(); err != nil {
return err
}
// Init last segment for writes.
if err := p.activeSegment().InitForWrite(); err != nil {
return err
}
p.index = NewSeriesIndex(p.IndexPath())
// index主要是keyid和idoffset
if err := p.index.Open(); err != nil {
return err
// rebuild
} else if p.index.Recover(p.segments); err != nil {
return err
}
return nil
}(); err != nil {
p.Close()
return err
}
return nil
}
Open函数主要干了三件事情:
- open当钱SeriesPartition下所有的SeriesSegment
- 选中当前Active的SeriesSegment,并且初始化
- 恢复Index.
openSegments
这是SeriesPartitionOpen做的第一件事情,之所以拿出来说,是因为这里面也有一个特殊的逻辑;
func (p *SeriesPartition) openSegments() error {
//........ 开启所有Segment
// Find max series id by searching segments in reverse order.
for i := len(p.segments) - 1; i >= 0; i-- {
if seq := p.segments[i].MaxSeriesID(); seq >= p.seq {
// Reset our sequence num to the next one to assign
p.seq = seq + SeriesFilePartitionN
break
}
}
// ....... 省略
这里前后省略了,只看中间部分。这里从后往前遍历所有的segment,然后更新seq。这里有个注释:Find max series id by searching segments in reverse order. 说明了series id的生成规则:seriesId=seq+SeriesFilePartitionN。举个例子,
因为SeriesFilePartitionN 默认数量是8个,所以每个partition中对seriesid的生成都是隔离的,这样避免了锁竞争。同时给定一个seriesId,也能很快的找出来他位于那个partition。
~~~ open结束之后,SeriesPartition的信息基本都被建立起来了。为了逻辑更加的清晰,接下来会把目光转到对SeriesIndex的介绍。因为后面的操作都是和这个有一定的关系。
SeriesIndex
seriesIndex在上面简单的提了一下,是SeriesPartition为了索引SeriesSegment所保存的信息。看一下具体的结构:
// SeriesIndex represents an index of key-to-id & id-to-offset mappings.
type SeriesIndex struct {
path string
count uint64
capacity int64
mask int64
maxSeriesID uint64
maxOffset int64
data []byte // mmap data
keyIDData []byte // key/id mmap data
idOffsetData []byte // id/offset mmap data
// In-memory data since rebuild.
keyIDMap *rhh.HashMap
idOffsetMap map[uint64]int64
tombstones map[uint64]struct{}
}
这里有三个重要的结构:
- KeyIdMap
- IdOffsetMap
- tombstones
这三个结构是最重要的数据。KeyIdMap保存了seriesKey-> seriesId的映射。seriesId是怎么生成的,上一面已经提到。IdOffsetMap保存了seriesId-> offset的映射。offset具体是啥,上面也已经提到。最后是tombstones,保存了所有被删除的series信息。除此之外,Index还保存了KeyId,idOffset等信息。
New和Open
也是先看看这个结构的New和Open函数。
func NewSeriesIndex(path string) *SeriesIndex {
return &SeriesIndex{
path: path,
}
}
// Open memory-maps the index file.
func (idx *SeriesIndex) Open() (err error) {
// Map data file, if it exists.
if err := func() error {
if _, err := os.Stat(idx.path); err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
if idx.data, err = mmap.Map(idx.path, 0); err != nil {
return err
}
hdr, err := ReadSeriesIndexHeader(idx.data)
if err != nil {
return err
}
idx.count, idx.capacity, idx.mask = hdr.Count, hdr.Capacity, hdr.Capacity-1
idx.maxSeriesID, idx.maxOffset = hdr.MaxSeriesID, hdr.MaxOffset
idx.keyIDData = idx.data[hdr.KeyIDMap.Offset : hdr.KeyIDMap.Offset+hdr.KeyIDMap.Size]
idx.idOffsetData = idx.data[hdr.IDOffsetMap.Offset : hdr.IDOffsetMap.Offset+hdr.IDOffsetMap.Size]
}
return nil
}(); err != nil {
idx.Close()
return err
}
idx.keyIDMap = rhh.NewHashMap(rhh.DefaultOptions)
idx.idOffsetMap = make(map[uint64]int64)
idx.tombstones = make(map[uint64]struct{})
return nil
}
New基本没有任何逻辑,只对index路径赋值。Open尝试读取文件中的数据,并且保存到对应的字段中。这里没啥特别之处。
Recover
接下来是从segment中恢复数据。
// Recover rebuilds the in-memory index for all new entries.
func (idx *SeriesIndex) Recover(segments []*SeriesSegment) error {
// Allocate new in-memory maps.
idx.keyIDMap = rhh.NewHashMap(rhh.DefaultOptions)
idx.idOffsetMap = make(map[uint64]int64)
idx.tombstones = make(map[uint64]struct{})
// Process all entries since the maximum offset in the on-disk index.
minSegmentID, _ := SplitSeriesOffset(idx.maxOffset)
for _, segment := range segments {
if segment.ID() < minSegmentID {
continue
}
if err := segment.ForEachEntry(func(flag uint8, id uint64, offset int64, key []byte) error {
if offset <= idx.maxOffset {
return nil
}
idx.execEntry(flag, id, offset, key)
return nil
}); err != nil {
return err
}
}
return nil
}
这里的逻辑有点意思,只恢复了部分segment数据,也就是maxOffset对应的segment以及之后的数据,才会在recover里面被重建索引。重建的过程就是遍历所有series,然后插入到index中。
func (idx *SeriesIndex) execEntry(flag uint8, id uint64, offset int64, key []byte) {
switch flag {
case SeriesEntryInsertFlag:
idx.keyIDMap.Put(key, id)
idx.idOffsetMap[id] = offset
if id > idx.maxSeriesID {
idx.maxSeriesID = id
}
if offset > idx.maxOffset {
idx.maxOffset = offset
}
case SeriesEntryTombstoneFlag:
idx.tombstones[id] = struct{}{}
default:
panic("unreachable")
}
}
这里同时也会动态的更新maxOffset和maxSeriesID。这里恢复部分索引应该是基于内存的考虑。重建所有索引内存消耗挺大。
~~~
SeriesIndex提供了一些查找函数:
- FindIDByNameTags:这个函数通过给定的measurement+tags查询SeriesId
- FindIDListByNameTags是FindIDByNameTags传数组版本
- FindOffsetByID 根据seriesId寻找offset
上述提到了,index里面只有部分数据保存到了内存里面,但是还有部分其实没有在内存。如果cache miss,就会计算offset,到文件中寻找。
SeriesFile
上面中断了一下分析了一下seriesIndex结构,seriesIndex结构总结一下就是,对当前SeriesPartition 比较活跃的几个SeriesSegment做了一下内存索引,并且保存了当前所有SeriesSegment的SeriesId信息和Offset信息,这部分是被保存在磁盘上的。那么接下来分析最顶层的结构:SeriesFile
// SeriesFile represents the section of the index that holds series data.
type SeriesFile struct {
path string
partitions []*SeriesPartition
maxSnapshotConcurrency int
refs sync.RWMutex // RWMutex to track references to the SeriesFile that are in use.
Logger *zap.Logger
}
从注释里面可以看出来,**seriesfile代表了index hold的series相关的data。**注意这里的index指的是influxdb的index模块,并不是SeriesIndex。从这个结构可以看到两个基本信息:
- SeriesFile有多个SeriesPartition
- 支持snapshot,并且控制snapshot的并发度。
New和Open
也是一下New和Open函数。
// NewSeriesFile returns a new instance of SeriesFile.
func NewSeriesFile(path string) *SeriesFile {
maxSnapshotConcurrency := runtime.GOMAXPROCS(0)
if maxSnapshotConcurrency < 1 {
maxSnapshotConcurrency = 1
}
return &SeriesFile{
path: path,
maxSnapshotConcurrency: maxSnapshotConcurrency,
Logger: zap.NewNop(),
}
}
// Open memory maps the data file at the file's path.
func (f *SeriesFile) Open() error {
// Wait for all references to be released and prevent new ones from being acquired.
f.refs.Lock()
defer f.refs.Unlock()
// Create path if it doesn't exist.
if err := os.MkdirAll(filepath.Join(f.path), 0777); err != nil {
return err
}
// Limit concurrent series file compactions
compactionLimiter := limiter.NewFixed(f.maxSnapshotConcurrency)
// Open partitions.
f.partitions = make([]*SeriesPartition, 0, SeriesFilePartitionN)
for i := 0; i < SeriesFilePartitionN; i++ {
p := NewSeriesPartition(i, f.SeriesPartitionPath(i), compactionLimiter)
p.Logger = f.Logger.With(zap.Int("partition", p.ID()))
if err := p.Open(); err != nil {
f.Logger.Error("Unable to open series file",
zap.String("path", f.path),
zap.Int("partition", p.ID()),
zap.Error(err))
f.close()
return err
}
f.partitions = append(f.partitions, p)
}
return nil
}
这里的逻辑比较简单,并发度是和当前机器环境相关的,Open就是Open所有的seriesPartition。不再赘述。接下来看一下非常重要的函数:CreateSeriesListIfNotExists
CreateSeriesListIfNotExists
这个函数做所以被单独拿出来,读过上一章的朋友们可能知道,这是我们留下的一个疑问,其实这个函数也是贯穿上述结构的根本所在。
首先看这个函数在SeriesFile中的实现:
func (f *SeriesFile) CreateSeriesListIfNotExists(names [][]byte, tagsSlice []models.Tags) ([]uint64, error) {
keys := GenerateSeriesKeys(names, tagsSlice)
keyPartitionIDs := f.SeriesKeysPartitionIDs(keys)
ids := make([]uint64, len(keys))
var g errgroup.Group
for i := range f.partitions {
p := f.partitions[i]
g.Go(func() error {
return p.CreateSeriesListIfNotExists(keys, keyPartitionIDs, ids)
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return ids, nil
}
比较简介,分为三步:
- 生成seriesKey
- 生成seriesKey-> seriesPartition的映射
- 调用SeriesPartition的CreateSeriesListIfNotExists函数
第一步就跳过了,比较简单。我们来看第二步:
// SeriesKeysPartitionIDs 选择具体的partition
func (f *SeriesFile) SeriesKeysPartitionIDs(keys [][]byte) []int {
partitionIDs := make([]int, len(keys))
for i := range keys {
partitionIDs[i] = f.SeriesKeyPartitionID(keys[i])
}
return partitionIDs
}
func (f *SeriesFile) SeriesKeyPartitionID(key []byte) int {
// 给每个series选取partition的逻辑
return int(xxhash.Sum64(key) % SeriesFilePartitionN)
}
func (f *SeriesFile) SeriesKeyPartition(key []byte) *SeriesPartition {
partitionID := f.SeriesKeyPartitionID(key)
if partitionID >= len(f.partitions) {
return nil
}
return f.partitions[partitionID]
}
第二步有三个函数,总结一下就是通过key,使用xxhash生成hash值,对SeriesFilePartitionN取模得到seriesPartition的下标。 最后返回一个数组,partitionIDs[i]表示第i个key应该写到哪个SeriesPartition。
有了第二步的信息,就开始调用SeriesPartition的CreateSeriesListIfNotExists进行写入。
func (p *SeriesPartition) CreateSeriesListIfNotExists(keys [][]byte, keyPartitionIDs []int, ids []uint64) error {
}
这个函数的签名如上所示,key就是生成的key数组,keyPartitionIDs是生成的partition分组信息,ids是需要返回的参数,ids[i]表示第i个key的seriesId。函数开始部分,首先从index里面检索,把已经存在的series key 的id找出来并且赋值:
var writeRequired bool
p.mu.RLock()
if p.closed {
p.mu.RUnlock()
return ErrSeriesPartitionClosed
}
for i := range keys {
if keyPartitionIDs[i] != p.id {
continue
}
id := p.index.FindIDBySeriesKey(p.segments, keys[i])
if id == 0 {
writeRequired = true
continue
}
ids[i] = id
}
p.mu.RUnlock()
接下来遍历所有的key,对于没有ids[i]没有被赋值的位置,尝试写入series:
// Track offsets of duplicate series.
newIDs := make(map[string]uint64, len(ids))
for i := range keys {
// Skip series that don't belong to the partition or have already been created.
if keyPartitionIDs[i] != p.id || ids[i] != 0 {
continue
}
// Re-attempt lookup under write lock.
key := keys[i]
if ids[i] = newIDs[string(key)]; ids[i] != 0 {
continue
} else if ids[i] = p.index.FindIDBySeriesKey(p.segments, key); ids[i] != 0 {
continue
}
// Write to series log and save offset.
id, offset, err := p.insert(key)
if err != nil {
return err
}
// Append new key to be added to hash map after flush.
ids[i] = id
newIDs[string(key)] = id
newKeyRanges = append(newKeyRanges, keyRange{id, offset})
}
并且保存seirsId和offset。这里深入一下,看一下insert逻辑。
func (p *SeriesPartition) insert(key []byte) (id uint64, offset int64, err error) {
id = p.seq
offset, err = p.writeLogEntry(AppendSeriesEntry(nil, SeriesEntryInsertFlag, id, key))
if err != nil {
return 0, 0, err
}
p.seq += SeriesFilePartitionN
return id, offset, nil
}
这里可以看出来id生成规则:seq+=SeriesFilePartitionN,这个上面其实已经提到过。这里写入或写到active的segment。最后是刷盘和index的更新,保证信息不丢
// Flush active segment writes so we can access data in mmap.
if segment := p.activeSegment(); segment != nil {
if err := segment.Flush(); err != nil {
return err
}
}
// Add keys to hash map(s).
for _, keyRange := range newKeyRanges {
p.index.Insert(p.seriesKeyByOffset(keyRange.offset), keyRange.id, keyRange.offset)
}
到这里series算是新建完成。其实后面还有一部分逻辑,就是出发index的compact:
if p.compactionsEnabled() && !p.compacting &&
p.CompactThreshold != 0 && p.index.InMemCount() >= uint64(p.CompactThreshold) &&
p.compactionLimiter.TryTake() {
p.compacting = true
log, logEnd := logger.NewOperation(p.Logger, "Series partition compaction", "series_partition_compaction", zap.String("path", p.path))
p.wg.Add(1)
go func() {
defer p.wg.Done()
defer p.compactionLimiter.Release()
compactor := NewSeriesPartitionCompactor()
compactor.cancel = p.closing
if err := compactor.Compact(p); err != nil {
log.Error("series partition compaction failed", zap.Error(err))
}
logEnd()
// Clear compaction flag.
p.mu.Lock()
p.compacting = false
p.mu.Unlock()
}()
}
可以看到compact的逻辑,注意,这里的compact是SeriesIndex的compact,这个compact的作用是把内存中的数据刷一部分到磁盘上去,因为内存中的index数量是有上限的。上面也提到了SeriesSegment的compact,和这里的index的compact不是一个。不要混淆。
~~~
介绍完CreateSeriesListIfNotExists 其实series基本就差不多了,这篇文章也挺长了,行文至此,准备结束。
总结
本篇文章介绍了influxdb series模块的存储和结构。可以看到里面还是有大量的值得学习和借鉴的细节。还有部分比较细节的内容留在下一章介绍,下一章准备series Index compact过程,以及index模块中,是怎么使用series模块的。