VictoriaMetrics源码:索引文件结构

VictoriaMetrics的索引文件,保存在{storagePath}/indexdb目录下:

# tree ./indexdb/ -L 1
./indexdb/
├── 1759A6CAD53ABA2E
├── 1759A6CAD53ABA2F
└── snapshots

一. 基本概念

indexdb目录下,通常有2个索引目录:

  • 一个为pre,即上个retentionPeriod使用的索引;
  • 一个为cur,即当前retentionPeriod使用的索引;

当retentionPeriod到期后,将删掉pre,将cur配置为pre,同时创建新的索引目录作为cur;
保留2个目录后,可以防止retentionPeriod到期后,没有index可用的现象。

pre与cur的索引目录,在代码中被称为table

1. part

table目录下,包含若干个子目录:

  • 每个子目录,被称为一个part
# ls indexdb/1759A6CAD53ABA2F -alh
总用量 4.0K
drwxr-xr-x 28 root root 4.0K 5月   5 09:01 .
drwxr-xr-x  5 root root   71 4月  27 09:35 ..
drwxr-xr-x  2 root root   98 5月   5 08:00 1141_2_1759A6CADA292254
drwxr-xr-x  2 root root   98 5月   5 07:16 13868_28_1759A6CADA29209F
drwxr-xr-x  2 root root   98 5月   5 07:53 1574_3_1759A6CADA29220F
drwxr-xr-x  2 root root   98 5月   5 07:52 16467_45_1759A6CADA292207

2. 索引文件

每个part目录下,包含若干个索引文件:

# ls indexdb/1759A6CAD53ABA2F/1141_2_1759A6CADA292254/ -alh
总用量 36K
drwxr-xr-x  2 root root   98 5月   5 08:00 .
drwxr-xr-x 28 root root 4.0K 5月   5 09:01 ..
-rw-r--r--  1 root root  149 5月   5 08:00 index.bin
-rw-r--r--  1 root root  15K 5月   5 08:00 items.bin
-rw-r--r--  1 root root 2.3K 5月   5 08:00 lens.bin
-rw-r--r--  1 root root  335 5月   5 08:00 metadata.json
-rw-r--r--  1 root root   48 5月   5 08:00 metaindex.bin

其中:

  • metaindex.bin:

    • 保存[]metaindexRow,用于索引index.bin;
    • 文件内容会被加载到内存,以加速查询;
  • index.bin:

    • 保存[]indexBlock,用于索引items.bin和lens.bin;
  • items.bin:

    • 保存index items的内容;
    • index item即各种索引项及其索引内容;
  • lens.bin:

    • 保存items的len信息;

VictoriaMetrics源码:索引文件结构_第1张图片

二. 索引文件中的Item

items.bin文件保存索引的item内容,item用以描述索引数据,以KV结构存储,共有7种item类型:

const (
    // Prefix for MetricName->TSID entries.
    nsPrefixMetricNameToTSID = 0

    // Prefix for Tag->MetricID entries.
    nsPrefixTagToMetricIDs = 1

    // Prefix for MetricID->TSID entries.
    nsPrefixMetricIDToTSID = 2

    // Prefix for MetricID->MetricName entries.
    nsPrefixMetricIDToMetricName = 3

    // Prefix for deleted MetricID entries.
    nsPrefixDeletedMetricID = 4

    // Prefix for Date->MetricID entries.
    nsPrefixDateToMetricID = 5

    // Prefix for (Date,Tag)->MetricID entries.
    nsPrefixDateTagToMetricIDs = 6
)

items的创建代码:

func (is *indexSearch) createGlobalIndexes(tsid *TSID, mn *MetricName) error {
    // The order of index items is important.
    // It guarantees index consistency.

    ii := getIndexItems()            // type indexItems
    defer putIndexItems(ii)

    // Create MetricName -> TSID index.
    ii.B = append(ii.B, nsPrefixMetricNameToTSID)
    ii.B = mn.Marshal(ii.B)
    ii.B = append(ii.B, kvSeparatorChar)
    ii.B = tsid.Marshal(ii.B)
    ii.Next()

    // Create MetricID -> MetricName index.
    ii.B = marshalCommonPrefix(ii.B, nsPrefixMetricIDToMetricName, mn.AccountID, mn.ProjectID)
    ii.B = encoding.MarshalUint64(ii.B, tsid.MetricID)
    ii.B = mn.Marshal(ii.B)
    ii.Next()

    // Create MetricID -> TSID index.
    ii.B = marshalCommonPrefix(ii.B, nsPrefixMetricIDToTSID, mn.AccountID, mn.ProjectID)
    ii.B = encoding.MarshalUint64(ii.B, tsid.MetricID)
    ii.B = tsid.Marshal(ii.B)
    ii.Next()

    prefix := kbPool.Get()
    prefix.B = marshalCommonPrefix(prefix.B[:0], nsPrefixTagToMetricIDs, mn.AccountID, mn.ProjectID)
    ii.registerTagIndexes(prefix.B, mn, tsid.MetricID)
    kbPool.Put(prefix)

    return is.db.tb.AddItems(ii.Items)
}

三. table目录的轮转

indexdb目录下有2个table目录,一个是pre,一个是cur。

这两个目录在VictoriaMetrics启动时,被自动创建出来。

// lib/storage/storage.go
func (s *Storage) openIndexDBTables(path string) (curr, prev *indexDB, err error) {
    ...
    var tableNames []string
    for _, fi := range fis {
        if !fs.IsDirOrSymlink(fi) {
            continue
        }
        tableName := fi.Name()
        if !indexDBTableNameRegexp.MatchString(tableName) {
            // Skip invalid directories.
            continue
        }
        tableNames = append(tableNames, tableName)
    }
    sort.Slice(tableNames, func(i, j int) bool {
        return tableNames[i] < tableNames[j]
    })
    // 若目录数量 < 2
    if len(tableNames) < 2 {
        // 没有目录,创建1个
        if len(tableNames) == 0 {
            prevName := nextIndexDBTableName()
            tableNames = append(tableNames, prevName)
        }
        // 再创建1个
        currName := nextIndexDBTableName()
        tableNames = append(tableNames, currName)
    }
    ...
    currPath := path + "/" + tableNames[len(tableNames)-1]
    curr, err = openIndexDB(currPath, s, 0, &s.isReadOnly)
    ...
    prevPath := path + "/" + tableNames[len(tableNames)-2]
    prev, err = openIndexDB(prevPath, s, 0, &s.isReadOnly)
    ...
    return curr, prev, nil
}

当到达retentionPeriod时,发生目录的轮转,cur变成pre,创建新目录变成cur。

VictoriaMetrics的代码中,使用一个定时器实现:

// lib/storage/storage.go
func (s *Storage) retentionWatcher() {
    for {
        d := nextRetentionDuration(s.retentionMsecs)    // retentionPeriod
        select {
        case <-s.stop:
            return
        case <-time.After(d):
            s.mustRotateIndexDB()
        }
    }
}

轮转的逻辑:

  • 首先,创建新的indexdb;
  • 然后,标记删掉老的indexdb;
  • 最后,配置cur=新的indexdb,同时exDB=老的indexdb;
// lib/storage/storage.go
func (s *Storage) mustRotateIndexDB() {
    // 1.创建新的
    newTableName := nextIndexDBTableName()
    idbNewPath := s.path + "/indexdb/" + newTableName
    rotationTimestamp := fasttime.UnixTimestamp()
    idbNew, err := openIndexDB(idbNewPath, s, rotationTimestamp, &s.isReadOnly)
    
    // 2.删掉老的
    idbCurr := s.idb()
    idbCurr.doExtDB(func(extDB *indexDB) {
        extDB.scheduleToDrop()
    })
    idbCurr.SetExtDB(nil)

    // 3.使用新的
    idbNew.SetExtDB(idbCurr)
    s.idbCurr.Store(idbNew)

    // Persist changes on the file system.
    fs.MustSyncPath(s.path)
    ...
}

参考:

1.https://zhuanlan.zhihu.com/p/368912946
2.https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247499...
3.https://mp.weixin.qq.com/s/fa_3TJuZ-p2uzjUvqAwung

你可能感兴趣的:(时序数据库)