• 2021-09-04

    记一次关于系统性能的有趣讨论

    Views: 14679 | No Comments

    有个同事问我:"你开发的分布式数据库系统, 如何避免 scan 扫描操作返回了 pending 事务状态的数据?"

    我说:"把数据扫描出来, 然后逐个判断过滤掉 pending 状态的数据."

    我感到奇怪, 对于他的问题, 解决方案非常显然啊. 解决方案非常直观, 兵来将挡水来土掩, 我相信他也能想到, 不想要的数据当然要剔除掉, 否则呢? 那么, 他的问题的点在哪?

    没错, 他接着问了:"我 scan 的时候只想返回 key, 但是, 要判断状态, 是不是还得去读取 value 解析出状态信息?"

    我当时是黑人问号脸:"当然要知道状态信息才能根据状态过滤呀, 不然呢?"

    他终于祭出了那个经典的让人哭笑不得的问题:"读取 value, 是不是性能会下降啊?"

    Continue reading »

    Posted by ideawu at 2021-09-04 10:29:04
  • 2021-08-27

    企业级SSD硬盘fsync速度

    Views: 13450 | No Comments

    小数据测试, 以便对硬盘 fsync 的速度有一个大概的了解. 结果:

    rate latency 备注
    4044/s 0.247ms Intel SATA SSD
    19720/s 0.051ms Intel NVMe SSD

    结论: SATA 盘的 QPS 是 4000, NVMe 的 QPS 是 20000.

    如果要开发一个分布式 KV 数据库, 那么对于每一个客户端请求, 至少进行 1 次日志 fsync. 为了提高吞吐量(QPS), 日志模块必须进行 batch 持久化.

    如果 batch 大小是 25 的话, 普通 SATA SSD 盘能达到 10w qps, 而 NVMe SSD 只需要 batch 是 5 即可达到 10w qps.

    Posted by ideawu at 2021-08-27 20:42:53
  • 2021-08-21

    生产者消费者模式的系统性能分析方法

    Views: 13578 | No Comments

    前一篇文章介绍了生产者消费者编程模式, 一种非常流行且强大的编程模式. 本文将分析采用这种模式的系统的性能分析方法, 以做性能优化.

    系统性能分析主要关注这几个指标:

    • qps/rps(queries per second, requests per second) - 每秒处理请求数, 也即吞吐量(throughput), 一般也称为处理速度的大小, 通俗也称为性能
    • latency - 单次请求处理的耗时. 一般关注平均值(avg), 最大值(max), 最小值(min), 百分位(percentile), 一般也称为处理速度的快慢, 通俗也称为性能
    • 成功率, 失败率 - 因为网络抖动或者其它原因, 某些请求的处理会成功/失败, 成功/失败请求占的比例, 失败请求不计算在 qps 里

    其中, qps 和 latency 在通俗语义在常常统称为"性能", 这会给科学分析带来歧义, 所以我们要特别注意弄清楚并区分. qps 和 latency 的关系公式为:

    qps = 1 / avg_latency    // 时间单位为秒
    avg_latency = 1 / qps    // 时间单位为秒
    

    Continue reading »

    Posted by ideawu at 2021-08-21 12:04:42
  • 2021-08-15

    生产者消费者编程模式

    Views: 11512 | No Comments

    相信很多人都知道"生产者消费者"编程模式, 也使用过这种模式, 但是, 可能只是本能不自觉地使用过, 未必对这种模式有清晰和深刻的理解. 特别是级联生产者消费者模式, 更是强大无比. 很多人可能没有意识到, Golang 语言的核心思想正是生产者消费者模式, 也即 go routine + channel.

    假设有一个功能, 处理某个任务需要进行3个步骤, 那么代码可以这样写:

    func worker(t *Task) {
        step1(t)
        step2(t)
        step3(t)
    }
    
    for t := recv_task(); t != nil {
        worker(t)
    }
    

    这样的代码非常直观, 也是我所推崇的程序设计核心原则之一. 不过, 这段代码毕竟是串行化的, 中间某个步骤会阻塞后面的步骤.

    对于串行化阻塞问题的最直接解决方案就是多线程并发. 例如, 每收到一个任务, 就启动一个线程去处理:

    for t := recv_task(); t != nil {
        go worker() // 启动线程/协程
    }
    

    这种并发模式有一定的缺陷, 一是并发数量不受控制, 二是如果做了并发数量控制, 这样的控制也不高效. 例如限制最多只有 N 个 worker 线程在同时运行, 如果刚好这 N 个线程都被阻塞在某一个步骤时, 那么整个系统就都没有在工作, 是被闲置的.

    Continue reading »

    Posted by ideawu at 2021-08-15 09:42:47
  • 2021-07-23

    复杂软件系统开发的第一原则: KISS

    Views: 4846 | No Comments

    俗话说:

    Keep It Simple, Stupid!

    由于大部分新手程序员在从学生转换成为工程师之前, 都经过所谓的"算法"编程训练, 特别是不少人还主动进行大量的"刷题"行为, 因此, 对"性能"的追求被潜移默化地植入了所有程序员的基因, 这就造成了程序员往往把细节上的所谓性能优化放到第一优先的位置.

    这种片面追求细节性能, 从而缺少大局观的思维, 其实是非常错误的. 比如 C++ 程序员, 几乎把性能优化等同于减少内存拷贝和无锁(lock free), 认为内存拷贝等于性能差, 认为加锁等于阻塞, 因此, C++ 程序员的代码主要在语法和语句层面上做优化, 逻辑非常别扭. 这种观念是非常落后的, 没有大局观, 这种程序员的技术视野极其狭窄.

    根据我多年的软件开发经验, 我想针对那些认为"性能最重要"的程序员, 表明一个事实: 所有的代码层面性能优化, 必然以增加逻辑分支, 增加更多的 Indirection 作为代价.

    什么意思呢? 也就是说, 所谓的性能优化, 必然会在代码里增加 if 分支, 增加新的类, 从而造成代码行数膨胀, 类的数量变多, 类的交叉调用关系变得更加复杂混乱. 这些后果都是非常负面的, 甚至无法抵消减少1%内存拷贝带来的性能提升.

    Continue reading »

    Posted by ideawu at 2021-07-23 21:44:28
  • 2021-06-26

    并发编程两原则

    Views: 4474 | No Comments

    之前写过一篇文章, 并发编程的核心技术 – 多版本(Multi Versioning), 本文继续对并发编程做一次更全面的总结, 这样的总结并非具体的编程指导, 而概括性的理论, 是笔记性质的.

    根据经验总结, 并发编程的指导思想可以总结为两个原则, 也即并发编程两原则:

    1. Sharding/Partitioning
    2. Leveling

    Continue reading »

    Posted by ideawu at 2021-06-26 20:17:54
|<<<123456789>>>| 1/13 Pages, 76 Results.