共计 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