Lecture#14 Query Planning & Optimization
Posted Angelia-Wang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lecture#14 Query Planning & Optimization相关的知识,希望对你有一定的参考价值。
SQL是声明性的,这意味着用户告诉 DBMS 他们想要什么答案,而不是如何得到答案。因此,DBMS 需要将 SQL 语句转换为可执行的查询计划。 但不同的查询计划的效率可能出现多个数量级的差别,如 Join Algorithms 一节中的 Simple Nested Loop Join 与 Hash Join 的时间对比 (1.3 hours vs. 0.45 seconds)。因此,DBMS 需要一种方法来为给定查询选择“最佳”计划。 这是 Query Optimizer 的工作。
Query Optimizer 第一次出现在 IBM System R,那时人们认为 DBMS 指定的查询计划永远无法比人类指定的更好。System R 的 optimizer 中的一些理念至今仍在使用。
我们先来看下一条 sql 语句在 BusTub 中的运行流程:
一条 sql 语句会经过 Parser -> Binder -> Planner -> Optimizer -> Executors 几个阶段。SQL 语句 parse 后,通过 binder 绑定 identifier 到各个实体,使用 planner 生成查询计划,而后使用 optimizer 优化成最终的查询计划,最终通过 executor 生成一颗算子执行树。
SQL 在被 parse 前,可经由 SQL Rewriter 通过某种转换规则来对 SQL 进行重写 (比如用一些额外的信息对数据库里面某些表进行标记,表示可以在这个节点或者磁盘上找到这些表),这个是可选的。
1️⃣ Parser:根据 sql 语句生成一颗抽象语法树 (Abstract Syntax Tree)
具体如何生成,请参考编译原理。Parser 不是数据库的核心部分,也不是性能瓶颈,因此除非热爱编译原理,或者想通过实现一个 sql Parser 对编译原理进行实践,否则一般都会采用第三方库。Bustub 中采用了 libpg_query 库将 sql 语句 parse 为 AST。
2️⃣ Binder:结合数据库的元信息,将抽象语法树转成一个具有库表信息的语义语法树
得到 AST 后,Binder 将遍历这棵树,将所有 identifier 解析成没有歧义的实体。实体是 Bustub 可以理解的各种 c++ 类。绑定完成后,得到的结果是一棵 Bustub 可以直接理解的树。这里把它叫做 Binder AST。
[10][lecture] Lecture 6: Raft
6.824 2018 Lecture 6: Raft (2)
KVservice
key/value service as the example, as in Lab 3
goal: 集群服务对客户端表现和单机一致
goal: 少数节点失效依旧可用
watch out for network partition and split brain!
系统层级 clients - k/v layer - k/v table - raft layer - raft log]
raft接口:client RPC -> Start() -> majority commit protocol -> applyCh
提示:
leader commits/executes 当集群中多数节点AppendEntries成功返回
leader 提交后通知集群提交和执行 (== send on applyCh)
为何不等所有节点回复?因为少数节点失效,仍要具备可用性
多数票方案为何有用?任何两个多数票选方案都一定存在重叠节点,保障了下一个leader可以看到所有commit过的log
lab 2B raft log
- 主节点存在,从节点就会和主节点交互,不会和其他从节点交互
- 如何保证切主过程前后client不会出现异常?比如读到老数据,重复的op,丢失的op,乱序的op等等
- 我们需要保证的是,当一个节点都按照log entry来执行了某个命令,其他节点必须达成共识也在这个log index执行这条命令;方法是从节点会按照主节点来同步log,因此可能会删除其log,选举和同步共同保证了commit的log不会被覆盖(raft保证了新主节点,一定包括所有commit的log),因此选主的时候要限制去选有最新log的节点(要求是,先比较最新的log term高优先主;term一致时长的优先主),候选人只会给满足 up to date log的节点投票
- 如何快速回退追随者的log,文中给出的默认方式每次回退一条;供参考的实现方式:在拒绝的请求中返回,互斥entry的term,第一次出现这个term的log index,如果主节点有这个term,nextIndex配置为回退到主节点该term的最新entry;如果没有nextIndex配置为追随者第一次出现这个term的log index
lab 2C persistence
节点crash后如何恢复?
raft允许少数节点crash后依旧可用,但是crash的节点要修复后加入集群,保证复制因子
两种策略:
第一,使用一个新的server加入,需要将全量log或者快照发送到该节点
第二,修复crash节点,重新加入,从持久化log中读取到log,然后慢慢从主节点catch up到最新的log
讨论哪些状态需要做持久化?
论文中给出,log[],votefor,currentTerm
因此上述状态的每次变更都需要持久化落盘操作
log[],需要记住log entry,来保证多数复制的log entry在选举中可以获胜,从而达到新leader必须有所有commit log的要求
voteFor,防止选举阶段发生两次投票事件造成双主
currentTerm,保障term单调递增,检测rpc消息中term是否是旧的
哪些状态不需要落盘?
commitIndex,lastApplied,next/matchIndex[],可以在重启后动态重建上述变量
持久化性能有影响?
SSD 0.1ms HD 10ms,因此持久化限制我们吞吐在 100-10000ops
RPC耗时,<<1ms
可以使用batch方式优化磁盘性能
service如何从crash中恢复?
最简单的方案,从初始态开始,重放持久化的raft日志,但是lastApplied没有持久化,因此需要额外的工作,同时回放所有日志对于长期运行的系统会很慢
使用snapshot技术,只回放近期的日志
log compaction and Snapshots (Lab 3B)
- raft log会慢慢变大,远大于state machine的状态本身,每次节点重启回放的时间非常用,影响了可用性
- 幸运的是,节点不需要又存储raft log又存储state machine,两者是冗余的,执行过的log已经在state machine中体现,client只看到state,看不到log,state通常不大,所以思路就是仅存储state,通过raft log entry来存储全量的state,作为snapshot,提交后就可以安全的删除snapshot覆盖的log entry
- 考虑哪些log不能删除?第一,un-commit log,可能已经是复制集中的多数,还没收到主的提交消息;第二,un-excuted log,还没反应到state;还有虽然已经执行过的其他节点还需要同步的log
- service周期性持久化快照:service with state, snapshot on disk, raft log, raft persistent,将全量的state快照作为一条特殊的command,因此也就是一条特殊的log entry。首先service将快照持久化到磁盘,然后通过一条特殊command即特殊的log entry通知raft快照,raft就这一条log enty达成共识,随后raft可以安全的清理快照覆盖的log,server可以随时做快照清理raft log
- 因此持久化部分,包括service’s snapshot,raft logs,两者联合等同于全量的log
- 节点crash恢复,首先读snapshot,然后从raft获得提交但是还没快照的log entry
- 如果追随者需要同步的日志主节点已经快照了清理了怎么办,使用InstallSnapshot rpc,(TODO 为何不保证只清理所有节点都同步的log)
- 主节点InstallSnapshot rpc参数,term,lastIncludedIndex,lastIncludedTerm,snapshot data
- 从节点反应,term低于自己拒绝;如果lastIncludedIndex自己已经有了拒绝;清理log,set lastApplied to lastIncludedIndex,对状态机执行snapshot替代持久化状态
- log和快照是统一的,主节点可以决定如何同步状态,对于新的节点,使用快照快一些;对于只落后一些的节点,使用log同步即可
configuration change not in lab
- 所谓config就是指集群中的节点配置
- 场景:需要增加节点,移除节点,或者增加复制集因子来提高可用性等等
- 触发依赖人为操作,raft需要自动处理不中断服务
- 注意每个节点都有整个集群的配置信息,如果在raft之外通知每个节点新的配置,问题就是每个节点改变配置的时间是不一致的,就有可能造成集群双主,例子如下
we get as far as telling S1 and S4 that the new config is 1,2,4
S1: 1,2,3 1,2,4
S2: 1,2,3 1,2,3
S3: 1,2,3 1,2,3
S4: 1,2,4
OOPS! now *two* leaders could be elected!
S2 and S3 could elect S2
S1 and S4 could elect S1
- 因此集群改变配置要依赖raft达成共识,采用一个过渡阶段来解决这个问题,集群一开始配置是cold,然后人为通知主节点切换配置到cnew,此时raft会记下一条log entry,每个节点默认使用其log中最后的config,leader首先commit cold-new,此时无论是old还是new都会有多数节点完成commit,然后主节点在commit cnew
- 主节点crash在上述不同阶段?是否会有双主产生?不会,一般来说,主节点需要满足以下条件之一:A:old 节点,还没复制到cold new;B,old-new节点,已经复制到cold-new;C,new节点,已经复制到cnew;A+A和C+C不可能,因为互斥原则;A+B,B同时需要old的多数票和new的多数票,和A互斥;A+C,除非coldnew已经提交,不会到C阶段,如果coldnew已经提交,就不会出现A;B+B,互斥;B+C,B需要两者的多数票,和C互斥
performance
- 一般来说,不是所有服务都需要性能特别高,比如gfs或mapreduce可以不那么高;kv service需要高性能
- 大多数一致性算法,复杂度基本一致,每次共识达成都需要一次rpc通信和一次落盘
- raft为了简单性损失了部分性能:第一,追随者会拒绝乱序的log,直到找到合适的log才接收(优化可以是空出一些hole然后写入指定位置,当网络乱序很多的场景);第二,appendEntry只能逐条进行,可以做batch或者流水线去优化;第三,快照技术对于很大的全量状态可用性很差;第四,leader如果很慢,整个系统就会很慢
- 对性能影响很大的部分:磁盘写入性能;网络消息性能;所有log command是串行的,无法利用多核;read-only操作可以快速执行
- 更多的优化参见:zookeeper,etcd3,paxos made live;harp
raft FAQ
Q: 何时主节点需要在读请求中commit no-op?
A: 两种方案保护client不会读到老的数据,leader刚刚选举完成时,不能确定commit位置,需要额外的操作,一种是commit no-op保障;另一种是租约机制。文中说读不会涉及任何write,只有一种场景除外,就是leader刚刚选举后的第一次read操作。
Q: commit no op保护的是什么?
A: 借助figure 8来阐述,依旧是c中的S1无法确定2是否已经commit,因此就无法知道返回给客户端的读是否需要包括2,所以它需要做一次commit,空的commit刚好适合这种场景,一旦commit一次后,2就会保证commit,后续的leader都会有2,否则会出现figure 8中的s5情况,2其实没有commit
Q: 租约机制如何实现,为何需要依赖bounded clock skew?
A: 有一种实现可以是,收到leader的心跳消息,保证接下来的100ms内不会产生选举,所以就不会出现figure 8中的情况,主节点可以安全的服务读请求100ms,然后每次心跳会续租。这需要每个节点对100ms的定义是一致的
Q: Cold和Cnew表示什么?是leader吗?
A: 表示的是集群的配置,主要是包含哪些节点,paper中没有提供更多的细节。可能就是ip地址加端口号,或者一个hostname
Q: 如何理解cold,cnew以及过度状态?
A: Cold_new的含义是,这个期间,多数票选需要保证节点both old and new。 Suppose C_old=S1,S2,S3 and C_new=S4,S5,S6, S1为leader,最后阶段S1 commit cnew,但是它仍是leader,所以需要step down,让位给cnew中的节点做主节点
Q: 为什么cold需要关闭自己,否则就会导致选举?
A: 主要问题是cold节点没有commit cnew配置,所以这些节点没有获得集群变更给cnew的信息
Q: 切换配置时选主需要old new同时获得多数票选,会影响性能多少?
A: 基本不会,基于修改配置并不频繁,old new同时多数票选带来的负荷也不大,当集群中节点相对稳定的时候
Q: 新成员加入有一个过程没有投票权,这个过程如何提高了集群可用性?
A: 该改进主要是为了新的节点可以快速得到全量的log,防止其加入后无法commit log,因为一直在做catch up操作,导致集群不可用。如果节点没有cathup,那么coldnew就无法commit,直到它catch up并且提交了cold new才会继续执行client command,因为log是按顺序提交的,新的log就要等coldnew提交完成,因此该阶段可以减少集群停留在coldnew的时间,使得配置变更更迅速
Q: 没有投票权的server状态是否仅存在于配置变更中?还是说无论何时,只有catch up的server才能投票?如何定义catch up?
A: 论文中相关细节不多,可以猜测一下,情况应该是leader开启一次集群配置变更操作,应该是在所有新加入的节点都已经catchup后,当leader开始coldnew时,这些server就都已经catch up了,因为只有多数节点完成coldnew log的复制,才会commit coldnew,当cold newcommit时,集群中节点就具备投票权。
Q: 论文中采用一种方式抑制新一轮的选举,为了防止old集群节点timeout触发新的选举,为什么不通过消息的来源来判断发送消息的节点是否在集群中,这样就可以判断是否可以接收这条消息了?
A: 这种方法也可行,不过考虑一下,有一种情况是,在old new状态下,某个new的节点成为leader,此时少数old节点还没开始变更,它们仅知道old配置节点的存在,我们不希望这些节点忽略来自新leader的选举(不过忽略也没问题,因为多数节点已经coldnew了,否则不可能new节点成为主)
Q: 变更的起始点和终止点?
A: 当前leader察觉到coldnew log entry时开始,如果在cold new没有commit时切主,另一个主没有cold new,那么本次变更就提前结束了,变更失败但是一致性仍在是对的;继续会commit coldnew,commit cnew后,变更结束
Q: 如果state本身和log一样都很大,比如都是唯一的kv插入,快照还有用吗?
A: 取决于具体情况,对于kv而言,快照可以具备和log不同的结构,比如更快速的load到内存,而不是一条一条回放日志,因此依旧具备实用性,一般来说,log远比state大。
Q: 快照rpc会很消耗带宽资源吗?
A: 会的,优化起来并不容易,因为如果要增量复制,对state要求比较高,对哪些log可以安全删除要求也很高。
Q: 写快照耗时太长,会不会导致选举发生?
A: 有可能发生,快照Tb级别,会耗时s级别甚至更长,此时解决方案是后台写快照,可以采用copy-on-write方案,子进程写快照,或者保证快照每次写小于超时时间,然后分多次写也可以。
Q: figure 13是否少了一些条件判断,比如If lastIncludedIndex < commitIndex, return
immediately. or If there is already a snapshot and lastIncludedIndex < currentSnapshot.lastIncludedIndex, return immediately.
A: 是的,要防止接收陈旧的snapshot,由于网络原因后者乱序问题,可能发生这种情况
Q: 如果接收快照时,我的快照优先于请求来的如何处理?
A: 忽略请求即可,是安全的。
Q: 实际应用中如何处理快照问题?
A: TODO 参考etcd-raft实现
Q: RPCinstallSnapshot原子性?执行一半失败了怎么办?
A: 和其他rpc一样,务必保证原子性,要么成功要么失败,不能有中间状态。同时该rpc实现还需要保证幂等性,leader可能会重复发送请求。
Q: 压缩技术可否应用在快照上?
A: 可以的,取决于存储的数据类型。如果每一次快照和前一次都有很多冗余,可以使用tree结构,就可以在不同快照版本之间共享node。
Q: 执行op和将op写入log之间有什么关系?
A: 只有leader commit的log才能执行,leader首先执行,然后追随者根据请求中的commitindex,执行本地的log,执行的含义是将log entry给上层应用,对于kv服务来说,交给kv层来执行get或者put请求
Q: 如何实现确定当前主节点时存在的,从而忽略选主的消息?
A: 每个节点随机有一个选举的超时时间,当超时时间到达还没收到主的心跳或者其他的选举请求时,发起选举。举例来说,hb 10ms一次,leader 发送心跳,10,20,30,假设s1节点在30时没收到心跳,35时timeout到达,开始出发选举,s2在30收到了心跳,因此s2会至少保证到40之前,不会发生超时选举事件,这里的假设是30-40之间,主是活着的,当然也可以更长,允许到50,也就是允许断链一次心跳,此时s2收到s1的选举,可以安全的忽略
reference
以上是关于Lecture#14 Query Planning & Optimization的主要内容,如果未能解决你的问题,请参考以下文章
[Computer Networking] {CMU14-740} Lecture 9: The Transport Layer; UDP
cs231n spring 2017 lecture14 Reinforcement Learning 听课笔记
控制变量法-初中物理-Nobel Lecture, December 12, 1929-php执行SET GLOBAL connect_timeout=2效果
Project management and planning
原Andrew Ng斯坦福机器学习——Lecture 5 Octave Tutorial—5.5 控制语句: for, while, if 语句