作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


本文是为了解决Victoria-Metrics中的过载问题而得出的较好实践。也是为了回答这样一些问题:

  • vm-storage的单核写入性能的极限在哪里?(请见之前的三篇文章)
  • 怎么样才能感知到,vm-insert到vm-storage这条写入链路上已经发生了过载?
  • 如果写入链路发生过载,是否会丢数据?如果丢数据,究竟在哪个环节丢数据?过载了如何做保护,如何避免雪崩?

下面开始回答以下问题:

1.vm-storage的单核写入性能的极限在哪里?

  • 全是新metric的情况:6000/核/s
  • 全是旧的metric的情况:43万/核/s
  • 新metric占整体1%的情况:24.6万/核/s

此外:写入性能是否会随着时间推移索引数增多后,导致写入性能下降?

会的!原因在于内存占用量升高,tsid cache命中率降低导致。

只要活跃的metric都在tsid cache中,写入性能就不会受到影响。

2.写入链路过载感知

2.1 vm-storage中,几乎不会发生丢数据的情况

统计drop相关的metric,可以看出vm-storage上丢弃的数据量:

  1. sum by () (rate(vm_hourly_series_limit_rows_dropped_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"})) +
  2. sum by () (rate(vm_daily_series_limit_rows_dropped_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"})) +
  3. sum by () (rate(vm_concurrent_addrows_dropped_rows_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"}))

或者,把接收到的行数为分母,把最终写入存储的行数为分子,可以计算出vm-storage上的写入成功率:

  1. sum by () (rate(vm_rows_added_to_storage_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"}[1m])) /
  2. sum by () (rate(vm_vminsert_metrics_read_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.+"}[1m]))

在没有刻意限制每小时和每天的新metric数量的情况下,实测过程中没有发现任何vm-storage上的数据丢弃。

2.2 vm-insert中的数据丢弃逻辑:

通过配置 -dropSamplesOnOverload 来丢弃数据

see: app/vminsert/netstorage/netstorage.go:53

  1. func (sn *storageNode) push(buf []byte, rows int) error {
  2. if len(buf) > maxBufSizePerStorageNode {
  3. logger.Panicf("BUG: len(buf)=%d cannot exceed %d", len(buf), maxBufSizePerStorageNode)
  4. }
  5. sn.rowsPushed.Add(rows)
  6. if sn.trySendBuf(buf, rows) {
  7. // Fast path - the buffer is successfully sent to sn.
  8. return nil
  9. } // 只要缓冲区写满,且配置了丢弃数据,就会直接丢弃数据。丢弃数据能够避免vm-insert OOM崩溃
  10. if *dropSamplesOnOverload && atomic.LoadUint32(&sn.isReadOnly) == 0 {
  11. sn.rowsDroppedOnOverload.Add(rows)
  12. dropSamplesOnOverloadLogger.Warnf("some rows dropped, because -dropSamplesOnOverload is set and vmstorage %s cannot accept new rows now. "+
  13. "See vm_rpc_rows_dropped_on_overload_total metric at /metrics page", sn.dialer.Addr())
  14. return nil
  15. }
  16. // Slow path - sn cannot accept buf now, so re-route it to other vmstorage nodes.
  17. if err := sn.rerouteBufToOtherStorageNodes(buf, rows); err != nil {
  18. return fmt.Errorf("error when re-routing rows from %s: %w", sn.dialer.Addr(), err)
  19. }
  20. return nil
  21. }

由源码可知:当vm-storage达到瓶颈,vm-insert与vm-storage之间的传输必然变慢,变慢后导致netstorage客户端的缓冲区无法及时释放。这个时候如果配置了-dropSamplesOnOverload选项,就会直接丢弃数据。

丢弃数据后,通过metric vm_rpc_rows_dropped_on_overload_total 会体现出丢弃的量。

虽然这个配置项能够避免vm-insert发生OOM(Out of memory)崩溃,但是这个层面上的数据丢弃无法补救。

建议vm-insert不要配置-dropSamplesOnOverload选项

vm-insert的http remote write协议层面

作为http服务器,可以看看vm-insert在http协议上如何处理过载:

see: lib/writeconcurrencylimiter/concurrencylimiter.go:32

  1. // Do calls f with the limited concurrency.
  2. func Do(f func() error) error {
  3. // Limit the number of conurrent f calls in order to prevent from excess
  4. // memory usage and CPU thrashing.
  5. select {
  6. case ch <- struct{}{}: // 放入channel成功代表允许的并发数足够
  7. err := f() // 执行具体的协议回调函数
  8. <-ch
  9. return err
  10. default:
  11. }
  12. // All the workers are busy.
  13. // Sleep for up to *maxQueueDuration.
  14. concurrencyLimitReached.Inc()
  15. t := timerpool.Get(*maxQueueDuration) // maxQueueDuration 默认60秒,也就是无法处理的请求最长要等待60秒
  16. select {
  17. case ch <- struct{}{}:
  18. timerpool.Put(t)
  19. err := f()
  20. <-ch
  21. return err
  22. case <-t.C:
  23. timerpool.Put(t)
  24. concurrencyLimitTimeout.Inc()
  25. return &httpserver.ErrorWithStatusCode{
  26. Err: fmt.Errorf("cannot handle more than %d concurrent inserts during %s; possible solutions: "+
  27. "increase `-insert.maxQueueDuration`, increase `-maxConcurrentInserts`, increase server capacity", *maxConcurrentInserts, *maxQueueDuration),
  28. StatusCode: http.StatusServiceUnavailable, // 等待60秒后仍然没有资源,则向调用端返回http 503错误
  29. }
  30. }
  31. }

由源码可知:后端过载后,发送数据变慢,变慢导致用于处理并发的协程长时间阻塞。新来的请求没有协程去处理,就进入了等待。等待足够长的时间仍然没有资源,就向调用端返回http 503错误码。

把无法处理的请求通过错误码返回,这样调用方就能够感知到后端的过载了。

通过metric可以查询到vm-insert上拒绝的请求数:vm_http_request_errors_total{protocol="promremotewrite"}

到这里,怎么样才能感知到,vm-insert到vm-storage这条写入链路上已经发生了过载?这个问题已经有了答案:

1.关闭-dropSamplesOnOverload选项,不要在vm-insert这个层面上丢包;

2.通过http remote write的状态码 http 503 来感知后端是否已经过载;

vm-insert仍然有其他细节可能导致丢包,比如配置了 -maxInsertRequestSize / -maxLabelValueLen / -maxLabelsPerTimeseries 等选项的情况下。

可以通过下面的表达式来查询vm-insert这个环节的转发成功率:

  1. sum by () (rate(vm_rpc_rows_sent_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*"})) /
  2. sum by () (rate(vm_rows_inserted_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*",type="promremotewrite"}))

如何合理配置vm-insert的参数

首先,分析一下vm-insert写数据到vm-storage的延迟是多少:

  1. max by () (rate(vm_rpc_send_duration_seconds_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*"}[1m]))

我实验群集查询出来的数值是 0.996~1.01秒。

-maxConcurrentInserts 参数不配置的情况下,每核的默认并发为4.

因此,每协程的延迟大约是: 1000ms / 4核 / 4 协程 = 62.5ms

vm-insert与vm-storage之间的延迟如此之短,所以 -insert.maxQueueDuration=60s 这个时间实在太长了。我在压测中发现,-insert.maxQueueDuration=2s能够带来很好的吞吐量,并使vm-insert的内存压力很小。

-insert.maxQueueDuration=2s后,remote write客户端的请求延迟要略大于2s。这样的话,vm写入链路上如果发生过载,绝大多数的过载请求都会通过 http 503来体现。

总结一下:

  • 通过metric max by () (rate(vm_rpc_send_duration_seconds_total{tenant=~"$tenant",namespace=~"$namespace",env_name=~"$env_name",pod_name=~"${cluster}.*"}[1m])) 来计算实例的最大延迟。然后除以总的协程数,得到每个协程的延迟。
  • 队列中的等待时间insert.maxQueueDuration要与上面得到的延迟值相对等同。否则,过载情况下会积累非常多远超于延迟时间的请求,容易导致vm-insert发生OOM
  • vm-insert的参数-maxConcurrentInserts建议不配置,使用默认的每核4协程的并发量。
    • 协程数增加对于vm-insert的吞吐量没有明显提升,反而导致增加协程调度的消耗。
    • 实际压测中发现每核4协程的吞吐量很好,瓶颈还是在vm-storage一侧。
  • 关闭-dropSamplesOnOverload,把过载的信息传给上游。然后由上游来选择丢弃还是重试。

3.过载的一系列问题

如果写入链路发生过载,是否会丢数据?如果丢数据,究竟在哪个环节丢数据?过载了如何做保护,如何避免雪崩?

综合上面的信息:

  • 如果不在vm-insert上配置-dropSamplesOnOverload选项,几乎不会发生丢数据

    • 如果需要知道在某些数据合法性检查的环节是否有丢数据,可以通过vm中的某些metric计算出来具体丢了多少;(压测过程中一次也没发生过)
  • 丢数据的最大环节仍然在vm-insert上,导致丢弃的原因是netstorage客户端的buffer写满。
  • 过载保护最好的位置是在remote write的发送端,通过http 503错误码来感知后端是否过载。
    • vm-storage过载后,表现为CPU耗满,插入时没有对应的可调度协程。最终导致vm-insert端连接超时。也就是说,vm-storage自身不会因为写入太多而发生崩溃。
    • vm-insert的过载一般由于vm-storage变慢导致。过载后,等待处理的缓冲区的数据变多,没有可用的insert协程,最终触发超时,返回http 503错误。只要insert.maxQueueDuration参数设置合理,一般也不会发生vm-insert上的崩溃。

总结

  • 把vm-insert和vm-storage看成一个整体,在这条路径上可以配置为不丢弃数据;
  • 把过载的检查和过载后的等待/重试等任务交给remote write的客户端;
  • vm-storage的单核性能极为强悍,且能够做到水平扩容,可以认为其数据写入能力是没有上限的。

vm-insert到vm-storage链路上的配置说明的更多相关文章

  1. Linux虚拟内存(swap)调优篇-“swappiness”,“vm.dirty_background_ratio”和“vm.dirty_ratio”

      Linux虚拟内存(swap)调优篇-“swappiness”,“vm.dirty_background_ratio”和“vm.dirty_ratio” 作者:尹正杰 版权声明:原创作品,谢绝转载 ...

  2. centos6 下FastDFS 在storage节点上nginx的fastdfs-nginx-module 模块编译出现的问题

    centos6.6  下FastDFS  在storage节点上   make(编译)nginx的fastdfs-nginx-module 出现如下报错: /root/fastdfs-nginx-mo ...

  3. 攻击链路识别——CAPEC(共享攻击模式的公共标准)、MAEC(恶意软件行为特征)和ATT&CK(APT攻击链路上的子场景非常细)

    结合知识图谱对网络威胁建模分析,并兼容MITRE组织的CAPEC(共享攻击模式的公共标准).MAEC和ATT&CK(APT攻击链路上的子场景非常细)等模型的接入,并从情报中提取关键信息对知识图 ...

  4. SQL VM上磁盘延迟高, 但Host和Storage Array上的延迟却很低的问题

    按照下面的步骤, 问题解决. =========================== Per Microsoft DDK, Microsoft storport.sys maintains a dev ...

  5. 虚拟机最佳实践:单个 VM、临时存储和已上传磁盘

    大家好! 我是 Drew McDaniel,来自 Microsoft Azure虚拟机功能研发团队,我从团队成立之初就已加入. 在本博客文章中,我将分享一些最佳实践指南,帮助您充分利用您的Azure虚 ...

  6. vm.dirty_background_ratio and vm.dirty_ratio

    http://hellojava.info/?p=264&utm_source=tuicool&utm_medium=referral 解决磁盘io紧张的一种临时方法 有些时候可能会碰 ...

  7. Standard 1.1.x VM与Standard VM的区别

    在Eclipse或MyEclipse中要设置Installed JREs时,有三个选择: - Execution Environment Description - Standard 1.1.x VM ...

  8. Azure Storage 分块上传

    概述 Azure 存储提供三种类型的 Blob:块 Blob.页 Blob 和追加 Blob.其中,块 Blob 特别适用于存储短的文本或二进制文件,例如文档和媒体文件. 块 Blob 由块组成,每个 ...

  9. Java HotSpot(TM) Client VM 与 server VM 的配置

    在Linux 6.5 下安装Elasticsearch 出现错误: JVM is using the client VM [Java HotSpot(TM) Client VM] but should ...

  10. 1, vm: PropTypes.instanceOf(VM).isRequired

    子模块的文件引入父工程对象时,出现红色warning,提示传入的对象类型不是所要求的类型. 思路是父工程引用的JS包和子模块使用的包不是同一个包,解决办法是父工程和子工程都使用同一个包. resolv ...

随机推荐

  1. 【软件安装记录篇】本地虚拟机Centos7快速安装MySQL

    安装 采用Installing MySQL on Linux Using the MySQL Yum Repository的方式进行安装. 1. 首先下载服务器版本对应的rpm文件,下载地址:[链接] ...

  2. FOR ALL ENTRIES IN 与 INNER JOIN 内表

    1.区别 FOR ALL ENTRIES IN 与 INNER JOIN 内表,目的都是通过内表找数据库表与之对应的数据,但是有区别. 1.1.写法 FOR ALL ENTRIES IN " ...

  3. AcWing 每日一题 - 暑假

    本篇解题记录题源来自 AcWing 的每日一题 · 暑假 补题链接:Here Week 1 星期四 AcWing 3761. 唯一最小数 利用 map 存出现过数的下标和次数即可 vector< ...

  4. Codeforces Round #690 (Div. 3) (简单题解记录)

    Codeforces Round #690 (Div. 3) 1462A. Favorite Sequence 简单看懂题即可,左边输出一个然后右边输出一个. void solve() { int n ...

  5. 二、docker的安装和镜像管理

    系列导航 一.docker入门(概念) 二.docker的安装和镜像管理 三.docker容器的常用命令 四.容器的网络访问 五.容器端口转发 六.docker数据卷 七.手动制作docker镜像 八 ...

  6. Element 动态表头渲染表格

    element 中的table表头动态渲染 https://blog.csdn.net/heixiuheixiu666/article/details/104705024/ Element 动态表头渲 ...

  7. slot-具名插槽

    定义组件:NamedSlot组件 <div class=""> <header> <slot name="header">& ...

  8. Git | git branch 分支操作

    假设我们已经有了稳定的代码,现在我想整一些花活.比较安全的一个方式是,在新的分支上整活. 新建 vga 分支:git branch vga,然后切换到 vga 分支:git switch vga,或者 ...

  9. ClickHouse的Join算法

    ClickHouse的Join算法 ClickHouse是一款开源的列式分析型数据库(OLAP),专为需要超低延迟分析查询大量数据的场景而生.为了实现分析应用可能达到的最佳性能,分析型数据库(OLAP ...

  10. crypto常用算法

    欧几里得算法(辗转相除法) def gcd(a, b): if b == 0: return a else: return gcd(b, a % b) 扩展欧几里得算法 def ext_euclid( ...