嵌套lambda 捕获shared_ptr引发的bug

bug代码片抽象成这样

#include <iostream>
#include <cstdlib>
#include <string>
#include <functional>
#include <vector>
#include <algorithm>
#include <thread>
#include <memory>
using namespace std;
int main()
{

    std::vector<shared_ptr<int>> v;
    for(int i=0;i<10; i++)
        v.push_back(make_shared<int>(i));
    std::vector<std::thread> threads;
    std::for_each(v.begin(), v.end(),
                  [&](shared_ptr<int> p) {
                  //改成传const引用就没问题了
                  //[&](const shared_ptr<int>& p) {
                    threads.emplace_back(std::move(std::thread([&]() {
                      std::cout << *p<< '\n';
                    })));
                  });
    
    
    /*for (auto &e : v) {
         threads.emplace_back(std::move(std::thread([&]() {
                      std::cout << *e << '\n';
                    })));
    }*/
    for (auto &t :threads)
        t.join();
}

问题出在内部的lambda是捕获引用,但是捕获的值是栈上的,这个栈上分配的值在该场景下是复用的,结果有问题。

嵌套lambda,一定要注意捕获参数。参考链接1有详细的介绍,总结五个常见的嵌套lambda搭配

  1. 传值,捕获值,没问题,但是会有赋值开销
  2. 传值,捕获引用,有问题,传的值是分配在栈上的,捕获引用可能会变,可能不存在等等
  3. 传引用,捕获引用,没问题,但是不能传右值
  4. 传const引用,捕获引用,没问题,但是传的值失去了左值能修改的特性
  5. 传值,移动捕获值,unique_ptr只能这么捕获。也是一个好的捕获方案,省一次拷贝。

上面的案例就犯了2 的错误。改成const 引用就好了

ref

  1. http://lucentbeing.com/writing/archives/nested-lambdas-and-move-capture-in-cpp-14/

  2. 关于lambda嵌套。很复杂。https://zh.cppreference.com/w/cpp/language/lambda

  3. 一个lambda的分析 https://web.mst.edu/~nmjxv3/articles/lambdas.html


Read More

erlang程序设计中文版读书笔记

个人读书笔记,我看书走马观花,侧重点比较诡异,大多流水记录,可读性不大。

入门

  • erl shell,执行最后要加个 .
  • 变量不变, 强制大写开头
  • = 表示模式匹配,不是常规语言中的赋值
    • 变量多次赋值,就会exception error: no match of right hand side value
  • 抛弃副作用,拥抱并行化
  • erlang中原子,表示非数字常量 (很诡异的歧义,注意)
    • 小写字母开头可以用_@连接
  • 每个表达式都必须有值
  • 元组与元组展开。和python c++类似
  • 列表,以及列表头尾快捷操作(有点像模板元)
  • 字符串
    • 就是整数列表,必须双引号,需要大写字母开头
    • $i表示字符i

顺序型编程

  • 一切都是表达式,一切表达式都有值

    • 所以说ruby perl rust那种最后留一个值做返回值的做法算是函数式风格的求值?
  • 模块 和python/js类似 ,-module(module_name) -export(func_name) 导出 -import(module_name, [func_name])
  • 函数
    • 同名不同目,也就是重载支持,可以,只做helper
    • func,也就是lambda,支持作为参数以及返回
  • 控制流对应成匹配,循环,那就用列表生成
  • if/case都是case模式匹配

  • 异常

    • try-catch 也是case模式的,丢失细节
    • erlang:error
    sqrt(X) when X < 0 ->
        erlang:error({squareRootNegativeArgument, X});
    sqrt(X) ->
        math:sqrt(X)
    

    有点像c++ 的constraint

  • 调用栈

    • erlang:get_stacktrace()
  • BIT 内建函数 buildin

    • tuple_to_list
    • 二进制流 «“cat”»
  • 属性-SomeTag()

  • 块表达式,begin end包起来整体求值

  • 宏,引用,这套和c有点像

  • 进程字典,每个进程有自己的私有数据

    • 运行时别用。当成元数据保存还行
  • 引用,全局唯一的值。有点像全局变量。

  • 下划线变量 用完就扔,调试用

编译运行

  • 指定搜索路径

    @spec code:add_patha(Dir) => true |{error, badd_directory}
    @spec code:add_pathz(Dir) => true |{error, badd_directory}
    
  • 编译运行与脚本运行

    • 脚本需要main入口,编译直接调用函数就行
    • 也得写makefile(2020了还手写吗)

并发

  • 并发原语

    • spawn启动一个进程,返回pid

    • a ! b表示发送

    • receive 可以带超时限制
      • 同步?
    • 进程管理 -> 注册进程

    • 一个echo

      start() ->
          spawn(fun() -> loop([]) end).
          
      rpc(Pid, Request) ->
          Pid ! {self(), Request},
          receive
              {Pid, Response} ->
                  Response
          end.
          
      loop(X) ->
          receive
              Any ->
                  io:format("Received:~p~n", [Any]),
                  loop(x)
          end.
      
      • 用到了尾递归技术,不能有多余的函数调用,不然不能复用栈空间、
  • MFA 启动进程, spawn(Mod, FuncName, Args) 动态更新升级?

并发编程中的错误处理

  • 链接进程

    进程依赖的强绑定,强制信号通知,这些东西在c是不强制的

  • on_exit 这东西c++也有,erlang的on_exit还可以hook已有 的进程,很妙

  • 天生分布式,进程的错误托管给别的进程来处理

  • 错误处理的三种基础盖面

    • 进程链接
    • 退出信号
    • 系统进程信息
  • 捕获退出的编程模式

    • 不在乎进程是否崩溃 Pid = spawn(func() -> ...end)
    • 进程崩溃我也崩溃,进程链接 Pid = spwan_link(fun() -> ...end)
    • 进程崩溃我处理错误

       ...
       process_flag(trap_exit, true),
       Pid = spwan_link(fun() -> ...end),
       ...
       loop(...).
      

    loop(State) -> receive {‘EXIT’, SomePid, Reason} -> %% do something with the error loop(State); … end

      
    
  • 错误处理原语

    • spawn_link
    • process_flag
    • link
    • unlink
    • exit
  • 工人-监工模型

  • monitor,守护进程

    keep_alive(Name, Fun) ->
      register(Name, Pid = spawn(Fun)),
      on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).
    

分布式编程

  • rpc
  • global名称注册函数/分布式锁定功能
  • cookies设置

基于socket

接口技术

  • open_port

文件编程

没啥说的

套接字编程

没啥说的

ETS/DETS

OTP

监控树和supervisor

多核问题

  • 状态不可变,并行化
  • MapReduce

更有效率

  • 大量进程 但不要太多
  • 避免副作用
  • 避免顺序瓶颈
  • 少量消息,大量计算

ref

  1. 关于erlang进程系统的资料

    1. https://www.iteye.com/blog/jzhihui-1482175
    2. https://www.erlang-factory.com/upload/presentations/558/efsf2012-whatsapp-scaling.pdf
    3. https://blog.yufeng.info/archives/2615
    4. https://www.zhihu.com/question/24732869
  2. 希望能看懂https://github.com/cbd/edis/blob/master/src/edis_sup.erl

  3. https://www.cnblogs.com/me-sa/ 这人写的erlang博客不错,讲真看这本书不如看他的博客


Read More

(译)Things I Wished More Developers Knew About Databases

翻译整理自 https://medium.com/@rakyll/things-i-wished-more-developers-knew-about-databases-2d0178464f78

You are lucky if 99.999% of the time network is not a problem.没有网络问题你就走大运了兄弟

一个数据,With 99.999% service availability, Google cites only 7.6% of Spanner (Google’s globally distributed database) issues are caused by networking even though it keeps crediting its dedicated networking as a core reason behind its availability.

ACID has many meanings. 正确理解ACID

ACID实现麻烦,NoSQL部分甚至没有ACID,在实现ACID上也有各种妥协,比如mongo的实现

提交落盘是个很重的操作,为了写性能高一些,就会牺牲一点D

Each database has different consistency and isolation capabilities. 每种数据库有自己的一致性和隔离策略

上面说了D,关于CI,实现也是各有取舍。根据CAP定律,C和A之间互有取舍(译者注:CAP的C和ACID的C不是一回事儿,但是这里说的consistency好像是一个事儿)

隔离级别实现程度不同,使用者按照自己的接受方案来做取舍

SQL标准的四个隔离级别,串行SI,可重复读RR,读提交RC,脏读RU,越严格代价约大,一般也就RC RR采用

https://github.com/ept/hermitage 这个文档介绍了不同隔离界别下数据库设计的实现场景

Optimistic locking is an option when you can’t hold a lock.当你拿不到所,就用乐观锁

数据库的锁是很重的,引入竞争,且要求数据一致性,排到锁所可能会受到网络分区的影响(译者注:为什么)也可能导致死锁,不好识别和解决,所以乐观锁就出现了,先更新,再检查版本,拿锁,拿不到,算了。

There are anomalies other than dirty reads and data loss. 有比脏读和丢数据更异常的场景

写偏序,没脏读,没数据丢失,但是结果就是不对

识别写偏序是很困难的。串行化,schema设计,数据库限制来约束写偏序

My database and I don’t always agree on ordering.数据库和我在时序上有不同的意见

用事务

Application-level sharding can live outside the application.应用级别分片可以脱离于应用本身

分片是水平扩展的放水阀,应用层分片也就是架构师/开发能预测数据怎么增长,能很好的水平扩展分片

其实想说就是分片在数据库上层,比如mycat这种,作者给的例子是vitess

AUTOINCREMENT’ing can be harmful. 自增ID可能有害

主键自增id在一些场景下的缺陷

  • 分布式数据库系统,维护全局自增代价过高
  • 根据主键做分片,自增id可能导致写偏序,热点分布不均匀
  • 主键更有意义,更唯一话,击中更快一些(译者注?这个影响会很大吗?)

主键自增ID和UUID索引在某些场景下都是个坏主意,要注意

Stale data can be useful and lock-free.过期数据可能有用且无锁

这里指的MVCC以及对应的快照实现,轻便无锁

  • 主动淘汰 vacuum
  • spanner有gc (译者注:rocksdb也有gc)

Clock skews happen between any clock sources. 什么时钟源都会有时钟偏序

时间同步,ntp服务gps时钟原子钟等等

google spanner是怎么保持整体时间有序的? TrueTime

Latency has many meanings.延迟的多种含义

对于数据库来说,延迟包含两部分,数据库本身延迟和客户端网络延迟,分析延迟相关问题以及指标时,要注意这两种

Evaluate performance requirements per transaction. 评估tps性能

要从典型场景考虑,比如

  • 插入一定条数数据的写吞吐和延迟
  • 查表的延迟。。。

这属于POC了吧,总之要考虑性能能不能满足自身业务的需求,并且收集指标信息和日志来分析

关于延迟的一些诊断方法,见https://medium.com/observability/want-to-debug-latency-7aa48ecbe8f7

Nested transactions can be harmful. 嵌套事务有害

Transactions shouldn’t maintain application state. 事务不应该维护应用的状态

指的是事务和数据共享竞争导致的数据错误

Query planners can tell a lot about databases.查询计划告诉你数据库的信息

就是看是不是全表扫描还是索引扫描了 explain

Online migrations are complex but possible.在线迁移复杂但也不是不可能

  • 双写数据库
  • 两个db都可读
  • 新db接管主要的读写
  • 停止写入旧db,只读,这时在新db上的读可能失败,因为数据不全,有一部分新数据没有
  • 同步所有数据,新写的新数据和旧数据

这是粗糙概括,参考文章https://stripe.com/blog/online-migrations

Significant database growth introduces unpredictability.数据库显著增长带来更多的不可预测问题

扩容了,即使你了解你的数据库,但还是可能会有不可预测的热点,不均匀的数据粉扑,硬件问题,增加的流量带来的网络分区问题等等,让你重新考虑你的数据模型,部署模型等等


Read More

回调lambda引发的shared_ptr循环依赖以及解决办法

参考链接资料汇总

如果你是搜索到这里的,请注意这个问题描述的场景

  • 对象自身的回调捕获了自己的值,自己造成了循环

出事的代码是这样的

class my_class {
// ...
public:
  typedef std::function<void()> callback;
  void on_complete(callback cb) { complete_callback = cb; }
private:
  callback complete_callback;
// ...
};
 
// ...
  std::shared_ptr<my_class> obj = std::make_shared<my_class>();
  obj->on_complete([obj]() {
    obj->clean_something_up();
  });
  executor->submit(obj);
// ...

注意,lambda默认是const,不改变值 SO

lambda的本质就是传值的一个指针,如果这里用std::function保存,会导致这个捕获的指针泄漏,导致这个指针永远不释放

解决方案1,weak_ptr,或者weak_from_this


class my_class {
// ...
public:
  typedef std::function<void()> callback;
  void on_complete(callback cb) { complete_callback = cb; }
private:
  callback complete_callback;
// ...
};
 
// ...
  std::shared_ptr<my_class> obj = std::make_shared<my_class>();
  std::weak_ptr<my_class> weak_obj(obj);
 
  obj->on_complete([weak_obj]() {
    auto obj = weak_obj.lock();
    if (obj) {
      obj->clean_something_up();
    }
  });
  executor->submit(obj);

解决方案2 ,lambda加上 mutable,见这个SO

参考链接2给出了一个比较经典的循环

void capture_by_value(uvw::Loop &loop) {
    auto conn = loop.resource<uvw::TcpHandle>();
    auto timer = loop.resource<uvw::TimerHandle>();

    // OK: capture uses [=]
    conn->on<uvw::CloseEvent>([=](const auto &, auto &) {
        timer->close();
    });

    // Now timer has a callback to conn, and vice versa...
    timer->on<uvw::CloseEvent>([=](const auto &, auto &) {
        conn->close();
    });
};

这里conn和timer循环了,如何规避?weak_from_this(前提,继承std::enable_shared_from_this.)

void capture_weak_by_value(uvw::Loop &loop) {
    auto conn = loop.resource<uvw::TcpHandle>();
    // Create a std::weak_ptr to the connection. weak_from_this() is new
    // in C++17, and is enabled on all classes that inherit from
    // std::enable_shared_from_this.
    auto w_conn = conn->weak_from_this();

    auto timer = loop.resource<uvw::TimerHandle>();
    auto w_timer = timer->weak_from_this();  // as above

    // OK, uses weak_ptr
    conn->on<uvw::CloseEvent>([=](const auto &, auto &) {
        if (auto t = w_timer.lock()) t->close();
    });

    // OK, uses weak_ptr
    timer->on<uvw::CloseEvent>([=](const auto &, auto &) {
        if (auto c = w_conn.lock()) c->close();
    });
});

ref

  1. http://web.archive.org/web/20180324083405/http://https://floating.io/2017/07/lambda-shared_ptr-memory-leak/ 原网址挂了
  2. https://eklitzke.org/notes-on-std-shared-ptr-and-std-weak-ptr 介绍weak_from_this

Read More

Optimizing Bulk Load in RocksDB

还是rockset的文章,讲他们怎么优化批量载入rocksdb的速度

几个优化

在延迟和吞吐之间的取舍

批量加载的时候,调高writebatch大小,其他场景,writebatch不要太大。

并发写

正常的对数据库的操作,保证只有一个写线程。这样不会有多线程写入阻塞问题。怕影响查询操作延迟,但对于这种加载场景,不需要考虑查询操作影响,把writebatch分配到不同的写线程做并发写,注意,要考虑共享的数据,尽可能让writebatch之间不影响不阻塞

不写memtable

构造的数据直接调用 IngestExternalFile() api,(rocksdb文档见这里) 避免写入memtable来同步memtable,这个动作速度快,且干净

但是有局限,这样构造,sst文件只有一层,如果有零星的大sst文件,后台compaction会非常慢。解决方法,一个writebatch写成一个sst文件。

停掉compaction

RocksDB Performance Benchmarks. 官方也建议,使用批量加载最好先停掉compaction然后最后做一次大的compaction,这样避免影响读写,但是

  • sst文件增多,点查很慢,加上定制bloomfilter有所改善可是查bloomfilter开销也很大
  • 最后一次compaction通常是单线程来做,虽然可以通过 max_subcompactions来改,但是效果有限,因为只有一层文件,文件是有重叠的,compaction算法找不到合并区间,所以最后还是一个线程遍历来搞,解决办法,手动对几个小范围做CompactFiles().,生成不是L0层的文件,这样就有区间,就能并发compaction了。前文2提到,他们L1 L0是不压缩的 (为什么压缩会影响写速率?)
结论

在这些优化前提下,加载200g未压缩数据(Lz4压缩后80g) 需要52分钟(70MB/s 18核)初始化加载用了35分钟,最后compaction用例17分钟,如果没有这些优化需要18小时,如果只增加writebatch大小以及并发写线程,用了5个小时

所有试验,只用了一个rocksdb实例

ref
  1. https://rockset.com/blog/optimizing-bulk-load-in-rocksdb/

  2. https://rockset.com/blog/how-we-use-rocksdb-at-rockset/

  3. https://rocksdb.org/blog/2017/02/17/bulkoad-ingest-sst-file.html


Read More

sqlite3资料整理


源码注释 https://github.com/HuiLi/Sqlite3.07.14

在线文档https://huili.github.io/

写一个sqlite 简单版本 https://github.com/madushadhanushka/simple-sqlite


Read More

记录几个数据指标用于估算


v2-0bca913bed8f7d40ac523dbb7688da07_720w

Google的Jeff Dean给的一些数据(一个talk的ppt, “Designs, Lessons and Advice from Building Large Distributed Systems” 23页),可以看到1Gbps的网络比硬盘的bandwidth高了很多,记住这些数据对设计高性能系统和对系统的性能估算很有帮助。

L1 cache reference 0.5 ns

Branch mispredict 5 ns

L2 cache reference 7 ns

Mutex lock/unlock 25 ns

Main memory reference 100 ns

Compress 1K bytes with Zippy 3,000 ns

Send 2K bytes over 1 Gbps network 20,000 ns

Read 1 MB sequentially from memory 250,000 ns

Round trip within same datacenter 500,000 ns

Disk seek 10,000,000 ns

Read 1 MB sequentially from disk 20,000,000 ns

Send packet CA->Netherlands->CA 150,000,000 ns

ref

https://www.zhihu.com/question/47589908


Read More

rockset是如何使用rocksdb的


20211210 他们的rocksdb开源了。有时间看看 https://github.com/rockset/rocksdb-cloud

rockset 是一个db服务提供商,他们用rocksdb来实现converged indexing 我也不明白是什么意思,在参考链接2有介绍,大概就是有一个文档,保存成行,成列,成index,他们大量采用的rocksdb

架构图是这样的

用户创建一个表会分成N个分片,每个分片有两个以上副本,每个分片副本放在一个rocksdb 叶节点上,每个叶节点有很多表的众多副本(他们的线上环境有一百多个),一个叶节点的一个分片副本有一个rocksdb实例,更多细节看参考链接34

下面是他们的优化手段

rocksdb-cloud

rocksdb本身是嵌入式数据存储,本身不高可用,Rockset做了rocksdb-cloud,基于S3来实现高可用

禁止WAL

架构本身有分布式日志存储来维护日志,不需要rocksdb本身的wal

Writer Rate Limit 写速率

叶节点接受查询和写,rockset能接受/ 容忍大量写导致查的高延迟,但是,还是想尽可能的让查的能力更平稳一些,所以限制了rocksdb实例的写速率,限制了并发写的线程数,降低写导致的查询延迟

在限制写的同时,也要让LSM 更平衡和以及主动触发rocksdb的stall机制,(?rocksdb原生没有,rockset自己实现的。rockset也要实现从应用到rocksdb端的限流

Sorted Write Batch

如果组提交是排好序的,并发写会更快,应用上层写的时候会自己排序

Dynamic Level Target Sizes

涉及到rocksdb compactiong策略,level compaction,本层文件大小没达到上限是不会做compact的,每层都是十倍放大,空间放大非常恐怖,见参考链接5描述,为了避免这个,上限大小编程动态的了,这样避免空间放大

AdvancedColumnFamilyOptions::level_compaction_dynamic_level_bytes = true
Shared Block Cache

这个是经验了,一个应用内,共用同一个blockcache,这样内存利用更可观

rockset使用25% 的内存来做block cache,故意留给系统page cache一部分,因为page cache保存了压缩的block,block cache保存解压过的block,page cache能降低一点系统读压力

参考链接6的论文也有介绍

L0 L1层不压缩

L0 L1层文件compact带来的优势不大,并且 L0 compact到L1层需要访问L1的文件,范围扫描也利用用不上L0的bloom filter 压缩白白浪费cpu

rocksdb 团队也推荐L0 L1不压缩,剩下的用LZ4压缩

bloom filter on key prefix

这和rockset的设计有关, 每个文档的每个字段都保存了三种方式(行,列,索引),这就是三种范围,所以查询也得三种查法,不用点查,用前缀范围查询,所以 BlockBasedTableOptions::whole_key_filtering to false,这样bloomfilter也会有问题,所以定制了ColumnFamilyOptions::prefix_extractor,针对特定的前缀来构造bloom filter

iterator freepool 迭代器池子

大量的范围查询创建大量的iterator,这是很花费性能的,所以有iterator 池,尽可能复用

综上,配置如下

Options.max_background_flushes: 2
Options.max_background_compactions: 8
Options.avoid_flush_during_shutdown: 1
Options.compaction_readahead_size: 16384
ColumnFamilyOptions.comparator: leveldb.BytewiseComparator
ColumnFamilyOptions.table_factory: BlockBasedTable
BlockBasedTableOptions.checksum: kxxHash
BlockBasedTableOptions.block_size: 16384
BlockBasedTableOptions.filter_policy: rocksdb.BuiltinBloomFilter
BlockBasedTableOptions.whole_key_filtering: 0
BlockBasedTableOptions.format_version: 4
LRUCacheOptionsOptions.capacity : 8589934592
ColumnFamilyOptions.write_buffer_size: 134217728
ColumnFamilyOptions.compression[0]: NoCompression
ColumnFamilyOptions.compression[1]: NoCompression
ColumnFamilyOptions.compression[2]: LZ4
ColumnFamilyOptions.prefix_extractor: CustomPrefixExtractor
ColumnFamilyOptions.compression_opts.max_dict_bytes: 32768

ref

  1. https://rockset.com/blog/how-we-use-rocksdb-at-rockset/
  2. https://rockset.com/blog/converged-indexing-the-secret-sauce-behind-rocksets-fast-queries/
  3. https://rockset.com/blog/aggregator-leaf-tailer-an-architecture-for-live-analytics-on-event-streams/
  4. https://www.rockset.com/Rockset_Concepts_Design_Architecture.pdf
  5. https://rocksdb.org/blog/2015/07/23/dynamic-level.html
  6. http://cidrdb.org/cidr2017/papers/p82-dong-cidr17.pdf

Read More

pure virtual method called


c++代码,程序偶尔有pure virtual method called打印

参考链接二有一个复现场景

找这种场景就可以了

真是惊奇,这种场景我以为编译器会抓到

ref

  1. https://stackoverflow.com/questions/10707286/how-to-resolve-pure-virtual-method-called
  2. https://devblogs.microsoft.com/oldnewthing/20131011-00/?p=2953

Read More

亚马逊ebs相关整理


ref

  1. https://blogs.nearsyh.me/2020/03/08/2020-03-08-Physalia/ 博客不错
  2. https://zhuanlan.zhihu.com/p/109891109
  3. https://www.usenix.org/system/files/nsdi20-paper-brooker.pdf
  4. https://cloud.tencent.com/developer/news/594892
  5. https://blog.acolyer.org/2020/03/04/millions-of-tiny-databases/

Read More

^