-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
229 lines (108 loc) · 129 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Workload-Driven Placement of Column-Store Data Structures on DRAM and NVM</title>
<link href="/2021/06/22/placementOfColumnStore/"/>
<url>/2021/06/22/placementOfColumnStore/</url>
<content type="html"><![CDATA[<p>DaMoN 2021 <a href="https://dl.acm.org/doi/pdf/10.1145/3465998.3466008">原文链接</a></p><h3 id="abstract"><a href="#abstract" class="headerlink" title="abstract"></a>abstract</h3><ul><li><p>研究列存储数据结构在 DRAM 和 NVM 的混合层次结构中的放置,目的是在不影响性能的情况下将尽可能多的数据放置在 NVM 中</p></li><li><p>提出一种启发式方法,利用轻量级访问计数器来建议哪些结构应该放在 DRAM 中,哪些应该放在 NVM 中</p></li><li><p>使用 TPC-H 的评估表明,超过 80% 的查询所接触的数据可以放在 NVM 中,几乎没有减速,而naively将所有数据放在 NVM 中会增加53%的运行时间</p></li></ul><h3 id="introduction"><a href="#introduction" class="headerlink" title="introduction"></a>introduction</h3><p>在本文中,我们研究了使用 NVM 来存储 SAP HANA 的读优化的列存储数据结构。</p><p>目标是将尽可能多的数据移动到 NVM,而不会对分析工作负载中的查询性能产生负面影响。</p><p><img src="https://i.loli.net/2021/06/20/Lgfeq2C6jAlsrGx.png" alt="image-20210620194013242"></p><p>当数据放在 NVM 中时,扫描繁重查询的运行时间仅增加了 16%,而点查询的运行时间却减慢了 115%。这可能归因于顺序加载的较低延迟和高效的prefetch。由于 DRAM 和 NVM 上的点访问和扫描之间的性能如此不同,我们认为在决定是否应将其放置在 NVM 中时,考虑数据结构上的主要访问类型至关重要。为此,我们建议使用访问计数器在数据结构级别区分点访问和扫描访问,以指导放置决策.</p><h3 id="background"><a href="#background" class="headerlink" title="background"></a>background</h3><h4 id="SAP-HANA"><a href="#SAP-HANA" class="headerlink" title="SAP HANA"></a>SAP HANA</h4><p>使用读优化的main fragment 和写优化的delta fragment 存储每个表。两者都会定期合并,通常是在增量超过特定大小时。我们关注main store ,因为它代表了大部分表数据,它采用domain encoding 进行数据压缩并加快访问速度。domain encoding 列由数据向量和字典组成。该字典包含已排序且唯一的原始列值。数据向量为列的每一行存储一个value id。默认情况下,数据向量是按位打包的,但也可以使用逻辑压缩技术(如run length 或稀疏编码)进行压缩。这可以伴随着重新排列表行,从而使整个表压缩变得更有效。字符串字典也可以被压缩以进一步减少内存使用。使用 SIMD 指令可以加速对数据向量的扫描。在查询执行过程中,访问数据向量和字典的方式主要有两种:获取过滤后的行集(SQL WHERE),首先在字典中搜索匹配值并生成一组值id。在许多情况下(例如,相等或范围谓词)可以利用字典的排序特性来避免对字典的完全扫描。然后通过扫描数据向量来获取与先前字典搜索产生的值 id 匹配的值 id 的行 id .第二个访问情况是需要具体化行值时,例如,用于投影、连接或聚合。在这种情况下,使用行 id 执行数据向量中的查找,这会产生一个值 id,然后在字典中查找以检索该值.</p><p><img src="https://i.loli.net/2021/06/20/GWDjtwlv1YOrLk4.png" alt="image-20210620195351052"></p><h4 id="relate-work"><a href="#relate-work" class="headerlink" title="relate work"></a>relate work</h4><p><strong>NVM in the storage layer</strong></p><p>一些工作将 NVM 用作缓存层次结构中的附加层。</p><p>缓冲区管理方法基于其驱逐和上次访问页面时的迁移策略,并且由于策略的性质,可能会在 DRAM 和 NVM 之间复制数据。我们认为在 NVM 中存储数据是一个放置问题,其中数据位于 NVM 或 DRAM 中,并建议区分扫描和点访问数据结构以指导放置决策。虽然我们实现了缓冲区管理数据结构的放置以简化实现,但我们的结果可以转移到 SAP HANA 的内存结构中</p><p>**data management on multi tier hierarchies **</p><p>一些工作专注于跨内存层次结构在其他级别放置数据。</p><p>Bhattacharjee 研究具有 HDD 和 SSD 的多层存储层次结构可以有效地用于数据管理。他们根据对给定工作负载的随机和顺序访问磁盘页面的计数来估计访问时间节省,从而将数据库对象静态放置在 SSD 而不是 HDD 上。boissier 根据列上的访问模式主要是顺序还是随机,以列粒度在 DRAM 和二级存储之间做出放置决策。然后将不同的存储布局用于放置在二级存储中的数据。我们还在本文中区分了随机点和顺序扫描访问,尽管是将内存数据放置在 DRAM 或 NVM 中,而不改变存储布局。vogel 提出了 Mosaic,存储引擎可以将数据最优的放置在tier-less pool,用于基于磁盘的系统中的扫描繁重工作负载。我们旨在为内存中存储提供类似的最佳放置建议.</p><h3 id="placement-policy"><a href="#placement-policy" class="headerlink" title="placement policy"></a>placement policy</h3><p>首先讨论我们如何在 NVM 中放置数据,然后是一个初步实验来评估在 NVM 中放置数据的可行性。然后,我们使用 SAP HANA 中的两个主要数据结构数据向量和字典上的访问计数器来识别主要访问类型。最后,我们基于访问计数器推导出对各个列的数据结构进行放置决策的启发式方法</p><h4 id="facilitating-Placement-on-NVM"><a href="#facilitating-Placement-on-NVM" class="headerlink" title="facilitating Placement on NVM"></a>facilitating Placement on NVM</h4><p>我们通过使用 NVM-backed layer 扩展 SAP HANA 的buffer cache 来实现数据结构放置。</p><p>当从buffer cache 中请求page时 就相当于该page在NVM中.</p><h4 id="Placement-Based-on-Data-Structure-Type"><a href="#Placement-Based-on-Data-Structure-Type" class="headerlink" title="Placement Based on Data Structure Type"></a>Placement Based on Data Structure Type</h4><p>为了初步了解将 SAP HANA 的main store放置在 NVM 中的可行性和潜力,我们使用来自 TPC-H 基准测试的查询测试了四种简单的放置策略,sf为 100。四种初始策略是:DRAM Only(),即所有数据放在DRAM中,NVM Only(), 数据向量在NVM()中,字典在NVM()中。请注意,除了数据向量和字典之外,NVM Only 策略还在 NVM 中放置索引。</p><p><img src="https://i.loli.net/2021/06/20/frVOG26iop3h9uI.png" alt="image-20210620203100447"></p><p>只有查询实际读取的数据才会加载到缓冲区缓存中,因此查询未触及的列(例如,L_COMMENT)不包括在内存使用统计中。</p><p>在许多情况下,NVM 可以容纳超过 70% 的数据量,查询速度下降不到 20%。虽然仅将数据向量放在 NVM 中已经产生了非常好的结果,但另外将选定的字典放在 NVM 中可能允许在类似性能下更低的 DRAM 使用率。在下文中,我们因此探索了更细粒度的数据放置策略,该策略还考虑了工作负载特征,特别是单个列上的访问模式。</p><h4 id="analyzing-access-types"><a href="#analyzing-access-types" class="headerlink" title="analyzing access types"></a>analyzing access types</h4><p>考虑在两个主要数据结构(数据向量和字典)上实现访问计数器。这些计数器代表访问数据结构条目的总数。为了能够放置在列 和 数据结构粒度上,我们分别为每一列收集访问计数器。正如我们之前已经确定点访问和扫描之间 NVM 的显着性能差异,我们区分这两种访问类型的计数器。对于点访问, 还会跟踪密集点访问(dense point accesses)。如果点访问在数据结构最后访问的条目之前或之后读取元素,则被认为是密集的。这种区别很有用,因为密集点访问构成了比真正的随机点访问更像扫描的访问模式,当具有这种访问模式的数据放置在 NVM 中时,这应该会导致不太明显的减速。我们只计算对每个数据结构的访问总数,因为我们目前只对在数据结构粒度上做出放置决策感兴趣。我们指出每个数据向量和每个字典只存在一组计数器(三个整数)。计数器也是惰性更新的,即,如果连续发生多次访问,这些首先由计划运算符在本地聚合,并仅添加到相应数据结构的计数器一次。因此,维护计数器的性能和空间开销可以忽略不计.</p><p><img src="https://i.loli.net/2021/06/20/B9AxLyzTD7aXRPo.png" alt="image-20210620204010924"></p><p>我们观察到数据向量主要通过密集点访问或scan,而字典主要使用点访问进行访问。字典也几乎从不扫描。</p><p>鉴于我们预计将数据放置在 NVM 中对扫描的影响较小,这与图 2 中的结果相匹配,在图 2 中,我们观察到主要在将字典放置在 NVM 中时会出现主要减速</p><h4 id="Placement-Based-on-Access-Counters"><a href="#Placement-Based-on-Access-Counters" class="headerlink" title="Placement Based on Access Counters"></a>Placement Based on Access Counters</h4><p>根据对列的访问来放置每列的数据向量和字典,目的是在 NVM 中放置尽可能多的数据,同时尽可能少地降低性能。对于启发式,我们考虑两个主要因素:首先,在所有 TPC-H 查询的完整运行中,超过 70% 的所有访问都是类似扫描的,并且我们还发现这种访问模式表现出类似的DRAM 和 NVM 上的性能,而随机点访问模式会导致将数据放入 NVM 时的性能损失。因此,启发式中的一个因素应该是数据结构的扫描与点访问比率。其次,我们发现许多内存消耗最高的列很少被访问。</p><p><img src="https://i.loli.net/2021/06/20/WhTNX5GjosL4dke.png" alt="image-20210620205515429"></p><p>将这些列的数据向量和字典放置在 NVM 中,而不管它们的扫描与点访问比率如何</p><p><img src="https://i.loli.net/2021/06/20/sla5PupFwvTBL87.png" alt="image-20210620205627188"></p><p>scan,点访问密集型或访问总数比较小放在NVM.</p><h3 id="evaluation"><a href="#evaluation" class="headerlink" title="evaluation"></a>evaluation</h3><p><img src="https://i.loli.net/2021/06/20/NamVioGMkvQJ9u5.png" alt="image-20210620205949357"></p><p><img src="https://i.loli.net/2021/06/20/k8G9BMigQERHPyY.png" alt="image-20210620210059758"></p><p>以数据结构粒度放置要比按列粒度放置优</p><h4 id="discussion"><a href="#discussion" class="headerlink" title="discussion"></a>discussion</h4><ul><li><p>水平分区数据的更细粒度放置也可能导致进一步改进的结果</p></li><li><p>评估其他工作负载将是可取的,例如,具有大量点查找的混合 HTAP 工作负载,以确定在这种情况下 NVM 对主要数据的适用性,并特别评估所提出的启发式方法的有效性</p></li><li><p>数据压缩和数据放置之间的交互,或者在查询优化期间考虑数据所在的位置。</p></li><li><p>如何或是否应该向用户展示像这里介绍的那样的数据放置替代方案。虽然一种选择是提供数据放置作为调整旋钮或提供放置顾问,但“自动驾驶”方法可能更需要系统自主做出这些决策而无需用户干预。</p></li></ul><h4 id="summary"><a href="#summary" class="headerlink" title="summary"></a>summary</h4><p>对domain encoding 的系统比较有用吧, 观察到 dictionary 的访问模式和 vector的访问模式不同, 于是按数据结构粒度进行数据的放置(NVM or DRAM). 提出的公式就直接硬编码了一些参数, 以及实验只是在tpch上进行的. 在NVM上的数据结构的布局似乎也没有redesign.实验中找到的策略也不是最优的.</p>]]></content>
<tags>
<tag>paper reading</tag>
</tags>
</entry>
<entry>
<title>C-store</title>
<link href="/2021/05/07/cstore/"/>
<url>/2021/05/07/cstore/</url>
<content type="html"><![CDATA[<h2 id="A-Column-oriented-DBMS"><a href="#A-Column-oriented-DBMS" class="headerlink" title="A Column-oriented DBMS"></a>A Column-oriented DBMS</h2><h3 id="abstract"><a href="#abstract" class="headerlink" title="abstract"></a>abstract</h3><h3 id="introduction"><a href="#introduction" class="headerlink" title="introduction"></a>introduction</h3><p>优化更新和优化读的数据结构之间存在矛盾。比如,当数据是按条目到达顺序存储时,可以在列的末尾以批处理或事务方式有效地插入新数据项。但是,这样读的代价会比较大。然而,以非数据到达顺序存储将使插入的代价昂贵。</p><p>C-Store从一个全新的角度解决了这个难题。将一个read-optimized的列存储和一个面向更新/插入的writeable store组合在一个系统中,由tuple mover连接。在top level,有一个小型的writeable store(WS)组件,它的体系结构支持高性能插入和更新。还有一个更大的组件称为read-optimized store(RS),它能够支持非常大量的存储。RS只支持一种非常受限的插入形式,即记录从WS批量移动到RS,这是tuple mover要做的。</p><p><img src="https://i.loli.net/2021/04/29/fo4l2xmXRA5iNSa.png" alt="image-20210429205327568"></p><p>查询必须同时访问两个存储系统中的数据。插入被发送到WS,而删除必须被发送到WS。在RS中标记,以便稍后由元组移动器清除。更新实现为插入和删除。为了支持高速tuple mover,通过高效的有序合并方法将元组从WS批量移动到RS。</p><h4 id="features-of-C-Store"><a href="#features-of-C-Store" class="headerlink" title="features of C-Store"></a>features of C-Store</h4><ol><li>一个混合架构,其中WS针对频繁插入和更新进行了优化,RS针对查询性能进行了优化。</li><li>表中的数据以不同顺序在几个overlapping projetions中冗余存储,以便使用最有利的projection来解决查询。</li><li>使用多种编码方案中的一种高度压缩列。</li><li>面向列的优化器和执行器,具有不同于面向行系统中的原语。</li><li>使用足够数量的overlapping projections,通过K-safety实现高可用和改进性能。</li><li>使用快照隔离以避免2PC和锁。</li></ol><p>C-Store在物理上存储多组列集。在一个group中每个列按某些属性排序。按相同属性排序的列组称为 projection ;同一列可能存在于多个投影中,可能按每个投影中的不同属性排序。多个排序器可以优化查询。</p><p>在这里可以看出projection的概念和通常的projection不同。</p><p>存储overlapping projections进一步提高了性能,即使某个服务器发生故障,也可以访问数据。我们称能容忍K次故障的系统为K-safe。</p><h3 id="data-model"><a href="#data-model" class="headerlink" title="data model"></a>data model</h3><p><img src="https://i.loli.net/2021/04/29/o37BMhtYy1eHxZQ.png" alt="image-20210429212712875"></p><p><img src="https://i.loli.net/2021/04/29/3OAKaQJ8MlHzsPd.png" alt="image-20210429212725069"></p><p>将排序键附加到由竖线分隔的投影上来指示projection的排序顺序。</p><p><img src="https://i.loli.net/2021/04/29/7YQv4Pb8pMfRNUt.png" alt="image-20210429212927647"></p><p>每个projection horizontally划分为1个或多个segment,segment被赋予一个Sid。在一个projection中基于sort key划分segment。</p><p>storage key在RS中编号为1、2、3,并不是物理存储的,而是从列中的元组的物理位置推断出来的。storage key在WS中物理存在,并以整数表示,比RS中任何段的最大整数storage key都大。</p><p>join index (sid, storage_key)</p><p><img src="https://i.loli.net/2021/04/29/esMbj9ANw4qrigZ.png" alt="image-20210429214528175"></p><h3 id="RS"><a href="#RS" class="headerlink" title="RS"></a>RS</h3><h4 id="encoding"><a href="#encoding" class="headerlink" title="encoding"></a>encoding</h4><p><strong>self order</strong> the column ordered by values in that column(按列中的值排序,即该列在sort key中)</p><p><strong>foreign-order</strong> by corresponding values of some other column in the same projection(该列不在sort key中)</p><ul><li><p>self order few distinct values:三元组序列(v, f, n)表示,v是存储在列中的值,f是v在列中第一次出现的位置,n是v在列中出现的次数。</p></li><li><p>foreign order few distinct values: (v, b), v是存储在列中的值,b是一个位图,表示该值存储在哪个位置</p><ul><li>0,0,1,1,2,1,0,2,1, we can Type 2-encode this as three pairs: (0, 110000100), (1, 001101001), and(2,000010010)</li></ul></li><li><p>Self-order, many distinct values:将列中的每个值表示为与列中前一个值的增量。</p><ul><li>1,4,7,7,8,12 would be represented by the sequence: 1,3,3,0,1,4</li></ul></li><li><p>Foreign-order, many distinct values: leave the values unencoded</p></li></ul><h3 id="WS"><a href="#WS" class="headerlink" title="WS"></a>WS</h3><p>WS也是一个列存储,实现了与RS相同的物理设计。因此,WS中存在相同的projection和join index。但是,存储表示形式有很大的不同,因为WS必须以事务方式有效地更新。</p><p>SK 显示存储</p><p>WS的水平分区方式与RS相同,因此RS segment与WS segment之间是1:1的映射。</p><p>假设WS size比较小 不压缩; 直接表示所有数据。因此,每个projection使用b -树索引,以保持逻辑排序键顺序。</p><p>WS projection中的每一列都表示为(v, sk),这样v是列中的值,sk是其对应的storage key。每个对在storage key上用b树表示。每个projection的sort key被另外表示为(s, sk),这样s是一个排序键值,sk是描述s第一次出现的storage key。同样,这个结构表示为sort key上的b树。要使用sort key search,可以使用后一个b -树查找storage key,然后使用前一个b -树集合查找记录中的其他字段。</p><h3 id="storage-management"><a href="#storage-management" class="headerlink" title="storage management"></a>storage management</h3><p>projection中的一个segment中的所有列存储在一个服务器节点上。join index和segment存储在一起。ws和相同key range的rs存储在一起。</p><h3 id="tuple-mover"><a href="#tuple-mover" class="headerlink" title="tuple mover"></a>tuple mover</h3><p>将创建一个新的RS segment,我们称之为RS’。然后,它从RS段的列中读入块,删除delete record vector值小于或等于low water mark的任何RS项,并合并来自WS的列值。然后将合并的数据写入新的RS段,该段随着合并的进行而增长。这种old master/new master将比就地更新策略更有效,因为基本上所有数据对象都将移动。需要维护join index.</p>]]></content>
<tags>
<tag>paper reading</tag>
</tags>
</entry>
<entry>
<title>X-engine</title>
<link href="/2021/05/07/x-engine/"/>
<url>/2021/05/07/x-engine/</url>
<content type="html"><![CDATA[<h2 id="X-engine"><a href="#X-engine" class="headerlink" title="X-engine"></a>X-engine</h2><h3 id="abstract"><a href="#abstract" class="headerlink" title="abstract"></a>abstract</h3><h3 id="introduction"><a href="#introduction" class="headerlink" title="introduction"></a>introduction</h3><p>网上电子商务交易有三个显著特征</p><ul><li><p>The tsunami problem</p><p>从11月11日00:00:00开始,底层存储引擎的事务工作负载(反映为每秒事务与时间的垂直峰值)急剧增加,就像一场巨大的海啸袭击了海岸。</p></li></ul><p><img src="https://i.loli.net/2021/05/07/5WIH1gBxqTfyEZl.png" alt="image-20210507161101933"></p><ul><li><p>The flood discharge problem</p><p>存储引擎必须能够在处理高并发电子商务的同时,将数据从主存快速移动到持久存储(即,释放内存中积累的洪水)</p></li><li><p>The fast-moving current problem</p><p>数据库缓存中的热记录会不断变化,任何记录的温度都可能迅速地从冷/暖变化到热或热到冷/暖。存储引擎需要确保能够尽快从深水中检索并有效地存储新出现的热记录。</p></li></ul><h3 id="system-overview"><a href="#system-overview" class="headerlink" title="system overview"></a>system overview</h3><p><img src="https://i.loli.net/2021/05/07/yl5bKwFj24dQScY.png" alt="image-20210507162545654"></p><p>类似 lsm tree structure</p><h3 id="detailed-design"><a href="#detailed-design" class="headerlink" title="detailed design"></a>detailed design</h3><h4 id="read-path"><a href="#read-path" class="headerlink" title="read path"></a>read path</h4><p><img src="https://i.loli.net/2021/05/07/iVfPCRubhBpJvUy.png" alt="image-20210507165025736"></p><p>extent ,类似SSTable, 每个extent包含了若干data blocks,schema data,block index.</p><p>data block: row-oriented,schema data: the types of each column,block index: the offset for each data block.</p><p>an extent 2MB across all levels. (extents reuse during compactions)</p><p>当向表中添加新列时,我们只需要在新版本的新extent上强制使用这个新列,而不需要修改任何现有的extent。当查询读取具有不同版本schema的extent时,会与最新版本的extent一致,并在旧版本中填充记录的空属性的默认值。</p><p><img src="https://i.loli.net/2021/05/07/R8PcTptgdSkeBH1.png" alt="image-20210507170114059"></p><p>row cache for optimizing point lookups.</p><p>LRU cache replacement policy.</p><p>a point lookup missed the memtables, the key of the query is hashed to its corresponding slot in the row cache for matches.</p><p>只在行缓存中保存最新版本的记录,由于时间局部性,这些记录有最大的机会被访问。为了实现这一点,我们在刷新期间用行缓存中的新记录替换旧版本的记录,以减少flush可能导致的cache miss。</p><p>block cache以数据块为单位缓冲数据。它为miss行缓存或 范围查询的每个请求提供服务(所以图5 中指向block cache有两条路径). table cache包含指向相应区段的子表头的元数据信息。找到extent后,我们使用Bloom过滤器过滤出不匹配的key。然后,我们搜索索引块来定位记录,最后从它的数据块中检索它。</p><p>由于 spatial locality, block cache可以作为row cache的补充.</p><p><img src="https://i.loli.net/2021/05/07/56a3pudVTzeEK9U.png" alt="image-20210507190136306"></p><p>multi version metadata index ,reuse extent without actually moving in the disk.</p><p><img src="https://i.loli.net/2021/05/07/xoM3Cw1e4stRF2n.png" alt="image-20210507190530999"></p><p><img src="https://i.loli.net/2021/05/07/Z7DIs2Kf1zEoBek.png" alt="image-20210507190641682"></p><p>Incremental cache replacement</p><p>During compactions, we check whether the data blocks of an extent to be merged are cached. If so, we replace the old blocks in the cache with the newly merged ones at the same place, instead of simply evicting all old ones</p><p>reduces the cache misses by keeping some blocks both updated and unmoved in the block cache</p><p>如果不更新cache 而是简单的evict from cache,那么下次访问这些block时还会产生io.</p><h4 id="write-path"><a href="#write-path" class="headerlink" title="write path"></a>write path</h4><p>memtable lock free skiplist </p><p>the state-of-the-art implementation of the skiplist-based memtables has a performance issue when querying hot records. Frequent updates on a single record generate many versions. If a hot record matches the predicate of a query with interests in only the latest version, the query may have to scan many old versions to locate the requested one.</p><p><img src="https://i.loli.net/2021/05/07/QPjiNuw79rOCD2k.png" alt="image-20210507193928980"></p><p>将同一记录的新版本垂直地附加到原始节点旁边,形成一个新的链表。减少扫描不必要的旧版本造成的开销</p><p>Asynchronous writes in transactions</p><p>避免one thread one transaction 写磁盘时的延迟,没有充分利用线程资源,在进行io时线程处于等待状态. 在一个大小为8的的队列中,只有一个线程在提交写,同时batch commit可以提高io.</p><p>Multi-staged pipeline</p><ul><li>在第一阶段,log buffering,线程从事务缓冲区收集每个写请求的wals(写前日志)到内存驻留日志缓冲区,并计算它们对应的CRC32错误检测代码。这个阶段包括计算和仅访问主存</li><li>在第二个阶段,log flushing,线程将缓冲区中的日志刷新到磁盘。刷新日志后,日志文件中的日志序号会上升。这些线程然后将已经被记录的写任务推到下一个阶段,</li><li>writing memtables,多个线程并行地追加活动memtable中的记录。这个阶段只访问主存</li><li>commits,所有任务完成的事务最终由多个线程并行提交,它们使用的资源,如锁被释放</li></ul><p>第一个和第三个阶段访问主存中不同的数据结构,而第二个阶段写入磁盘。因此,重叠它们可以提高主存和磁盘的利用率。</p><p>由于在前两个阶段的数据依赖关系(log buffer 或者 log flush 需要保证顺序),每个阶段我们只安排一个线程。对于其他阶段,我们分配多个线程并行处理。从前两个阶段提取任务的操作是抢占式的,只允许第一个到达的线程处理该阶段。其他阶段是并行,允许多个线程并行工作。</p><p><img src="https://i.loli.net/2021/05/07/laife4G6CIxQLtu.png" alt="image-20210507210044102"></p><h3 id="flush-and-compaction"><a href="#flush-and-compaction" class="headerlink" title="flush and compaction"></a>flush and compaction</h3><p>intra-Level0 compactions to actively merge warm extents in Level0</p><p><strong>data reuse</strong></p><p>在压缩过程中重用区段和数据块,以减少合并两个相邻层(即Leveli和Leveli+1)所需的I/ o数量。为了增加重用的机会并使其有效,我们将一个区段的大小减少到2 MB,并进一步将一个区段划分为多个16 KB的数据块。如果压缩过程中涉及到的某个区段的键范围没有与其他区段的键范围重叠,我们只需更新其对应的元数据索引,而不实际将其移动到磁盘上,就可以重用它。</p><p><img src="https://i.loli.net/2021/05/07/2wipISZTUBjuX5a.png" alt="image-20210507200014850"></p><p><strong>Asynchronous I/O</strong></p><p>压缩操作包括三个互不相干的阶段:</p><ul><li>从存储中检索两个输入区段,</li><li>合并它们</li><li>将合并后的区段(一个或多个)写回存储。</li></ul><p>第一和第三阶段是I/O阶段,而第二阶段是计算密集型阶段。我们在第一和第三阶段发出异步I/O请求。第二阶段是作为第一I/O阶段的回调函数实现的。当多个压缩并行运行时,第二阶段的执行与其他阶段的执行重叠,充分利用io。</p><p><strong>FPGA</strong></p><p>在X-Engine中区分不同的Compaction</p><ul><li>intra-Level0 Compaction,level 0 内部的compaction 不会进入level1 </li><li>Minor Compaction,在每两个Level之间合并,except the largest level</li><li>Major Compaction,合并最大级和它上面那一级</li><li>Self-Major Compaction,在最大级内进行合并,减少碎片,删除无用records。</li></ul><p>阿里巴巴的一个在线应用程序tailored的配置示例。在这个应用程序中,有频繁的删除。当应该删除的记录数量达到其阈值时,将触发压缩来删除这些记录,并将这种压缩给予最高优先级,以防止存储空间的浪费。在删除之后,将对Level0内的压缩进行优先级排序,以帮助加快对Level0中最近插入的记录的查找。minor、major和self major分别按此顺序排列</p><p>compaction concurrent at the level of sub-tables</p><p>感觉sub-table的概念类似与clickhouse中的分区.</p>]]></content>
<tags>
<tag>paper reading</tag>
</tags>
</entry>
<entry>
<title>Spitfire</title>
<link href="/2021/03/08/spitfire/"/>
<url>/2021/03/08/spitfire/</url>
<content type="html"><![CDATA[<h2 id="Spitfire-A-Three-Tier-Buffer-Manager-for-Volatile-and-Non-Volatile-Memory"><a href="#Spitfire-A-Three-Tier-Buffer-Manager-for-Volatile-and-Non-Volatile-Memory" class="headerlink" title="Spitfire: A Three-Tier Buffer Manager for Volatile and Non-Volatile Memory"></a>Spitfire: A Three-Tier Buffer Manager for Volatile and Non-Volatile Memory</h2><h3 id="abstract"><a href="#abstract" class="headerlink" title="abstract"></a>abstract</h3><p>buffer manager设计的关键假设就是数据必须从non-volatile storage迁移到DRAM, 才能对数据进行操作,而且storage速度比DRAM慢几个数量级。但是新的非易失性存储器(NVM)技术的到来,使这些先前的假设无效。</p><p>Hymem是最近提出的buffer manager,用于由DRAM、NVM和SSD组成的三层存储层次。Hymem支持cache-line-grained loading和NVM-aware数据迁移策略。虽然这些优化提高了它的吞吐量,Hymem却受到了两个限制。首先,它是一个单线程的缓冲区管理器。其次,在NVM仿真平台上进行的仿真。</p><p>本文提出了spilfile, multi-threaded three-tier buffer manager,在Optane PMMs上测试。我们介绍一个在多层存储层次结构中进行数据迁移的策略。数据迁移策略必须根据设备和工作负载的特征进行调整。鉴于此,我们提出了一种机器学习技术,用于为任意工作负载和存储层次自动调整策略。</p><h3 id="introduction-and-background"><a href="#introduction-and-background" class="headerlink" title="introduction and background"></a>introduction and background</h3><h4 id="Hymem"><a href="#Hymem" class="headerlink" title="Hymem"></a>Hymem</h4><ol><li>新分配的16 KB页面驻留在SSD上。当一个事务请求该页面时,Hymem急切地将整个页面移动到DRAM。</li><li>DRAM需要回收空间时,使用clock算法选择victim page evict.</li><li>选择好victim page后需要考虑是否将page放到nvm(如果page不在nvm的话).当page被考虑进入NVM,Hymem检查该页面是否在admission queue。如果是,则将其从队列中删除并放到nvm。否则,它将被添加到队列中,并直接移动到SSD,从而绕过NVM。</li><li>NVM需要回收空间时,同样使用clock算法选择page evict.</li></ol><p>Hymem使用cache line grained从一个冷的16 KB页面中提取热数据。通过只加载那些需要的cache line,Hymem降低了它的带宽消耗。</p><p>当一个事务请求一个页面时,Hymem检查该页面是否在DRAM缓冲区中。如果它不在DRAM缓冲区中,它会检查该页面是否在NVM缓冲区中。如果在NVM中,它直接移动驻留在NVM上的页面到DRAM。如果不在NVM中,它将驻留在SSD上的整个页面放入DRAM缓冲区,此时不利用从SSD到NVM的路径.</p><p><img src="https://i.loli.net/2021/03/08/rn6ti8OF47p5jvW.png" alt="image-20210308135402852"></p><p>Hymem维护了两个bitmasks(标记为resident和dirty)来跟踪loaded和dirty的cacheline, cacheline大小为64B,一个nvm page大小为16KB,所以一个page包含256个cacheline.</p><p><img src="https://i.loli.net/2021/03/08/LifqlwoAU23NDWu.png" alt="image-20210308140228268"></p><p>mini page 的大小为16个 cache line,如果超过16个cache line 会加载整个nvm page到DRAM.</p><p><img src="https://i.loli.net/2021/03/08/Lk5rtZpQ9AjhKuX.png" alt="image-20210308141425450"></p><h4 id="spitfire"><a href="#spitfire" class="headerlink" title="spitfire"></a>spitfire</h4><p>由于CPU能够直接操作在NVM中的数据,所以spitfire不会积极的将数据从NVM移动到DRAM,lazy数据迁移策略保证只有热数据会被移动到DRAM.</p><p>考虑到必须根据设备的特性和负载来调整迁移策略,本文采用ML的方法来为任意负载和存储层次自动调整策略。这种自适应数据迁移方案无需手动调优策略。</p><h4 id="conribution"><a href="#conribution" class="headerlink" title="conribution"></a>conribution</h4><ul><li>data migration in a multi-tier storage hierarchy</li><li>an adaptation mechanism that converges to a near-optimal policy for an arbitrary workload and storage hierarchy without requiring any manual tuning</li><li>multi-threaded, three-tier buffer manager</li><li>evaluate Spitfire on Optane DC PMMs</li><li>a set of guidelines for choosing the storage hierarchy and migration policy based on workload</li><li>an NVM-SSD hierarchy works well on write-intensive workloads</li></ul><h4 id="optane-dc-pmms"><a href="#optane-dc-pmms" class="headerlink" title="optane dc pmms"></a>optane dc pmms</h4><p>这里不太懂为啥没用pmdk.</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-attr">fd</span> = open((<span class="hljs-string">"/mnt/pmem0/file"</span>, O_RDWR, <span class="hljs-number">0</span>)<span class="hljs-comment">;</span><br><span class="hljs-attr">res</span> = ftruncate(fd, SIZE)<span class="hljs-comment">;</span><br><span class="hljs-attr">ptr</span> = mmap(nullptr, SIZE, PROT_WRITE, MAP_SHARED, fd, <span class="hljs-number">0</span>)<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure><h3 id="spiltfile-data-migration"><a href="#spiltfile-data-migration" class="headerlink" title="spiltfile data migration"></a>spiltfile data migration</h3><p><img src="https://i.loli.net/2021/03/08/dXw4xpkSfjEY5ti.png" alt="image-20210308141543015"></p><h4 id="bypass-dram-during-reads"><a href="#bypass-dram-during-reads" class="headerlink" title="bypass dram during reads"></a>bypass dram during reads</h4><p>$D_r$ 是从NVM到DRAM移动数据的可能性.当$D_r=0.01$意味着100次的read才会有一次将data 移动到DRAM,这种惰性策略保证温数据保存在NVM中不会移动到DRAM.</p><h4 id="bypass-dram-during-writes"><a href="#bypass-dram-during-writes" class="headerlink" title="bypass dram during writes"></a>bypass dram during writes</h4><p>$D_w$是写数据时使用DRAM的可能性.在DRAM-SSD的层次结构下,$D_w=1$.当$D_w$较小时,避免了频繁的从DRAM向下移动数据,而直接将数据在nvm中持久化.这样也可以避免DRAM中的热数据移出DRAM.</p><h4 id="bypass-nvm-during-reads"><a href="#bypass-nvm-during-reads" class="headerlink" title="bypass nvm during reads"></a>bypass nvm during reads</h4><p>当请求的page不在DRAM和NVM中,将SSD上的数据直接复制到DRAM,从而在读操作期间绕过NVM。如果读入DRAM缓冲区的数据随后没有被修改,并且被选择替换,那么只是简单地丢弃page。如果页面被修改,然后选择从DRAM中删除,那么spitfire会考虑将其移动到NVM.</p><p>$N_r$表示读操作时,将数据从SSD复制到NVM的可能性。 当一个页面从SSD中取出,然后在DRAM中修改并移出时,一个eager策略需要对NVM进行两次写操作:一次是在取出时,一次是在从DRAM中移出时。使用惰性策略时(例如,$N_r = 0.01$),spitfire会在修改后的页面被从DRAM中移出时才在NVM上保存这个page。这就消除了spitfire从SSD获取页面时对NVM的第一次写入操作的代价。</p><p>为了减少从SSD到NVM再到DRAM的数据迁移策略.可以采用从NVM到DRAM的惰性策略$(D_r=0.01)$,积极的从SSD到NVM的数据迁移策略$(N_r=0.2)$.</p><h4 id="bypass-nvm-during-writes"><a href="#bypass-nvm-during-writes" class="headerlink" title="bypass nvm during writes"></a>bypass nvm during writes</h4><p>通过在写操作过程中绕过NVM,spitfire可以确保只有经常从DRAM交换出来的页面才会存储在NVM上。这种优化类似于Hymem中DRAM到NVM的队列机制。与Hymem不同,spitfire并不显式地维护这样一个队列,而是采用概率方法。除了减少对NVM的写入次数外,这种优化还确保只有温数据存储在NVM缓冲区中。</p><p>$N_w$表示写操作时将数据从DRAM拷贝到NVM的概率。使用默认的写路径$N_w = 1$。较小的$N_w$减小了数据向NVM的向下迁移。当DRAM的容量与NVM相当时,这种惰性策略是有益的,因为它确保从DRAM上移出的冷数据不会驱逐NVM上的温数据。</p><h4 id="data-migration-policy"><a href="#data-migration-policy" class="headerlink" title="data migration policy"></a>data migration policy</h4><p>可以用元组表示数据移动策略:$<D_r,D_w,N_r,N_w>$,针对不同的负载,肯定会有不同的数据移动策略的参数设置.</p><p>假设page P产生了N次读,那么P被移动到DRAM的概率为$1-((1-D_r)^N)$.</p><h3 id="adaptive-data-migration"><a href="#adaptive-data-migration" class="headerlink" title="adaptive data migration"></a>adaptive data migration</h3><p>通过 模拟退火 simulated annealing (SA)来寻找最优的data migration policy.</p><h3 id="system-architecture"><a href="#system-architecture" class="headerlink" title="system architecture"></a>system architecture</h3><h4 id="buffer-management"><a href="#buffer-management" class="headerlink" title="buffer management"></a>buffer management</h4><p>mapping table记录page buffer在哪里(NVM DRAM).</p><p>当请求一个页面时,Spitfire执行一个table lookup,返回一个包含位置的page descriptor(如果有的话).如果该页面在DRAM上找到,那么它将返回一个对DRAM frame的引用。否则,如果在NVM上找到了该页面,并且迁移策略允许Spitfire绕过DRAM,那么它将返回对NVM frame的引用。如果在NVM和DRAM中都没有找到,spitfire从SSD中检索页面,并根据多层数据迁移策略将其放置在DRAM或NVM中。</p><p>page descriptor包含了latches,使用page的计数,a bit 确定是不是脏页.指针指向page.</p><p><img src="https://i.loli.net/2021/03/08/bcrHgdODACksJpT.png" alt="image-20210308201143589"></p><p>与Hymem类似,spitfire采用了clock缓存替换策略,回收DRAM和NVM中的空间。</p><h4 id="concurrency-control"><a href="#concurrency-control" class="headerlink" title="concurrency control"></a>concurrency control</h4><p>为了支持并发操作,使用了以下数据结构或协议:</p><ul><li>一个并发哈希表,用于管理从逻辑页标识符到共享页描述符的映射</li><li>用于缓存替换策略的并发bitmap</li><li>multi-versioned timestamp-ordering (MVTO)并发控制协议</li><li>乐观锁的并发B+树</li><li>用于线程安全页面迁移的轻量级锁</li></ul><p>spitfire为一个page descriptor维护了三个latch,当数据从NVM移动到SSD时,只需要锁NVM和SSD latch,这样还可以访问DRAM上的page.</p><p>当一个page在NVM和DRAM上都存在,访问时绕过DRAM,则DRAM中的page不包含NVM中的修改信息.为了防止这种问题,需要获取DRAM和NVM的latches,在将NVM上的page移动到DRAM之前释放掉其他对象对NVM上page的引用,最终释放获得的锁存。</p><h4 id="recovery"><a href="#recovery" class="headerlink" title="recovery"></a>recovery</h4><p>NVM-aware write-ahead logging protocol</p><p>日志记录包括:(1)事务标识符和页面标识符,(2)记录类型,(3)该事务前一个日志记录的日志序号,(4)日志记录前后的数据镜像。</p><p>一旦事务的提交日志记录被持久化到NVM日志缓冲区中,事务就被认为已提交。当NVM日志缓冲区大小达到阈值时,它的内容将异步追加到磁盘上的日志文件。在后台,spitfire会定期刷脏页面。但是,由于NVM是持久的,NVM缓冲区中的修改页面不会被刷新到SSD。因此,NVM缓冲区中的页比SSD对应页新。</p><p>在恢复过程中,spitfire首先重构NVM缓冲区来识别页的最新版本。这是通过扫描NVM缓冲区来收集页id并构造映射表来完成的。其次,NVM日志缓冲区需要追加到日志文件中,(将NVM中的日志append到日志文件应该是为了使用传统的方案恢复数据库)因为该缓冲区是持久的。一旦恢复了映射表并完成了日志文件,喷火式战斗机将继续使用传统的恢复方案恢复数据库。</p>]]></content>
<tags>
<tag>paper reading</tag>
</tags>
</entry>
<entry>
<title>ClickHouse in memory part和wal分析</title>
<link href="/2021/03/03/clickhouse_in_memory_format/"/>
<url>/2021/03/03/clickhouse_in_memory_format/</url>
<content type="html"><![CDATA[<h3 id="相关PR"><a href="#相关PR" class="headerlink" title="相关PR"></a>相关PR</h3><p><a href="https://github.com/ClickHouse/ClickHouse/pull/10697">https://github.com/ClickHouse/ClickHouse/pull/10697</a></p><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>在MergeTree中添加了part的in memory format,数据存储在内存中。 part在第一次合并时写入磁盘。 如果零件的行或字节大小小于阈值<code>min_rows_for_compact_part</code>和<code>min_bytes_for_compact_part</code>,则将以内存格式创建part。 还提供可选的<code>Write-Ahead-Log</code>支持,默认情况下启用此功能,并通过设置<code>in_memory_parts_enable_wal</code>进行控制。</p><h4 id="示例SQL"><a href="#示例SQL" class="headerlink" title="示例SQL"></a>示例SQL</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> default.mem (<br> a UInt32,<br> b UInt32,<br> c UInt32<br>) <span class="hljs-keyword">ENGINE</span> = MergeTree()<br><span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">BY</span> a<br><span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> b<br><span class="hljs-keyword">SETTINGS</span> min_rows_for_compact_part = <span class="hljs-number">3</span>;<br></code></pre></td></tr></table></figure><p>当单次插入数据行数小于3时,就会以in memory part的形式插入,in memory part的数据暂时不落盘,但会写日志到<code>wal.bin</code>文件。</p><h3 id="Write-Ahead-Log"><a href="#Write-Ahead-Log" class="headerlink" title="Write Ahead Log"></a>Write Ahead Log</h3><p>相关实现主要在<code>src/Storages/MergeTree/MergeTreeWriteAheadLog.h</code></p><p>当插入如下一行数据时,可以看到<code>wal.bin</code>中的内容。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">localhost :) <span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> default.mem <span class="hljs-keyword">values</span>(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>);<br></code></pre></td></tr></table></figure><figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs tap">$ hexdump -C wal.bin <br>00000000 <span class="hljs-number"> 01 </span>00<span class="hljs-number"> 02 </span>7b 7d<span class="hljs-number"> 00 </span>07<span class="hljs-number"> 31 </span> 5f<span class="hljs-number"> 31 </span>5f<span class="hljs-number"> 31 </span>5f<span class="hljs-number"> 30 </span>03<span class="hljs-number"> 01 </span> |...{}..1_1_1_0..|<br>00000010 <span class="hljs-number"> 01 </span>61<span class="hljs-number"> 06 </span>55<span class="hljs-number"> 49 </span>6e<span class="hljs-number"> 74 </span>33 <span class="hljs-number"> 32 </span>01<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 62 </span>06 |.a.UInt32.....b.|<br>00000020 <span class="hljs-number"> 55 </span>49 6e<span class="hljs-number"> 74 </span>33<span class="hljs-number"> 32 </span>02<span class="hljs-number"> 00 </span><span class="hljs-number"> 00 </span>00<span class="hljs-number"> 01 </span>63<span class="hljs-number"> 06 </span>55<span class="hljs-number"> 49 </span>6e |UInt32.....c.UIn|<br>00000030 <span class="hljs-number"> 74 </span>33<span class="hljs-number"> 32 </span>03<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span> |t32....|<br>00000037<br></code></pre></td></tr></table></figure><p>一个part对应<code>wal.bin</code>一条记录,插入一个part会被序列化为:<code>Version+metadata+ActionType+PartName+Block</code></p><p>具体逻辑在<code>void MergeTreeWriteAheadLog::addPart(DataPartInMemoryPtr & part)</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">MergeTreeWriteAheadLog::addPart</span><span class="hljs-params">(DataPartInMemoryPtr & part)</span></span><br><span class="hljs-function"></span>{<br> ...<br> <br> writeIntBinary(WAL_VERSION, *out);<br><br> ActionMetadata metadata{};<br> metadata.part_uuid = part->uuid;<br> metadata.write(*out);<br><br> writeIntBinary(<span class="hljs-keyword">static_cast</span><UInt8>(ActionType::ADD_PART), *out);<br> writeStringBinary(part->name, *out);<br> block_out->write(part->block);<br> block_out->flush();<br><br> ...<br>}<br><br></code></pre></td></tr></table></figure><ul><li><p><code>WAL_VERSION</code>为<code>UInt8</code>类型,1字节.</p></li><li><p><code>ActionMetadata</code>,包括<code>min_compatible_version(1字节),ser_meta.length()(VarInt)</code>.</p></li><li><p><code>ActionType</code>表示这次操作是插入还是删除。</p></li><li><p><code>PartName</code>表示这一part的分区键,最小block,最大block,merge level。在序列化为字符串时还会在开始写入<code>PartName</code>长度。</p></li><li><p><code>Block</code>由<code>NativeBlockOutputStream</code>负责序列化,具体实现逻辑在<code>src/DataStreams/NativeBlockOutputStream.h</code></p></li></ul><p>删除part与插入的日志类似,只是不会插入<code>block</code>内容。</p><h4 id="从WAL中恢复数据"><a href="#从WAL中恢复数据" class="headerlink" title="从WAL中恢复数据"></a>从WAL中恢复数据</h4><p>当重启clickhouse时,需要从WAL中恢复in memory part</p><p>主要实现逻辑在<code>MergeTreeData::MutableDataPartsVector MergeTreeWriteAheadLog::restore(const StorageMetadataPtr & metadata_snapshot)</code>,会在<code>void MergeTreeData::loadDataParts(bool skip_sanity_checks)</code>中被调用。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">MergeTreeData::MutableDataPartsVector <span class="hljs-title">MergeTreeWriteAheadLog::restore</span><span class="hljs-params">(<span class="hljs-keyword">const</span> StorageMetadataPtr & metadata_snapshot)</span></span><br><span class="hljs-function"></span>{<br> ...<br><br> <span class="hljs-keyword">while</span> (!in->eof())<br> {<br> ...<br><br> <span class="hljs-keyword">try</span><br> {<br> ...<span class="hljs-comment">//读metadata</span><br><br> <span class="hljs-keyword">if</span> (action_type == ActionType::DROP_PART)<br> {<br> dropped_parts.insert(part_name);<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (action_type == ActionType::ADD_PART)<br> {<br> ...<span class="hljs-comment">//从wal中读block内容</span><br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">"Unknown action type: "</span> + toString(<span class="hljs-keyword">static_cast</span><UInt8>(action_type)), ErrorCodes::CORRUPTED_DATA);<br> }<br> }<br> <span class="hljs-keyword">catch</span> (<span class="hljs-keyword">const</span> Exception & e)<br> {<br> ...<br> }<br><br> <span class="hljs-keyword">if</span> (action_type == ActionType::ADD_PART)<br> {<br> ...<span class="hljs-comment">//在内存中重建in memory part</span><br> }<br> }<br><br> MergeTreeData::MutableDataPartsVector result;<br> <span class="hljs-built_in">std</span>::copy_if(parts.begin(), parts.end(), <span class="hljs-built_in">std</span>::back_inserter(result),<br> [&dropped_parts](<span class="hljs-keyword">const</span> <span class="hljs-keyword">auto</span> & part) { <span class="hljs-keyword">return</span> dropped_parts.count(part->name) == <span class="hljs-number">0</span>; });<br><br> <span class="hljs-keyword">return</span> result;<br>}<br></code></pre></td></tr></table></figure><h3 id="in-memory-part写入数据"><a href="#in-memory-part写入数据" class="headerlink" title="in memory part写入数据"></a>in memory part写入数据</h3><p>实现主要在<code>src/Storages/MergeTree/MergeTreeDataPartWriterInMemory.h</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">MergeTreeDataPartWriterInMemory::write</span><span class="hljs-params">(</span></span><br><span class="hljs-function"><span class="hljs-params"> <span class="hljs-keyword">const</span> Block & block, <span class="hljs-keyword">const</span> IColumn::Permutation * permutation)</span></span><br></code></pre></td></tr></table></figure><p><code>block</code>为写入的数据块,<code>permutation</code>用来对<code>block</code>的数据排序,若为<code>nullptr</code>则不需要排序</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs c++"> ...<br><span class="hljs-keyword">if</span> (permutation)<span class="hljs-comment">//排序</span><br>{<br> ...<br>}<br><span class="hljs-keyword">else</span><br>{<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-keyword">auto</span> & col : columns_list)<br> result_block.insert(block.getByName(col.name));<br>}<br><br> ...<br> <br>part_in_memory->block = <span class="hljs-built_in">std</span>::move(result_block);<br><br><span class="hljs-keyword">if</span> (settings.rewrite_primary_key)<br> calculateAndSerializePrimaryIndex(primary_key_block);<br></code></pre></td></tr></table></figure><p>主要逻辑包括排序,然后将插入的<code>block</code>移动到part中的block,然后计算主键。在 in memory part中数据的存储格式仍然是列式。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">MergeTreeDataPartWriterInMemory::calculateAndSerializePrimaryIndex</span><span class="hljs-params">(<span class="hljs-keyword">const</span> Block & primary_index_block)</span></span><br><span class="hljs-function"></span>{<span class="hljs-comment">//计算主键</span><br> <span class="hljs-keyword">size_t</span> rows = primary_index_block.rows();<br> <span class="hljs-keyword">if</span> (!rows)<br> <span class="hljs-keyword">return</span>;<br><br> <span class="hljs-keyword">size_t</span> primary_columns_num = primary_index_block.columns();<br> index_columns.resize(primary_columns_num);<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>; i < primary_columns_num; ++i)<br> {<br> <span class="hljs-keyword">const</span> <span class="hljs-keyword">auto</span> & primary_column = *primary_index_block.getByPosition(i).column;<br> index_columns[i] = primary_column.cloneEmpty();<br> index_columns[i]->insertFrom(primary_column, <span class="hljs-number">0</span>);<br> <span class="hljs-keyword">if</span> (with_final_mark)<br> index_columns[i]->insertFrom(primary_column, rows - <span class="hljs-number">1</span>);<br> }<br>}<br><br></code></pre></td></tr></table></figure><p>在计算主键时,可以看出主键只会将第一行的主键值插入到<code>index_columns</code>中,并会根据<code>with_final_mark</code>来确定是否将<code>block</code>的最后一行插入到主键中。这里猜测是因为in memory part 的数据量一般比较小,所以作了比较简单的计算。</p><h3 id="in-memory-part-读取数据"><a href="#in-memory-part-读取数据" class="headerlink" title="in memory part 读取数据"></a>in memory part 读取数据</h3><p>实现主要在<code>src/Storages/MergeTree/MergeTreeDataPartReaderInMemory.h</code></p><p>主要实现逻辑在<code>readRows</code>中,将in memory part中的数据读到<code>res_columns</code>中并返回读取的行数。</p><p>如果读取的是整个part的行,则会把part中的列读到结果列中,如果只是部分行,则会创建新的列,并读取所需行。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">size_t</span> <span class="hljs-title">MergeTreeReaderInMemory::readRows</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> from_mark, <span class="hljs-keyword">bool</span> continue_reading, <span class="hljs-keyword">size_t</span> max_rows_to_read, Columns & res_columns)</span></span><br><span class="hljs-function"></span>{<br> ...<br> <span class="hljs-keyword">auto</span> column_it = columns.begin();<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>; i < num_columns; ++i, ++column_it)<br> {<br> <span class="hljs-keyword">auto</span> [name, type] = getColumnFromPart(*column_it);<br><br> <span class="hljs-comment">/// Copy offsets, if array of Nested column is missing in part.</span><br> <span class="hljs-keyword">auto</span> offsets_it = positions_for_offsets.find(name);<br> <span class="hljs-keyword">if</span> (offsets_it != positions_for_offsets.end())<br> {<br> ...<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (part_in_memory->block.has(name))<br> {<br> <span class="hljs-keyword">const</span> <span class="hljs-keyword">auto</span> & block_column = part_in_memory->block.getByName(name).column;<br> <span class="hljs-keyword">if</span> (rows_to_read == part_rows)<br> {<br> res_columns[i] = block_column;<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-keyword">if</span> (res_columns[i] == <span class="hljs-literal">nullptr</span>)<br> res_columns[i] = type->createColumn();<br><br> <span class="hljs-keyword">auto</span> mutable_column = res_columns[i]->assumeMutable();<br> mutable_column->insertRangeFrom(*block_column, total_rows_read, rows_to_read);<br> res_columns[i] = <span class="hljs-built_in">std</span>::move(mutable_column);<br> }<br> }<br> }<br><br> total_rows_read += rows_to_read;<br> <span class="hljs-keyword">return</span> rows_to_read;<br>}<br><br></code></pre></td></tr></table></figure>]]></content>
<tags>
<tag>ClickHouse</tag>
</tags>
</entry>
<entry>
<title>ClickHouse解析</title>
<link href="/2021/01/26/clickhouse_principle/"/>
<url>/2021/01/26/clickhouse_principle/</url>
<content type="html"><![CDATA[<h1 id="clickhouse简介"><a href="#clickhouse简介" class="headerlink" title="clickhouse简介"></a>clickhouse简介</h1><p>“When we released ClickHouse, we had only one goal in mind, to give people <strong>the fastest analytical DBMS</strong> in the world.” — Alexey Milovidov</p><h2 id="ClickHouse提出背景"><a href="#ClickHouse提出背景" class="headerlink" title="ClickHouse提出背景"></a>ClickHouse提出背景</h2><p>初始设计目标是服务于Yandex.Metrica.这是一款web流量分析工具.在采集数据时,一次页面点击(click),就会产生一条记录,clickhouse就是基于这样的点击事件流(Click Stream)的数据仓库(Data WareHouse).</p><p><img src="https://i.loli.net/2020/10/25/uLgO3bylXiJBS1D.png" alt="image-20201025125556599"></p><h2 id="clickhouse适用场景"><a href="#clickhouse适用场景" class="headerlink" title="clickhouse适用场景"></a>clickhouse适用场景</h2><p>各类数据分析类场景(BI领域等)</p><h2 id="clickhouse不适用的场景"><a href="#clickhouse不适用的场景" class="headerlink" title="clickhouse不适用的场景"></a>clickhouse不适用的场景</h2><ul><li>不支持事务<ul><li>已经有计划去实现在单个事务中支持大规模数据的insert.</li><li>完整的ANSI SQL Transaction不在roadmap中.</li></ul></li><li>不擅长根据主键按行粒度进行查询(虽然支持),故不应该把ClickHouse当作Key-Value数据库使用.</li><li>不擅长按行update delete(虽然支持).</li></ul><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul><li>DDL,DML,权限控制,数据备份与恢复,分布式</li><li>列式存储与数据压缩(降低IO 向量化执行SIMD)</li></ul><h2 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h2><p><img src="https://i.loli.net/2020/10/25/BoWdPmkbcTEeVIC.png" alt="image-20201025131707509"></p><ul><li>server 在接入层 ClickHouse 支持多种接口:<ul><li>Reust HTTP 方式.</li><li>ClickHouse Native,通过 ClickHouse 协议 TCP 的方式接入,性能会更好。</li><li>其他 mysql等.</li></ul></li><li>parser 解析器,解析器创建AST.</li><li>Interpreter 解释器,从AST创建查询执行流水线.<ul><li>IBlockInputStream接口总共有60多个实现类,它们涵盖了ClickHouse数据摄取的方方面面。这些实现类大致可以分为三类:第一类用于处理数据定义的DDL操作,例如DDLQueryStatusInputStream等;第二类用于处理关系运算的相关操作,例如LimitBlockInput-Stream、JoinBlockInputStream及AggregatingBlockInputStream等;第三类则是与表引擎呼应,每一种表引擎都拥有与之对应的BlockInputStream实现,例如MergeTreeBaseSelect-BlockInputStream(MergeTree表引擎). 类似于算子.以 insert为例.</li></ul></li><li>Storages</li><li>datatype 数据的序列化和反序列化.</li></ul><h1 id="mergetree"><a href="#mergetree" class="headerlink" title="mergetree"></a>mergetree</h1><p>表引擎是clickhouse设计中的特色.clickhouse存储层有多种表引擎.最常用最基本的是mergetree.</p><h3 id="基本数据类型"><a href="#基本数据类型" class="headerlink" title="基本数据类型"></a>基本数据类型</h3><p><strong>column field</strong></p><p>内存中的一列数据由一个column对象表示.如果需要操作单个具体的数值则需要使用field对象,表示一个单值.</p><ul><li><p>field类似union,可以存储不同类型的值,但在任何时候只有一个值可以被存储.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">/// src/Core/Field.h</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Field</span></span><br><span class="hljs-class">{</span><br> ...<br><span class="hljs-built_in">std</span>::<span class="hljs-keyword">aligned_union_t</span><DBMS_MIN_FIELD_SIZE - <span class="hljs-keyword">sizeof</span>(Types::Which),<br> Null, UInt64, UInt128, Int64, Int128, Float64, String, Array, Tuple, Map,<br> DecimalField<Decimal32>, DecimalField<Decimal64>, DecimalField<Decimal128>, DecimalField<Decimal256>,<br> AggregateFunctionStateData,<br> UInt256, Int256<br> > storage;<br> <br> ...<br>}<br></code></pre></td></tr></table></figure></li></ul><ul><li>根据数据类型不同,column有不同的实现对象.以<code>ColumnInt8</code>为例</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">using</span> ColumnInt8 = ColumnVector<Int8>;<br>...<br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ColumnVector</span> <span class="hljs-keyword">final</span> :</span> <span class="hljs-keyword">public</span> COWHelper<ColumnVectorHelper, ColumnVector<T>><br>{<br> ...<br> <span class="hljs-keyword">using</span> ValueType = T;<br> <span class="hljs-keyword">static</span> <span class="hljs-keyword">constexpr</span> <span class="hljs-keyword">bool</span> is_POD = !is_big_int_v<T>;<br> <span class="hljs-keyword">using</span> Container = <span class="hljs-built_in">std</span>::<span class="hljs-keyword">conditional_t</span><is_POD,<br> PaddedPODArray<ValueType>,<br> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span><ValueType>>;<br> ...<br>} <br></code></pre></td></tr></table></figure><p><strong>block</strong> </p><p>clickhouse的内部数据操作是面向block对象的.本质是由数据对象,datatype 与列名称组成的三元组(column datatype 列名称字符串).Column提供了数据的读取能力,而DataType知道如何正反序列化,在具体的实现过程中,Block并没有直接聚合Column和DataType对象,而是通过ColumnWithTypeAndName对象进行间接引用。</p> <figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">/// src/Core/Block.h</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ColumnWithTypeAndName</span></span><br><span class="hljs-class">{</span><br> ...<br> ColumnPtr column;<br> DataTypePtr type;<br> String name;<br> ...<br>}<br><br><span class="hljs-keyword">using</span> ColumnsWithTypeAndName = <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span><ColumnWithTypeAndName>;<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Block</span></span><br><span class="hljs-class">{</span>...<br> <span class="hljs-keyword">using</span> Container = ColumnsWithTypeAndName; <br> ...<br> <br>}<br></code></pre></td></tr></table></figure><h2 id="mergetree的创建"><a href="#mergetree的创建" class="headerlink" title="mergetree的创建"></a>mergetree的创建</h2><p>engine=MergeTree();</p><ul><li>partition by 分区键 </li><li>order by 排序键</li><li>primary key 主键</li></ul><p><img src="https://i.loli.net/2020/10/27/PLtsQ9b1NlDndJA.png" alt="image-20201027125337217"></p><ul><li><p>partition 分区目录 属于相同分区的数据最终会被合并到同一个分区目录,而不同分区的数据不会被合并在一起.</p></li><li><p>checksums 校验文件 存储了各类文件的size和size的哈希值 用于校验文件的完整性和正确性</p></li><li><p>columns 列信息 明文存储<img src="https://i.loli.net/2020/10/27/QGSK4Pw5UYB7V1D.png" alt="image-20201027125623314"></p></li><li><p>计数文件 记录当前数据分区目录下的数据总行数</p></li><li><p>primary.idx 一级索引文件 存放稀疏索引</p></li><li><p>[column].bin 数据文件</p></li><li><p>[column].mrk 列字段标记文件 保存了bin文件中的数据偏移量.首先通过稀疏索引(primary.idx)找到对应数据的偏移量信息(.mrk),再通过偏移量直接从.bin文件中读取数据</p></li><li><p>mrk2 如果采用了自适应大小的索引间隔 标记文件为mrk2</p></li><li><p>partition.dat minmax_[column].idx 如果使用了分区键 则会生成 例如EventTime字段对应的原始数据为2019-05-01、2019-05-05,分区表达式为PARTITION BY toYYYYMM(EventTime)。partition.dat中保存的值将会是2019-05,而minmax索引中保存的值将会是2019-05-012019-05-05。</p></li><li><p>skp_idx 二级索引</p></li></ul><h2 id="数据分区"><a href="#数据分区" class="headerlink" title="数据分区"></a>数据分区</h2><p>在ClickHouse中,数据分区(partition)是针对本地数据而言的</p><p>分区规则:</p><ul><li>不指定分区键 默认所有数据会被写入all分区</li><li>分区键为整型 直接按照整型的字符形式输出 作为分区id的取值</li><li>日期类型 按照yyyymmdd进行格式化后的字符形式输出</li><li>其他类型 hash之后的值作为分区id的取值</li></ul><p><img src="https://i.loli.net/2020/10/27/vqruVRd9wFLnCsT.png" alt="image-20201027131306288"></p><p>数据在写入时,会对照分区ID落入相应的数据分区</p><h3 id="分区目录命名"><a href="#分区目录命名" class="headerlink" title="分区目录命名"></a>分区目录命名</h3><p><img src="https://i.loli.net/2020/10/27/zbSaBYPRlDeqsN1.png" alt="image-20201027131505524"></p><h3 id="分区目录合并"><a href="#分区目录合并" class="headerlink" title="分区目录合并"></a>分区目录合并</h3><p>MergeTree的分区目录并不是在数据表被创建之后就存在的,而是在数据写入过程中被创建的。伴随着每一批数据的写入(一次INSERT语句), MergeTree都会生成一批新的分区目录。即便不同批次写入的数据属于相同分区,也会生成不同的分区目录。也就是说,对于同一个分区而言,也会存在多个分区目录的情况。</p><p>合并规则:</p><ul><li>MinBlockNum:取同一分区内所有目录中最小的MinBlockNum值。</li><li>MaxBlockNum:取同一分区内所有目录中最大的MaxBlockNum值。</li><li>Level:取同一分区内最大Level值并加1。</li></ul><p><img src="https://i.loli.net/2020/10/27/rTvLeyI6awNAgu9.png" alt="image-20201027132538594"></p><h2 id="一级索引"><a href="#一级索引" class="headerlink" title="一级索引"></a>一级索引</h2><p><img src="https://i.loli.net/2020/10/27/NegJICanu4xE3wo.png" alt="image-20201027133717459"></p><p>由于稀疏索引占用空间小,所以primary.idx内的索引数据常驻内存,取用速度自然极快。</p><p>ClickHouse对主键索引的定义和传统数据库的定义稍有不同,它的主键索引没用主键去重的含义,但仍然有快速查找主键行的能力。</p><p>ClickHouse的主键索引存储的是每一个granularity中起始行的主键值.</p><h2 id="索引查询过程"><a href="#索引查询过程" class="headerlink" title="索引查询过程"></a>索引查询过程</h2><p>MergeTree按照index_granularity的间隔粒度,将一段完整的数据划分成了多个小的间隔数据段,一个具体的数据段即是一个MarkRange。</p><p>三个步骤:</p><ul><li>生成查询条件区间</li><li>递归交集判断 如果不存在交集 剪枝算法优化整段markrange 存在交集将此区间进一步拆分成8个子区间,如果存在交集且步长小于8 则记录markrange返回.</li><li>合并markrange区间并返回</li></ul><p><img src="https://i.loli.net/2020/10/27/l1RGWoSLndmx4bK.png" alt="image-20201027140445322"></p><p>因为MarkRange转换的数值区间是闭区间,所以会额外匹配到临近的一个区间</p><h2 id="二级索引"><a href="#二级索引" class="headerlink" title="二级索引"></a>二级索引</h2><p>granularity定义了一行跳数索引能够跳过多少个index_granularity区间的数据。</p><p><img src="https://i.loli.net/2020/10/27/VWc2KZ9lUwOIrRQ.png" alt="image-20201027140918613"></p><p>跳数索引:</p><ul><li>minmax 记录了一段数据的最小最大值</li><li>set 完整形式为set(max_rows),其中max_rows是一个阈值,表示在一个index_granularity内,索引最多记录的数据行数。如果max_rows=0,则表示无限制,<img src="https://i.loli.net/2020/10/27/GHfhaBjyILbz3RN.png" alt="image-20201027141647356"></li></ul><h2 id="数据存储"><a href="#数据存储" class="headerlink" title="数据存储"></a>数据存储</h2><p><img src="C:\Users\87028\AppData\Roaming\Typora\typora-user-images\image-20201027142825161.png" alt="image-20201027142825161"></p><p><img src="https://i.loli.net/2020/10/27/9XiFhKzpqcrVuMJ.png" alt="image-20201027143035111"></p><p>.bin文件中引入压缩数据块的目的至少有以下两个:其一,虽然数据被压缩后能够有效减少数据大小,降低存储空间并加速数据传输效率,但数据的压缩和解压动作,其本身也会带来额外的性能损耗。所以需要控制被压缩数据的大小,以求在性能损耗和压缩率之间寻求一种平衡。其二,在具体读取某一列数据时(.bin文件),首先需要将压缩数据加载到内存并解压,这样才能进行后续的数据处理。通过压缩数据块,可以在不读取整个.bin文件的情况下将读取粒度降低到压缩数据块级别,从而进一步缩小数据读取的范围。</p><h2 id="数据标记"><a href="#数据标记" class="headerlink" title="数据标记"></a>数据标记</h2><p>每一个列字段[Column].bin文件都有一个与之对应的[Column].mrk数据标记文件,用于记录数据在.bin文件中的偏移量信息。</p><p>一行标记数据使用一个元组表示,一个index_granularity对应一行标记数据. 元组内包含两个整型数值的偏移量信息。它们分别表示在此段数据区间内,在对应的.bin压缩文件中,压缩数据块的起始偏移量;以及将该数据压缩块解压后,其未压缩数据的起始偏移量。</p><p><img src="https://i.loli.net/2020/10/27/THeQr7n4j8YuxE6.png" alt="image-20201027144946353"></p><p>编号与markrange对应</p><p><img src="https://i.loli.net/2020/10/27/JK2lnFD9UrRtHEC.png" alt="image-20201027145014833"></p><h2 id="数据写入过程"><a href="#数据写入过程" class="headerlink" title="数据写入过程"></a>数据写入过程</h2><p>生成分区目录,每一批数据写入都会生成一个新的分区目录,按照index_granularity索引粒度生成一级索引,mrk,bin文件</p><p><img src="https://i.loli.net/2020/10/27/cyLad4mWg2CJH6Y.png" alt="image-20201027153053719"></p><h2 id="数据读出过程"><a href="#数据读出过程" class="headerlink" title="数据读出过程"></a>数据读出过程</h2><p><img src="https://i.loli.net/2020/10/27/TIJRo8brPM1xF73.png" alt="image-20201027153952604"></p><h2 id="update"><a href="#update" class="headerlink" title="update"></a>update</h2><p>ClickHouse的异步update机制。ClickHouse对update的执行是低效的,ClickHouse内核中的MergeTree存储一旦生成一个Data Part,这个Data Part就不可再更改了。所以从MergeTree存储内核层面,ClickHouse就不擅长做数据更新删除操作。ClickHouse的语法把Update操作也加入到了Alter Table的范畴中.</p><p>当用户执行一个如上的Update操作获得返回时,ClickHouse内核其实只做了两件事情:</p><ol><li>检查Update操作是否合法;</li><li>保存Update命令到存储文件中,唤醒一个异步处理merge和mutation的工作线程;先查找到需要update的数据所在datapart,之后对整个datapart做扫描,更新需要变更的数据,然后再将数据重新落盘生成新的datapart,最后用新的datapart做替代并remove掉过期的datapart。</li></ol><p>使用insert语句代替update语句。当需要对某一指定id更新数据时,就重新插入一条该id的数据.</p><p>使用MergeTree的变种AggregatingMergeTree.</p><p>AggregatingMergeTree继承自 MergeTree,存储上和基础的MergeTree其实没有任何差异,而是在数据Merge的过程中加入了“额外的合并逻辑”, AggregatingMergeTree 会将相同主键的所有行(在一个数据片段内)替换为单个存储</p><p>配合anyLast函数,替换每行数据为一种预聚合状态。其中anyLast聚合函数声明聚合策略为保留最后一次的更新数据.</p><h2 id="insert-buffer"><a href="#insert-buffer" class="headerlink" title="insert buffer"></a>insert buffer</h2><p>默认情况下,每个单独的insert到<code>MergeTree</code>都会创建一个part,存储在文件系统上是一个单独的目录中,因此,向<code>MergeTree</code>进行插入最好是通过批量插入的方式进行.</p><p>将要写的数据缓冲到RAM中,定期将其刷新到另一个表中。在读取操作中,同时从缓冲区和另一个表读取数据。</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-constructor">Buffer(<span class="hljs-params">database</span>, <span class="hljs-params">table</span>, <span class="hljs-params">num_layers</span>, <span class="hljs-params">min_time</span>, <span class="hljs-params">max_time</span>, <span class="hljs-params">min_rows</span>, <span class="hljs-params">max_rows</span>, <span class="hljs-params">min_bytes</span>, <span class="hljs-params">max_bytes</span>)</span><br></code></pre></td></tr></table></figure><p>如果满足所有<code>min</code>条件或至少一个<code>max</code>条件,则将数据从缓冲区中刷新并写入目标表。</p><p>在写操作期间,数据被插入到<code>num_layers</code>数目的随机缓冲区中。或者,如果要插入的数据部分足够大(大于<code>max_rows</code>或<code>max_bytes</code>),则直接写入目标表,而省略缓冲区。</p><p>从 Buffer 表读取时,将从缓冲区和目标表(如果有)处理数据。<br>请注意,Buffer 表不支持索引。换句话说,缓冲区中的数据被完全扫描,对于大缓冲区来说可能很慢。(对于目标表中的数据,将使用它支持的索引。)</p><p>如果服务器异常重启,缓冲区中的数据将丢失。</p>]]></content>
<tags>
<tag>ClickHouse</tag>
</tags>
</entry>
<entry>
<title>拉曼查酿心人剧评</title>
<link href="/2020/12/25/%E6%8B%89%E6%9B%BC%E6%9F%A5%E9%85%BF%E5%BF%83%E4%BA%BA/"/>
<url>/2020/12/25/%E6%8B%89%E6%9B%BC%E6%9F%A5%E9%85%BF%E5%BF%83%E4%BA%BA/</url>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2020/12/26/sA1recgftJ7VuX6.jpg" alt="d4fd92bd8104c4b6c99d7c0c67930ae"></p><p>第一次去看这种环境话剧,一开始还很期待环境话剧的演出形式. 不像是观众在台下,演员在台上的那种传统演出形式. 演员和观众有了更多的互动, 观众也随着演出的转场而移步换景, 好像剧中的故事真的就发生在自己周围, 沉浸感十足.</p><p>拉曼查是西班牙的葡萄酒产区,故事也主要发生在这个地方. 辛雨追寻男朋友路涛的脚步来到了西班牙,然而却和路涛产生了矛盾. 后来房东姐姐过来安慰辛雨,并帮她安排了在拉曼查采摘葡萄的”高薪工作”. 其实在这里就可以大概猜出路涛的退缩可能就是因为房东丽娜. 后来,辛雨来到拉曼查,邂逅了罗蒙,在拉曼查的生活让辛雨逐渐和过去的自己和解,和路涛和解. 葡萄酿酒,拉曼查酿心. 最后辛雨接受了罗蒙的追求,留在了西班牙,留在了拉曼查.</p><p><img src="https://i.loli.net/2020/12/26/vrfkxd6uq29bwIz.jpg" alt="b9c42b5aeaf3d86d81416418b69437d"></p><p>故事比较简单,其中的许多细节都很用心.比如现场的环境布置, 跟着演员一起走过他们在马德里的屋子,坐着前往拉曼查的火车,到拉曼查的酒庄,都有一种身临其境的感觉. 其中一幕是观众隔着窗户和百叶窗在看,虽然听不到演员的对白,我站的比较靠后也没看到他们在屋子里发生了什么.但这就给观众一种置身其中而又旁观的感觉.还有在从马德里到拉曼查的路上,手电筒的光打在积木上,手电筒缓缓移动,在墙上打出火车行进的光影,特别惊喜.中间还穿插的很多的吉他,钢琴演奏还有歌舞.虽然我不太喜欢太多的歌舞元素,可能还是比较喜欢讲故事多一点吧.</p><p>ps: clickhouse在圣诞节的彩蛋,后面应该会写clickhouse的内容啦.</p><p>Happy new year.</p><p><img src="https://i.loli.net/2020/12/26/vioFQObKH5h7S3N.png" alt="f83fca24b7371cd9d257914514c3ebf"></p>]]></content>
<tags>
<tag>戏剧</tag>
</tags>
</entry>
<entry>
<title>LaTeX常用命令(3)</title>
<link href="/2020/12/24/LaTeX%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4(3)/"/>
<url>/2020/12/24/LaTeX%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4(3)/</url>
<content type="html"><![CDATA[<p>这次主要写一些数学相关的东西.</p><hr><p>在LaTeX中最常用到的就是文本模式和数学模式.数学模式又分为行内公式和行间公式两种.</p><p>简单来说,行内公式就是将数学式子写在文本行中,与文本融为一体,适用于比较简单的式子,比如说: <img src="https://www.zhihu.com/equation?tex=a%5E2+b%5E2=c%5E2" alt="[公式]"> .</p><p>而行间公式是将数学式插在文本行之间,自成一行.适用于较复杂的式子.</p><p>行内公式的三种形式</p><ul><li>$…….$</li><li>(…….)</li><li>\begin{math}……..\end{math}</li></ul><p>排版效果都相同,我一般使用第一种方法.</p><p>行间公式的三种形式</p><ul><li>$$……..$$</li><li>[……….]</li><li>\begin{displaymath}…..\end{displaymath}</li></ul><p>排版效果都相同,我一般使用第一种或第二种方法.</p><p>除此之外,还可以使用equation公式环境,会自动生成带序号的行间公式.</p><p>数学相关的宏包比较多,一般来说,公式宏包amsmath和字符宏包amssymb是最常用的.</p><p><strong>常用数学符号</strong></p><p>这里偷个懒,用一下刘海样老师的《LaTeX入门》中整理的数学符号。</p><p><img src="https://pic1.zhimg.com/80/v2-ccda495fa31337b40f2621cc43f91234_720w.jpg" alt="img"></p><p><img src="https://pic1.zhimg.com/80/v2-57501edad60547ac7caeedde28649960_720w.jpg" alt="img"></p><p><img src="https://pic3.zhimg.com/80/v2-42210e5e4377b1a88983b254c506327a_720w.jpg" alt="img"></p><p><img src="https://pic4.zhimg.com/80/v2-710f0732753a1b2226612ede151bcdf3_720w.jpg" alt="img"></p><p><img src="https://pic2.zhimg.com/80/v2-c588ef9e531c12e3fc0cda3ca9884875_720w.jpg" alt="img"></p><p><img src="https://pic4.zhimg.com/80/v2-d59db9aaa09c749eb6cf54e2f8e0def3_720w.jpg" alt="img"></p><p><img src="https://pic3.zhimg.com/80/v2-e8d8c9a5dfb8ebae49a285a424aea0d2_720w.jpg" alt="img"></p><p>同时如果遇到不会打的符号可以通过<a href="http://detexify.kirelabs.org/classify.html">这个</a>网站手写查询。</p><p>\boldmath 用来加粗数学符号,如向量。需用在数学模式之外。例:\boldmath$\alpha$</p><p>斜体大写希腊字母:\mathnormal</p><p>粗斜体希腊字母:\boldsymbol</p><p>空心大写字母:\mathbb</p><p>公式中如果需要文字可以使用\text{文字},将文字插入公式中。</p><p>\frac 与\dfrac</p><p>\frac生成的公式会在高度上压缩,\dfrac可以避免这个问题。</p><p>实例图如下</p><p><img src="https://pic1.zhimg.com/80/v2-baf4a635dee001d47e0fcd15af6be6d8_720w.jpg" alt="img"></p><p>上下标:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">\sum_{i=1}^{n}<br></code></pre></td></tr></table></figure><p><img src="https://pic4.zhimg.com/80/v2-be129ea3fea5f7b44f600cba5203c507_720w.jpg" alt="img"></p><p>多行上下标:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">$$\lim\limits_{\substack{P\to P_0\\P\in D}}f(P)=f(P_0)$$<br></code></pre></td></tr></table></figure><p><img src="https://pic2.zhimg.com/80/v2-82cb750d832ad886665b9a0afe7e31f5_720w.jpg" alt="img"></p><p>数学中的表格环境之前有介绍到,这里就不重复了。<a href="https://zhuanlan.zhihu.com/p/35041879">LaTeX-我的常用命令(2)</a></p><p>定理环境:</p><p>需要先使用定义定理类环境的命令:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">\newtheorem{环境名}[]{标题}[]<br></code></pre></td></tr></table></figure><p>示例:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs text">\newtheorem{defi}{定义}<br>\begin{defi}<br>$a^2+b^2=c^2$<br>\end{defi}<br></code></pre></td></tr></table></figure><p><img src="https://pic1.zhimg.com/80/v2-4dd330bed972976bb98583e69c17c940_720w.jpg" alt="img"></p><p>数学模式中的空白</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs text">\quad<br>\qquad<br>\,%大小约为3/18个\quad<br>\;%5/18\quad<br>\:%4/18\quad可用于对空白的控制<br></code></pre></td></tr></table></figure><p>自适应的放大命令</p><p>可根据内容自动放大括号</p><p>示例</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs text">\left(\drac{\frac ab +c}{d}\right)<br>%如果只出现左半边括号,也需要在另一端加\right.<br></code></pre></td></tr></table></figure><p><img src="https://pic3.zhimg.com/80/v2-f0b53419bc0f01609f5507debf855cf6_720w.jpg" alt="img"></p>]]></content>
<tags>
<tag>LaTeX</tag>
</tags>
</entry>
<entry>
<title>LaTeX常用命令(2)</title>
<link href="/2020/12/24/LaTeX%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4(2)/"/>
<url>/2020/12/24/LaTeX%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4(2)/</url>
<content type="html"><![CDATA[<p>这是第二篇,主要写一下表格,列表,图的内容,还有上次没写到的参考文献.</p><hr><h2 id="表格"><a href="#表格" class="headerlink" title="表格"></a>表格</h2><p>LaTeX提供了无表格框线环境tabbing和有表格框线的tabular,tabular*,array,其中array是用于数学模式的表格环境.</p><p>就我个人而言,主要使用tabular,array这两种表格环境.</p><p>先来介绍<strong>tabular</strong></p><p>tabular的命令结构如下:</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs tex">\begin{tabular}[位置]{列格式}<br>内容& 内容&// %其中& 表示分隔 ,//表示换行<br>\end{tabular}<br></code></pre></td></tr></table></figure><p>其中’位置’为可选参数,</p><p>‘列格式’可以设置表格中数据的对齐方式,列宽,列间距等</p><ul><li>l 左对齐</li><li>c 居中对齐</li><li>r 右对齐</li><li>| 画一条垂直线</li><li>|| 画两条垂直线</li><li>{n}{列格式} 用n次列格式 ,举例: *{3}{|c}相当于输入 |c|c|c</li><li>@{声明} 它会将侧边与列之间的空白或是列与列之间的空白删除插入声明的内容</li></ul><p><strong>合并单元格</strong></p><p>\cline{i-j}可以从第i列的左侧到第j列的右侧画一条线.</p><p>\hline 画一条与列表宽度相同的水平线</p><p>如果要合并行需要使用multirow 宏包,</p><p>\multirow{n}*{内容} 表示合并n行以’内容’填充其中.</p><p>\multicolumn{n}{列格式}{内容}表示合并n列,以’内容’填充,列格式与前面介绍的相同.下面举个例子来说明.</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs text">\documentclass[UTF8]{ctexart}<br>\usepackage{multirow}<br>\begin{document}<br>\begin{tabular}{||c|c|c|c||}<br>\hline<br>\multirow{2}*{合并行}&\multicolumn{3}{c||}{合并列}\\<br>\cline{2-4}<br>&测试&测试&测试\\<br>\hline<br>\end{tabular}<br>\end{document}<br></code></pre></td></tr></table></figure><p><img src="https://pic2.zhimg.com/80/v2-b1e312ddae0b39778eee21af6ef80a31_720w.jpg" alt="img"></p><p><strong>array</strong></p><p>array需要使用宏包array</p><p>array的命令结构如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs text">\begin{array}[位置]{列格式}<br>内容& 内容&// %其中& 表示分隔 ,//表示换行<br>\end{array}<br></code></pre></td></tr></table></figure><p>可以看到与tabular完全相同,array就是在数学模式中的表格环境.</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs text">\documentclass[UTF8]{ctexart}<br>\usepackage{array}<br>\begin{document}<br>\begin{equation}<br>\left\{<br>\begin{array}{*{3}{l@{+}}l@{=}l}<br>a_{11}x_{1}&a_{12}x_{2}&\cdots&a_{1n}x_n&c_1\\<br>a_{21}x_{1}&a_{22}x_{2}&\cdots&a_{2n}x_n&c_2\\<br>a_{i1}x_{1}&a_{i2}x_{2}&\cdots&a_{in}x_n&c_i\\<br>a_{m1}x_{1}&a_{m2}x_{2}&\cdots&a_{mn}x_n&c_m\\ <br>\end{array}\right.<br>\end{equation}<br>\end{document}<br></code></pre></td></tr></table></figure><p><img src="https://pic1.zhimg.com/80/v2-ebac80d794b8c4c05496e75ccea1cdfc_720w.jpg" alt="img"></p><h2 id="列表"><a href="#列表" class="headerlink" title="列表"></a>列表</h2><p>列表环境有itemize,enumerate和description</p><p>itemize相当于无序列表,enumerate相当于有序列表</p><p>itemize的命令结构如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs text">\begin{itemize}<br>\item[标志] 条目<br>\end{item}<br></code></pre></td></tr></table></figure><p>标志默认是实心大圆点,可以更改为其他标志,示例如下</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs text">\begin{itemize}<br>\item 第一条测试<br>\item[$\rightarrow$] 第二条测试<br>\end{itemize}<br></code></pre></td></tr></table></figure><p><img src="https://pic3.zhimg.com/80/v2-a276dfdc63a147ce4a75f5cb4b90bba6_720w.jpg" alt="img"></p><p>排序列表enumerate前面的标号默认是1,2,3….,不再赘述</p><h2 id="图形插入"><a href="#图形插入" class="headerlink" title="图形插入"></a>图形插入</h2><p>插图需使用graphics宏包</p><p>常用的插图命令为</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">\includegraphics[参数1=选项,...]{图名}<br></code></pre></td></tr></table></figure><p>下面介绍一下常用的参数</p><ul><li>height 插图的高度</li><li>width 插图的宽度</li><li>scale 相比于原图缩放系数</li><li>angle 正值表示逆时针旋转,负值表示顺时针旋转</li></ul><p>当把图插入段落中时需使用figure环境,示例如下</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs text">\begin{figure}[htbp]%<br>\centering<br>\includegraphics [scale=0.6]{test.jpg}<br>\caption{测试}%标题<br>\end{figure}<br></code></pre></td></tr></table></figure><blockquote><p>『h』当前位置。将图形放置在正文文本中给出该图形环境的地方。如果本页所剩的页面不够,这一参数将不起作用。<br>『t』顶部。将图形放置在页面的顶部。<br>『b』底部。将图形放置在页面的底部。<br>『p』浮动页。将图形放置在一只允许有浮动对象的页面上。</p></blockquote><p>当我们需要两个或多个图并排排列时,需使用minipage环境,示例如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs text">\begin{figure}[htbp]<br>\centering<br>\begin{minipage}[c]{0.5\textwidth}<br>\centering<br>\includegraphics[width=0.3\textwidth]{test.jpg}<br>\caption{测试1}<br>\end{minipage}%<br>\begin{minipage}[c]{0.5\textwidth}<br>\centering<br>\includegraphics[width=0.3\textwidth]{test.jpg}<br>\caption{测试2}<br>\end{minipage}<br>\end{figure}<br></code></pre></td></tr></table></figure><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>参考文献可以使用环境thebibliography,</p><p>thebibliography的命令结构如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs text">\begin{thebibliography}{最宽序号}<br>\bibitem[文献序号1]{检索名1} 文献信息1<br>\end{thebibliography}<br></code></pre></td></tr></table></figure><p>通常用99为最宽序号</p><p>文献序号用于设置文献在参考文献中的序号,可以不填,默认为按顺序的阿拉伯数字</p><p>检索名用于正文中的引用</p><p><strong>引用命令</strong></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">\cite{检索名1,检索名2...}<br></code></pre></td></tr></table></figure><p>在参考文献较少时可以使用thebibliography环境,为了更好的管理文献可以使用BibTeX,文献管理程序.</p>]]></content>
<tags>
<tag>LaTeX</tag>
</tags>
</entry>
<entry>
<title>LaTeX常用命令(1)</title>
<link href="/2020/12/23/LaTeX%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4(1)/"/>
<url>/2020/12/23/LaTeX%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4(1)/</url>
<content type="html"><![CDATA[<p>这一篇主要记录自己常用的一些LaTeX的操作,包括常用的一些格式,下一节LaTeX文章主要为数学公式及图表的内容.</p><h2 id="文档类型"><a href="#文档类型" class="headerlink" title="文档类型"></a>文档类型</h2><p>一般来说,我们写TeX文档时,第一句命令就是文档类型的命令.</p><p>文档类型主要有 article,report,book,beamer,还有用于中文排版的ctexart,ctexrep,ctexbook分别对应article,report,book.用于排版中文的文类可以不用再调用ctex宏包直接使用中文.而且文档中的图,表,日期等都是中文显示.一般写一些课程小论文用ctexart就可以.</p><h2 id="标题页"><a href="#标题页" class="headerlink" title="标题页"></a>标题页</h2><p>LaTeX中提供了一组题名的命令,可以用于生成标题页的题名,作者,日期等内容.</p><p>以下命令写在导言区中.</p><figure class="highlight mel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs mel">\title{论文}%论文题名<br>\author{作者}%作者姓名<br>%\<span class="hljs-keyword">date</span>{}%日期<br></code></pre></td></tr></table></figure><p>如果不设置日期,将默认是今天的日期.</p><p>在正文中</p><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs livescript"><span class="hljs-string">\maketitle</span><br></code></pre></td></tr></table></figure><p>将会自动生成默认格式的标题页</p><p><img src="https://pic4.zhimg.com/v2-c1cd03a4f65d8a0900d7c82d5a61f613_b.jpg" alt="img"></p><p>一般写一些课内小论文之类的直接用默认格式,如果要写论文或是毕业论文之类比较正式的文档就可以使用模板.</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css">\<span class="hljs-selector-tag">thanks</span>{}%添加脚注,对题名的说明或是作者简介之类.<br></code></pre></td></tr></table></figure><p>article默认题名与正文相连,不单独设页,book,report则会创建单独的一页.</p><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>一般在正文前都有摘要,在文档类article,ctexart中都提供了摘要环境</p><figure class="highlight ada"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs ada">\<span class="hljs-keyword">begin</span>{<span class="hljs-keyword">abstract</span>}<br>内容<br>\<span class="hljs-keyword">end</span>{<span class="hljs-keyword">abstract</span>}<br></code></pre></td></tr></table></figure><p>该环境会默认生成粗体字居中的摘要标题.</p><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><p>在正文之前使用命令</p><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs livescript"><span class="hljs-string">\tableofcontents</span><br></code></pre></td></tr></table></figure><p>就可以生成默认格式的目录,如果对默认的目录格式不满意可以使用titletoc宏包修改目录的格式.</p><p>目录格式命令</p><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs dust"><span class="xml">\titlecontents</span><span class="hljs-template-variable">{标题名}</span><span class="xml">[左间距]</span><span class="hljs-template-variable">{标题格式}</span><span class="hljs-template-variable">{标题标志}</span><span class="hljs-template-variable">{无序号标题}</span><span class="hljs-template-variable">{指引线与页码}</span><span class="xml">[下间距]</span><br></code></pre></td></tr></table></figure><p>下面是我使用的目录页设置命令</p><figure class="highlight taggerscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs taggerscript"><span class="hljs-symbol">\t</span>itlecontents{section}[0em]{<span class="hljs-symbol">\z</span>ihao{4}<span class="hljs-symbol">\b</span>f }{<span class="hljs-symbol">\t</span>hecontentslabel<span class="hljs-symbol">\ </span>}{}<br>{<span class="hljs-symbol">\h</span>space{.5em}<span class="hljs-symbol">\t</span>itlerule*[4pt]{$<span class="hljs-symbol">\c</span>dot$}<span class="hljs-symbol">\c</span>ontentspage}<br><span class="hljs-symbol">\t</span>itlecontents{subsection}[2em]{<span class="hljs-symbol">\v</span>space{0.1<span class="hljs-symbol">\b</span>aselineskip}<span class="hljs-symbol">\z</span>ihao{-4}}{<span class="hljs-symbol">\t</span>hecontentslabel<span class="hljs-symbol">\ </span>}{}<br>{<span class="hljs-symbol">\h</span>space{.5em}<span class="hljs-symbol">\t</span>itlerule*[4pt]{$<span class="hljs-symbol">\c</span>dot$}<span class="hljs-symbol">\c</span>ontentspage}<br><span class="hljs-symbol">\t</span>itlecontents{subsubsection}[4em]{<span class="hljs-symbol">\v</span>space{0.1<span class="hljs-symbol">\b</span>aselineskip}<span class="hljs-symbol">\z</span>ihao{-4}}{<span class="hljs-symbol">\t</span>hecontentslabel<span class="hljs-symbol">\ </span>}{}<br>{<span class="hljs-symbol">\h</span>space{.5em}<span class="hljs-symbol">\t</span>itlerule*[4pt]{$<span class="hljs-symbol">\c</span>dot$}<span class="hljs-symbol">\c</span>ontentspage}<br></code></pre></td></tr></table></figure><h2 id="页眉页脚"><a href="#页眉页脚" class="headerlink" title="页眉页脚"></a>页眉页脚</h2><p>LaTeX中有4种页眉页脚的版式</p><ol><li>empty 页眉页脚都空置</li><li>plain 页眉空置,页脚中间是页码,无页脚线</li><li>headings 偶数页页眉左端是页码,右端是章标题;奇数页页眉的右端是页码,左端是节标题,是book类的默认格式</li><li>myheadings 格式与headings基本相同,左页页眉的右端和右页页眉的左端空置</li></ol><p>使用命令\pagestyle{}设置版式,使用\thispagestyle{}设置当前页的格式,页码一般从正文开始,所以正文前的页一般要设置为empty版式,</p><p>如果需要自己设置页码页眉的格式可以用fancyhdr宏包.</p><figure class="highlight mathematica"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs mathematica">\<span class="hljs-variable">fancyhead</span><span class="hljs-punctuation">[</span>参数<span class="hljs-punctuation">]</span><span class="hljs-punctuation">{</span>页眉内容<span class="hljs-punctuation">}</span><br>\<span class="hljs-variable">fancyfoot</span><span class="hljs-punctuation">[</span>参数<span class="hljs-punctuation">]</span><span class="hljs-punctuation">{</span>页眉内容<span class="hljs-punctuation">}</span><br><span class="hljs-operator">%</span>参数选项可以为<span class="hljs-variable">EL</span><span class="hljs-operator">,</span><span class="hljs-variable">EC</span><span class="hljs-operator">,</span><span class="hljs-variable">ER</span><span class="hljs-operator">,</span><span class="hljs-variable">OL</span><span class="hljs-operator">,</span><span class="hljs-variable">OC</span><span class="hljs-operator">,</span><span class="hljs-variable">OR</span>其中<span class="hljs-built_in">E</span>代表左页<span class="hljs-operator">,</span><span class="hljs-built_in">O</span>代表右页<span class="hljs-operator">,</span><span class="hljs-variable">L</span><span class="hljs-operator">,</span><span class="hljs-built_in">C</span><span class="hljs-operator">,</span><span class="hljs-variable">R</span>分别代表左<span class="hljs-operator">,</span>中<span class="hljs-operator">,</span>右<br></code></pre></td></tr></table></figure><p>目录页的设置可以参考<a href="http://www.ctex.org/PackageCTeX/">这里</a></p><h2 id="代码环境"><a href="#代码环境" class="headerlink" title="代码环境"></a>代码环境</h2><p>需使用listings宏包,使用lstset在导言区配置,</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs routeros">\lstset{<br><span class="hljs-attribute">numbers</span>=left, <br><span class="hljs-attribute">frame</span>=tb,<br><span class="hljs-attribute">aboveskip</span>=3mm,<br><span class="hljs-attribute">belowskip</span>=3mm,<br><span class="hljs-attribute">showstringspaces</span>=<span class="hljs-literal">false</span>,<br><span class="hljs-attribute">columns</span>=flexible,<br><span class="hljs-attribute">framerule</span>=1pt,<br><span class="hljs-attribute">rulecolor</span>=\color{gray!35},<br><span class="hljs-attribute">backgroundcolor</span>=\color{gray!5},<br>basicstyle={\ttfamily},<br><span class="hljs-attribute">numberstyle</span>=\tiny\color{gray},<br><span class="hljs-attribute">keywordstyle</span>=\color{blue},<br><span class="hljs-attribute">commentstyle</span>=\color{dkgreen},<br><span class="hljs-attribute">stringstyle</span>=\color{mauve},<br><span class="hljs-attribute">breaklines</span>=<span class="hljs-literal">true</span>,<br><span class="hljs-attribute">breakatwhitespace</span>=<span class="hljs-literal">true</span>,<br><span class="hljs-attribute">tabsize</span>=3,<br>}<br></code></pre></td></tr></table></figure><p>以上是我的代码环境设置,可以参考.</p><p>在需要插入代码的时候</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs arduino">\<span class="hljs-built_in">begin</span>{lstlisting}[language=C++,escapeinside=``]<br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span><span class="hljs-meta-string"><iostream></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;<br><span class="hljs-keyword">int</span> main<br>{<br><span class="hljs-built_in">cout</span><<<span class="hljs-string">"Hello world!"</span><<<span class="hljs-built_in">endl</span>;<span class="hljs-comment">//`输出`</span><br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br>\<span class="hljs-built_in">end</span>{lstlisting}<br></code></pre></td></tr></table></figure><p>其中如果需要使用中文需要逃逸环境.</p><p>显示效果如下:</p><p><img src="https://pic3.zhimg.com/v2-32d7e95010dac6872d14c370040b9e62_b.jpg" alt="img"></p><h2 id="其他一些想到的"><a href="#其他一些想到的" class="headerlink" title="其他一些想到的"></a>其他一些想到的</h2><p>geometry宏包可以改变页边距</p><p>使用如下:</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs vim">\usepackage[a4paper,<span class="hljs-keyword">left</span>=<span class="hljs-keyword">cm</span>,<span class="hljs-keyword">right</span>=<span class="hljs-keyword">cm</span>,top=<span class="hljs-keyword">cm</span>,bottom=<span class="hljs-keyword">cm</span>]{geometry}<br></code></pre></td></tr></table></figure><p>脚注:</p><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs dust"><span class="xml">\footnote</span><span class="hljs-template-variable">{文字}</span><br></code></pre></td></tr></table></figure><p>间距调整</p><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gcode">\<span class="hljs-attr">vspace{1</span>cm}<span class="hljs-meta">%</span>水平间距调整<br>\<span class="hljs-attr">vskip 7</span>cm <span class="hljs-meta">%</span>垂直间距调整<br></code></pre></td></tr></table></figure>]]></content>
<tags>
<tag>LaTeX</tag>
</tags>
</entry>
<entry>
<title>packed memory arrays-rewired</title>
<link href="/2020/12/14/packed%20memory%20arrays-rewired/"/>
<url>/2020/12/14/packed%20memory%20arrays-rewired/</url>
<content type="html"><![CDATA[<h1 id="packed-memory-arrays-rewired"><a href="#packed-memory-arrays-rewired" class="headerlink" title="packed memory arrays-rewired"></a>packed memory arrays-rewired</h1><h1 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h1><h2 id="a-b-tree"><a href="#a-b-tree" class="headerlink" title="(a,b)-tree"></a>(a,b)-tree</h2><p>(a,b)-tree是一个平衡的(所有的叶子在同一层)搜索树,</p><ul><li>2≤a≤(b+1)/2,当a==(b+1)/2时 为b-tree</li><li>根结点最多有b个子结点。</li><li>除了根节点之外,每个内部节点至少有a个子节点,最多有b个子节点。</li><li>所有从根到叶的路径都是相同的长度。</li></ul><p>(2,3)-tree 是b树 (a,b)-tree相当于b-tree的扩展,b-tree是(a,b)的特例. </p><p><a href="https://cs.lmu.edu/~ray/notes/abtrees/">https://cs.lmu.edu/~ray/notes/abtrees/</a></p><p><a href="https://en.wikipedia.org/wiki/(a,b)-tree">https://en.wikipedia.org/wiki/(a,b)-tree</a></p><h2 id="传统-PMAa"><a href="#传统-PMAa" class="headerlink" title="传统 PMAa"></a>传统 PMAa</h2><p>一个数组,其中的元素是按照排序的顺序存储的,交叉元素或间隙。间隙的目的是提供额外的空间,以便在数组的任意位置插入新元素,而不需要移动现有元素的长序列来维持排序顺序。</p><h3 id="PMA的实现"><a href="#PMA的实现" class="headerlink" title="PMA的实现"></a>PMA的实现</h3><p>搜索数组中的元素可以通过二分搜索利用排序顺序来实现。范围扫描包括在范围的两个端点之间对数组进行顺序迭代。这里,空槽被简单地忽略。要插入新元素,算法首先在数组中搜索它的目标槽。但是,如果该位置已经被另一个元素e占据,则算法首先将e和e的所有相邻元素移向数组中最接近的间隙。最后,要删除数组中的元素,算法只需将其槽标记为空。</p><p>在部分数据变得过于稀疏或过于密集时,在本地重新调整数据结构。</p><ol><li>定位要插入元素的位置。我们要么事先知道这个,要么执行代价为O(log2N)的二分查找。</li><li>如果我们要插入的单元格是空闲的,我们只需添加元素,并将该单元格标记为used。</li><li>但是,如果要插入的单元格被使用了,我们计算所需单元格所在的最小块(大小为log N)的上限密度阈值,并检查是否会违反上限密度阈值。如果我们注意到没有超过阈值,我们只需重新平衡所有元素,包括新的元素到那个块中。如果我们违反了较高的密度阈值,我们考虑一个两倍大的块(其中包括我们将插入的单元格),并检查是否违反了密度阈值。我们不断向上移动,直到找到一个不违反上密度阈值的块。</li><li>如果我们找不到这样的块,我们就分配一个两倍大的数组,并整齐地将所有现有的元素复制到新的数组中,元素之间的间隔是恒定的.</li></ol><h3 id="APMA"><a href="#APMA" class="headerlink" title="APMA"></a>APMA</h3><p>hammering 敲打指的是连续且频繁的插入发生在数组的相同区域。</p><p>在hammering发生的段放置更多的gap,但也可能会出现乒乓效应,性能甚至不如传统PMA.</p><p><img src="https://i.loli.net/2020/10/31/3lUX82qmFJyBtip.png" alt="image-20201031194615762"></p><p>如图所示,最后插入的是14,15,16.apma认为在第一段中插入数据过多,于是在rebalance的过程中在第一段中放置更多的gap,但是实际上的插入序列如果是17,18,19,则性能可能会很差.</p><h3 id="Calibrator-tree"><a href="#Calibrator-tree" class="headerlink" title="Calibrator tree"></a>Calibrator tree</h3><p><img src="https://i.loli.net/2020/11/08/rug3HlaGUioPILm.png" alt="image-20201108164808490"></p><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>基于树的索引结构的物理内存布局会随着时间的推移而恶化(比如B-树中的合并与分裂),物理级别上的顺序扫描就会变成非顺序的,因此扫描操作会变慢.Packed Memory Arrays(PMA)通过管理所有数据在一个顺序稀疏阵列来防止顺序扫描变成非顺序的。本文在PMA的基础上提出了一个改进的数据结构:Rewired Memory Array(RMA)。RMA可以达到具有竞争力的更新和点查找性能,同时始终提供优越的扫描性能-接近密集列扫描。</p><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>针对列存存储引擎,本文提出了一种基于PMA的数据结构。列存变得稀疏而不是密集。在存储的元素中有空的间隙,以适应未来可能的更新。因此,可以直接就地执行更新。扫描是顺序的,比(a,b)-tree快. 更新可以在对数复杂度完成,但比不上(a,b)-tree。</p><p><img src="https://i.loli.net/2020/11/08/8FaG4lIwVOip7PD.png" alt="image-20201108124846773"></p><p>有几个因素会阻碍稀疏数组的性能。对于扫描,通过检查数组的每个插槽是否被填满,需要付出大量的CPU 代价。对于更新,数据结构偶尔需要重新平衡,这增加了这些操作的平均成本。此外,在重新平衡期间,数组中的元素可能会被移动,这也会导致PMAs的索引分隔符键的更新.(索引存储每个段的最小key).在存在数据倾斜的情况下,稀疏数组会遇到最坏的情况,并且在更新期间会进行更多的内部重组.</p><h3 id="主要贡献"><a href="#主要贡献" class="headerlink" title="主要贡献"></a>主要贡献</h3><ul><li>通过提出cluster、固定大小的段、静态索引和内存重布线,克服了scan的一些性能问题,同时减少了内部重平衡的延迟。</li><li>对自适应再平衡进行了改进。自适应再平衡减少了出现更新倾斜时发生的再平衡数量。尽管如此,我们还是发现了APMA的自适应再平衡策略是有害的,并提出了一种新的算法,解决了它的局限性。</li><li>提出了一种新的批量装载算法。它特别适合流场景,其中数组的基数保持不变,并且定期分批执行具有相同插入和删除数量的更新。</li></ul><h2 id="RMA"><a href="#RMA" class="headerlink" title="RMA"></a>RMA</h2><p>RMA的目的是提供快速的面向列的扫描,同时在更新方面接近(a, b)树。</p><h3 id="segments"><a href="#segments" class="headerlink" title="segments"></a>segments</h3><p>段的大小是固定的,不依赖于数据结构的当前基数或容量,将段大小绑定到I/O模型的块大小O(B),</p><p>cluster:将长序列的元素放在一起,将长序列的间隔放在一起.</p><p>在每个槽,扫描需要检查它是否为空。为了避免检查槽是否为空,我们在一个名为cards的数组中跟踪每个段的当前基数。我们把所有的元素放在片段的一端,把间隙放到另一端。奇数段将元素放在右边,偶数段将元素放在左边.</p><p>扫描对每两个片段的密集值序列执行一个紧循环。(在两个段内进行二分?)</p><p>RMAs非常像(a, b)树。实际上,组成数组的段类似于(a, b)树的叶节点。</p><p>(a, b)树在更新中有优势,而稀疏数组在扫描中有优势。</p><h3 id="index"><a href="#index" class="headerlink" title="index"></a>index</h3><p>RMAs维护一个静态索引以改进点查找和更新。它是静态的,在RMA调整大小后构建,它包含固定数量的条目。尽管如此,单个条目还是可以改变的,在再平衡期间就会发生。索引只有分隔符键被存储,打包在一个连续的数组中。节点遍历是通过计算节点与数组中当前位置的偏移来执行的</p><p><img src="https://i.loli.net/2020/11/08/mv9RHA1MGEOdJD3.png" alt="image-20201108135554072"></p><p>根节点包含r分隔符键,和r + 1个孩子,最左边的r个子树是高度为h - 1的满子树.槽的内容是相关段的分隔键.</p><h3 id="re-balancing"><a href="#re-balancing" class="headerlink" title="re-balancing"></a>re-balancing</h3><p>rebalance包含两个过程,第一个过程中,将元素移动和压缩到间隔的右端或辅助附加存储中。在第二个过程中,继续前进,将元素复制到它们在数组中的最终位置。如果重新平衡的间隔小于虚拟页,我们的解决方案仍然遵循这种方案。否则,我们直接将元素从数组的页面移动并直接展开,以将parray重新平衡为一组未使用的物理页面pbuffer。最后,我们在parray和pbuffer之间一个一个地交换虚拟地址。这种技术的优点是它只对每个元素执行一次复制,而不是两次。</p><p><img src="https://i.loli.net/2020/11/08/X8KJVozAfnjyevb.png" alt="image-20201108143530259"></p><p>除了RMA使用的空间之外,我们还维护了一组按需分配的备用缓冲区。在重新平衡时,元素被重新分配到缓冲区空间中(参见图6b)。然后,使用内存重新连接,来自缓冲区的页面成为阵列的一部分,而阵列的旧物理页面成为备用缓冲区,以便在未来重新平衡时重用(参见图6c)。</p><p>在RMA当前使用的存储之后是备用缓冲区的物理页面(如图6a所示)。当需要扩展RMA时,我们吸收RMA中现有的备用缓冲区。如果需要缩小RMA,我们将RMA结束时释放的页面吸收到缓冲区中。好处是减轻从操作系统获取新的物理页面的开销.</p><h3 id="bulk-loading"><a href="#bulk-loading" class="headerlink" title="bulk loading"></a>bulk loading</h3><p>避免在单个批处理中对相同的段进行多次再平衡。</p><p>自顶向下的策略:假设批处理中的元素已经预先排序,关键思想是从根开始遍历校准器树,然后递归地将输入序列传播给子元素。但是,如果给定节点的阈值不能满足,算法将触发再平衡,将输入序列与当前窗口中的现有元素合并。缺点是可能会造成额外的rebalance,因为顶部的阈值比较小.</p><p>自底向上的策略:首先,对插入的输入序列S进行排序。然后,该算法分三次运行。在第一次遍历中,它扫描S,只改变将插入元素的每个段的最终基数。在第二步中,它扫描被插入的片段,检查阈值是否被遵守。如果没有,它确定需要重新平衡的段间隔。最后,它对被插入的片段进行第三步。如果一个段没有标记进行再平衡,它就简单地插入来自S的相关元素,否则,rebalance第二步中的段间隔,合并相关的现有元素与排序后的序列S. 该方法可以扩展到批量插入和删除。</p><h3 id="kv分离"><a href="#kv分离" class="headerlink" title="kv分离"></a>kv分离</h3><p>改进了点查找,因为查找特定键需要遍历的内存空间更少了。</p><h3 id="自适应-re-balancing"><a href="#自适应-re-balancing" class="headerlink" title="自适应 re-balancing"></a>自适应 re-balancing</h3><p>采用自适应再平衡来减少锤击带来的未来再平衡的数量。自适应策略被分为两个不同的部分。在第一部分中,在每次插入时,会在一个名为Detector的自定义数据结构中收集一些额外的元数据。第二部分是实际的再平衡机制,其中检查收集的元数据,以确定数组中元素的重新分布。其核心算法是递归的自适应算法。它从当前calibrator子树的顶部开始,在每一步中,它将确定分配到其子节点上的元素的数量。</p><p><img src="https://i.loli.net/2020/10/31/bPMGxNy9Unr4pDz.png" alt="image-20201031200041751"></p><p><img src="https://i.loli.net/2020/10/31/9KoMQU6nFLWP3iC.png" alt="image-20201031200641386"></p><h3 id="detector"><a href="#detector" class="headerlink" title="detector"></a>detector</h3><p>算法1是用于更新片段s元数据的代码,在每次插入数据之后调用。它首先在Q中记录当前操作的时间戳。时间戳可以通过某个离散的全局计数器或CPU时间戳计数器获得。此外,它还检查正在插入的键k的后继或前驱是否分别匹配kbwd或kfwd。如果两个键中有一个匹配,相关的计数器增加1,直到最大阈值SC。否则,两个计数器都减少1。当计数器达到0时,相关的kbwd和kfwd值被succk和predk替换</p><p>与kbwd关联的值3意味着,在最近的插入中,刚刚插入的键的后继至少有3次是19。因此,算法可能会猜测,未来新的插入很可能在这个范围内[predecessor(19), 19]。</p><p><img src="https://i.loli.net/2020/11/08/rsqHA8uc6JOZFdL.png" alt="image-20201108151101780"></p><h3 id="自适应算法"><a href="#自适应算法" class="headerlink" title="自适应算法"></a>自适应算法</h3><p>自适应算法对传统的TPMA再平衡过程进行了整合和扩充。与TPMA一样,整个过程的第一步是找到重新平衡的窗口。该操作通过遍历和验证校准树的密度阈值来完成。第二步,称为预处理阶段,包括产生一组有标记的间隔,利用检测器中的信息。如果没有创建标记间隔,则按照TPMA中的方法进行再平衡过程.否则,第三步是实际的自适应算法。该算法自顶向下遍历calibrator树,确定要放置在每个子节点上的元素数量,传输基数最小的节点中标记的间隔。这一步的输出是W中所有段的目标基数。在自适应算法的执行过程中,数组中的任何元素都不会被物理复制。只有在其结束时,当W中所有段的目标基数已经确定时,元素才会在数组中重新分布。</p><p>标记一些最近有更新的段。如果其段关联的计数器大于给定的阈值,则会发出带有kbwd或kfwd的大小为2的间隔.</p><p>自适应算法是自顶向下遍历校准器子树,根在W.</p><p>objective function 尝试将相同数量的标记间隔重新分配给每个子节点。如果不能做到这一点,例如 只有一个间隔 其余标记的间隔被移动到基数最小的子节点</p><p><img src="https://i.loli.net/2020/11/08/JHCdTKugaA9m7oN.png" alt="image-20201108193201610"></p><p>在继续进行下一个递归级别之前,可能需要增加或者减少节点的目标基数。如果一个孩子v的元素密度小于其较低的阈值,那么它就会尽可能多地从其兄弟姐妹那里借用元素,从而使元素密度大于给定阈值.</p><p><img src="https://i.loli.net/2020/11/02/Gz8yB57mYSNxIJA.png" alt="image-20201102205433999"></p>]]></content>
<tags>
<tag>paper reading</tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/2020/12/12/hello-world/"/>
<url>/2020/12/12/hello-world/</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo new <span class="hljs-string">"My New Post"</span><br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo server<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo generate<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo deploy<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>]]></content>
</entry>
</search>