berkeley db 组提交机制 group commit

berkeley db的组提交机制 见 src/log/log_put.c, __log_flush_int().

 

一个 事务 是否已经提交, 关键看其在log里是否有 commit log 记录, 并且此log记录已经sync到磁盘上了. 所以transaction commit, flush log文件是最费时的操作. 在berkeley db中, 如果很多线程同时提交事务. 那么这些commit事件会被分组处理, 即只让一个线程flush, 其他的线程wait. 一次flush后, 检查 当前wait的所有线程, 如果线程 的事务 commit log record已经被flush, 则此线程被唤醒并从 _log_flush_int()返回. 否则另外选出一个线程接着做flush操作.

 

注: __log_flush_int() 并不单独针对 txn commit, 任何对log 文件的flush都会调用此函数.

// 进入/退出函数时, 都会持有 log region mutex
__log_flush_int()
if (lp->db_log_inmemory) { lp->s_lsn = lp->lsn; STAT(++lp->stat.st_scount); return (0); } // 这里设置 flush_lsn, 指明 此lsn及其之前的log 需要被sync到磁盘. if (lsnp == NULL) { // 没有给定lsn, 则flush整个log文件 flush_lsn.file = lp->lsn.file; // lp->lsn为log 系统下一条log的lsn flush_lsn.offset = lp->lsn.offset - lp->len; // lp->len为刚写入的log大小. 设flush_lsn为刚写入log的lsn } else if (lsnp->file > lp->lsn.file || // 指定了lsn. 检查check 指定的lsn是否有效? (lsnp->file == lp->lsn.file && lsnp->offset > lp->lsn.offset - lp->len)) { __db_errx(env, DB_STR_A("2516",...); __db_errx(env, DB_STR("2517",...)); return (__env_panic(env, DB_RUNRECOVERY)); } else { if (ALREADY_FLUSHED(lp, lsnp)) // 指定lsn已经被sync, 退出. 注: 内部实现: 查lp->s_lsn, 上一次sync的lsn return (0); flush_lsn = *lsnp; // flush_lsn设为指定的lsn }
  // release参数表明是否 释放log region mtx. (!release表明独占的情况, 别的thread无法进入此函数.)
if (release && lp->in_flush != 0) { // lp->in_flush 标识当前做flush的 thread数, 这里即已经有thread在flush了 if ((commit = SH_TAILQ_FIRST(&lp->free_commits, __db_commit)) == NULL) { // free list为空 if ((ret = __env_alloc(&dblp->reginfo,sizeof(struct __db_commit), &commit)) != 0) // 分配一个 goto flush; memset(commit, 0, sizeof(*commit)); if ((ret = __mutex_alloc(env, MTX_TXN_COMMIT,DB_MUTEX_SELF_BLOCK, &commit->mtx_txnwait)) != 0) { __env_alloc_free(&dblp->reginfo, commit); return (ret); } MUTEX_LOCK(env, commit->mtx_txnwait); // 因为是self-block, 先锁一下. } else SH_TAILQ_REMOVE(&lp->free_commits, commit, links, __db_commit); // freelist不空, 直接从freelist拿 lp->ncommit++; // 等待做commit的线程数 if (LOG_COMPARE(&lp->t_lsn, &flush_lsn) < 0) // lp->t_lsn记录下当前等待commit的 最大的lsn; lp->t_lsn = flush_lsn; // 每次flush都是代表当前所有thread commit->lsn = flush_lsn; SH_TAILQ_INSERT_HEAD(&lp->commits, commit, links, __db_commit); LOG_SYSTEM_UNLOCK(env); // 释放log region mutex, 别的thread可以进入此函数 MUTEX_LOCK(env, commit->mtx_txnwait); // 等待... LOG_SYSTEM_LOCK(env); lp->ncommit--; do_flush = F_ISSET(commit, DB_COMMIT_FLUSH); // 检查是否需要当前线程做 flush F_CLR(commit, DB_COMMIT_FLUSH); SH_TAILQ_INSERT_HEAD(&lp->free_commits, commit, links, __db_commit); // 归还 db_commit到 freelist if (do_flush) { lp->in_flush--; flush_lsn = lp->t_lsn;// lp->t_lsn: 等待commit的 最大的lsn; 这样每次flush即代表了所有的 thread } else return (0); // 表示 需要flush的log record已经被别的thread 代劳了 } // gc 等待 结束 flush: MUTEX_LOCK(env, lp->mtx_flush); // 这里我们已持有log env的mtx. 注意 锁的顺序不能反了. // the last-sync LSN saved in s_lsn is the LSN of the first byte after the byte we absolutely know was written to disk if (flush_lsn.file < lp->s_lsn.file || (flush_lsn.file == lp->s_lsn.file && flush_lsn.offset < lp->s_lsn.offset)) { MUTEX_UNLOCK(env, lp->mtx_flush); // 参见 上面的ALREADY_FLUSHED, 一样的 goto done; } if (lp->b_off != 0 && LOG_COMPARE(&flush_lsn, &lp->f_lsn) >= 0) { // lp->b_off为 log buf中的当前offset.
    // lp->f_lsn 为log buffer中第一个byte所在的log record对应的lsn
if ((ret = __log_write(dblp,dblp->bufp, (u_int32_t)lp->b_off)) != 0) { MUTEX_UNLOCK(env, lp->mtx_flush); // 上面的意思: log buf不为空; 且flush_lsn对应的log rec在buffer中. goto done; } lp->b_off = 0; } else if (dblp->lfhp == NULL || dblp->lfname != lp->lsn.file) // 此线程从来未写过log? witf? if ((ret = __log_newfh(dblp, 0)) != 0) { MUTEX_UNLOCK(env, lp->mtx_flush); goto done; } b_off = lp->b_off; // log buf中当前的offset w_off = lp->w_off; // log 文件的当前write offset f_lsn = lp->f_lsn; // log buffer第一个byte对应的lsn lp->in_flush++; // in_flush用法还是很tricky, 仔细看 if (release) LOG_SYSTEM_UNLOCK(env); // unlock log region mtx. 让 thread可以进入此方法来做 gc. if (!lp->nosync) { if ((ret = __os_fsync(env, dblp->lfhp)) != 0) { // 实际上 sync的是 整个log文件. 和flush_lsn无关. MUTEX_UNLOCK(env, lp->mtx_flush); // flush_lsn的意义:是否已经flush, buffer处理 if (release) LOG_SYSTEM_LOCK(env); lp->in_flush--; goto done; } STAT(++lp->stat.st_scount); } lp->s_lsn = f_lsn; // 重要! s_lsn: >= 已经flush的(file, offset). 首先s_lsn=f_lsn; buf里的lsn没有被write的情况.但刚才可能 if (b_off == 0) // 写了 log buf(或别的线程在别的地方写了.), 对应b_off=0的情况. lp->f_lsn只在__log_fill(), b_off==0才改动. lp->s_lsn.offset = w_off; // buf空了, 则w_off肯定都flush了 MUTEX_UNLOCK(env, lp->mtx_flush); // 在拿 log region mtx前一定要 unlock mtx_flush. if (release) LOG_SYSTEM_LOCK(env); lp->in_flush--; ncommit = 1; // 计算刚才的flush 完成了几个commit. done: if (lp->ncommit != 0) { // 有wait的commit first = 1; // 如还需要flush, 只让一个commit 做flush SH_TAILQ_FOREACH(commit, &lp->commits, links, __db_commit) if (LOG_COMPARE(&lp->s_lsn, &commit->lsn) > 0) { // 此commit已完成 MUTEX_UNLOCK(env, commit->mtx_txnwait); // 唤醒 SH_TAILQ_REMOVE(&lp->commits, commit, links, __db_commit); ncommit++; } else if (first == 1) { F_SET(commit, DB_COMMIT_FLUSH); // 让此commit做flush MUTEX_UNLOCK(env, commit->mtx_txnwait);// 唤醒 SH_TAILQ_REMOVE(&lp->commits, commit, links, __db_commit); lp->in_flush++; first = 0; } } return (ret); }

 

 

postgres 类似机制, backend/access/transam/xlog.c, XLogFlush()

    /*
     * Since fsync is usually a horribly expensive operation, we try to
     * piggyback as much data as we can on each fsync: if we see any more data
     * entered into the xlog buffer, we'll write and fsync that too, so that
     * the final value of LogwrtResult.Flush is as large as possible. This
     * gives us some chance of avoiding another fsync immediately after.
     */

 

posted @ 2016-02-28 23:13  brayden  阅读(462)  评论(0编辑  收藏  举报