关于时序数据库:VictoriaMetrics源码索引文件结构

6次阅读

共计 4569 个字符,预计需要花费 12 分钟才能阅读完成。

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 信息;

二. 索引文件中的 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

正文完
 0