[TOC]
存储与检索
一个数据库在最基础的层次上需要完成两件事情:当你把数据交给数据库时,它应当把数据 存储起来;而后当你向数据库要数据时,它应当把数据返回给你
-这有点不言自明
#!/bin/bash
db_set () {
echo "$1,$2" >> database
}
db_get () {
grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
}
示例很有意思,有点将db解构的意思, 有存储么? 有 ; 有检索么? 有(虽然是遍历grep而已);
那么来到了下一个问题, 索引
哈希索引
不言自明,但是不适应磁盘
SSTables和LSM树
为排序字符串表(Sorted String Table)也有一说是 set of sorted key-value table,简称SSTable
来自chatgpt:
SSTable(Sorted String Table) 是一种用于存储有序数据文件的持久化数据结构,广泛用于各种现代数据库和存储系统中,如 Apache Cassandra 和 LevelDB。SSTable 的设计旨在高效地支持大规模读写操作,尤其适用于需要快速查找和合并操作的场景。
SSTable 结构
SSTable 的基本特点和结构包括:
1.有序数据:
SSTable 存储的键值对按键进行排序。这使得查找、范围扫描和合并操作非常高效。
2.不可变性:
一旦写入磁盘,SSTable 就不会被修改。任何数据更新都会生成新的 SSTable,而不是修改已有的。这种不可变性简化了并发控制和一致性管理。
3.数据文件:
SSTable 主要由数据文件、索引文件和布隆过滤器文件组成。
* 数据文件:存储实际的键值对。
* 索引文件:存储键及其在数据文件中的偏移位置,支持快速查找。
* 布隆过滤器:用来快速判断一个键是否不存在于 SSTable 中,从而减少不必要的磁盘读操作。
4. 合并操作:
当需要对 SSTable 中的数据进行更新或删除时,系统会生成新的 SSTable 文件。定期的合并(compaction)操作会将多个 SSTable 合并成一个,以减少磁盘占用和提高查询效率。
基于 SSTable 的数据引擎
基于 SSTable 的数据引擎如 Apache Cassandra 和 LevelDB,利用 SSTable 的特点来提供高效的数据存储和检索功能。
1. Apache Cassandra
Cassandra 是一个分布式 NoSQL 数据库系统,使用 SSTable 作为底层存储结构。其工作原理包括:
写操作:
数据首先写入内存中的 Memtable,当 Memtable 达到一定大小时,数据会被刷新到磁盘,形成新的 SSTable。
SSTable 文件是不可变的,因此写操作不会引起磁盘文件的修改,而是生成新的 SSTable 文件。
读操作:
读操作会首先查找 Memtable,然后查找最近的 SSTable 文件。
使用布隆过滤器来快速判断一个键是否在某个 SSTable 中,以减少不必要的磁盘读取。
通过索引文件定位键在数据文件中的位置,以快速读取数据。
合并操作:
定期合并(Compaction)操作将多个 SSTable 合并成一个,删除过期数据,减少存储空间,并优化查询性能。
2. LevelDB
LevelDB 是一个轻量级的持久化键值对存储库,使用类似于 SSTable 的结构。其特点和工作原理包括:
写操作:
写操作首先写入内存中的 Memtable,当 Memtable 满了之后,写入磁盘形成新的 SSTable 文件。
使用 Write-Ahead Log (WAL) 记录写操作,以便在崩溃时恢复数据。
读操作:
读操作首先查找 Memtable,然后查找 SSTable 文件。
使用布隆过滤器和索引文件来提高查询效率。
合并操作:
LevelDB 会自动执行合并(Compaction)操作,将较小的 SSTable 文件合并成更大的文件,以优化读写性能。
总结
SSTable 是一种高效的数据存储结构,通过保持有序性和不可变性,极大地优化了查找、插入和删除操作的性能。基于 SSTable 的数据引擎,如 Apache Cassandra 和 LevelDB,通过内存表、WAL 和定期合并操作,实现了高效的写入和查询性能,广泛应用于需要处理大规模数据的场景中。
其实还有
### HBase 简介
Apache HBase 是一个基于列存储的分布式数据库,运行于 Hadoop HDFS 之上。它设计用于处理大规模数据集,特别是在需要高吞吐量和低延迟访问的环境中。HBase 的主要特点包括:
1. **列存储**:HBase 按列族(Column Family)存储数据,适合宽表设计,可以存储大量稀疏数据。
2. **分布式架构**:HBase 运行在 Hadoop 集群上,能够水平扩展,以处理大量数据和高并发请求。
3. **高可用性和容错性**:通过 HDFS 提供的数据复制和冗余机制,HBase 提供高可用性和数据持久性。
4. **实时读写**:HBase 支持高吞吐量的随机读写操作,适用于需要快速访问和更新数据的场景。
### SSTable 和 HBase 的关系
SSTable(Sorted String Table)是 HBase 存储数据的核心数据结构之一。HBase 的存储模型借鉴了 Google 的 Bigtable 设计,采用了 LSM(Log-Structured Merge)树结构,SSTable 是其中的重要组成部分。
### HBase 的数据存储流程
HBase 的数据存储和管理主要包括以下几个组件和步骤:
1. **HRegion**:HBase 的基本存储单元,每个表由多个 HRegion 组成,每个 HRegion 存储一个键范围内的数据。
2. **MemStore**:在内存中存储的有序数据结构,用于缓存写入的数据。
3. **HFile**:HBase 将数据持久化到磁盘上,以 HFile 格式存储,HFile 实际上就是 SSTable 文件。
### HBase 写入流程
1. **写请求**:客户端发送写请求到 HBase RegionServer。
2. **写前日志(WAL)**:数据首先写入 WAL,以保证持久性和恢复能力。
3. **MemStore**:数据同时写入 MemStore,这是一个内存中的有序数据结构。
4. **刷新到磁盘**:当 MemStore 达到一定大小时,数据被刷新到磁盘,形成 HFile(SSTable)。
### HBase 读取流程
1. **查找 MemStore**:首先在 MemStore 中查找数据。
2. **查找 BlockCache**:如果 MemStore 中没有找到数据,接着在 BlockCache(内存中的缓存)中查找。
3. **查找 HFile**:如果 BlockCache 中也没有找到数据,最后在 HFile 中查找。
### 合并和压实(Compaction)
随着时间的推移,HBase 会生成多个 HFile 文件。为了优化查询性能和节省存储空间,HBase 定期执行合并和压实操作,将多个 HFile 合并成一个新的文件,同时删除过期和重复的数据。这类似于 LSM 树中的 compaction 操作。
### 总结
SSTable 是 HBase 中数据存储的核心组件,通过将数据有序存储在 SSTable 文件中,HBase 实现了高效的读写性能和可扩展性。HBase 采用 LSM 树结构,通过 MemStore 缓存写入数据,并将其批量刷新到磁盘上的 SSTable(HFile),从而优化磁盘 I/O 操作。同时,HBase 通过合并和压实操作,保持数据的有序性和高效访问,适用于大规模数据处理和实时访问的应用场景。
B树 B+树
先不作赘述
LSM
尽管B树实现通常比LSM树实现更成熟,但LSM树由于其性能特点也非常有趣。根据经验,通 常LSM树的写入速度更快,而B树的读取速度更快【23】。 LSM树上的读取通常比较慢,因 为它们必须在压缩的不同阶段检查几个不同的数据结构和SSTables。
LSM(Log-Structured Merge)树 是一种用于写密集型工作负载的存储数据结构,旨在优化磁盘写入性能。LSM 树通过将写入操作集中到内存中,并批量写入磁盘,从而减少磁盘I/O操作。LSM 树广泛应用于现代数据库和存储系统,如 Apache Cassandra、LevelDB 和 RocksDB。
LSM 结构
LSM 树的基本思想是将写入操作首先记录在内存中,然后批量地写入到磁盘上。其核心概念和工作原理如下:
1.内存表(Memtable):
所有写入操作(插入、更新、删除)首先写入内存表(Memtable)。
内存表通常是一个有序的数据结构,如平衡树或跳表,以便快速写入和读取。
2.写前日志(WAL):
写操作在写入内存表之前,会被记录在写前日志(Write-Ahead Log, WAL)中,以确保数据的持久性。即使系统崩溃,也可以通过 WAL 恢复未持久化的数据。
3. SSTable(Sorted String Table):
当内存表达到一定大小时,会被刷新到磁盘,形成不可变的 SSTable 文件。
SSTable 文件按键有序,并且不会修改,只会新生成。
4. 合并和压实(Compaction):
随着时间的推移,会有越来越多的 SSTable 文件。为了提高查询效率并节省存储空间,需要定期进行合并和压实操作,将多个 SSTable 文件合并成一个新的文件,同时删除过期的数据。
5. 读操作:
读操作首先查找内存表,然后查找最近的 SSTable 文件。
使用布隆过滤器和索引来提高查询效率,减少不必要的磁盘读取。
如果单说sstable 这篇levelDb的文档写的不错: leveldb-handbook.readthedocs.io/zh/latest/s…
优缺点:
-
优点
- 高写入性能:
- 顺序写入:LSM 树通过将数据写入内存(Memtable),然后批量顺序写入磁盘(SSTable),极大地提高了写入性能。
- 减少写放大:批量写入减少了随机写入磁盘的次数,降低了写放大效应。
- 良好的写入吞吐量:
- 内存缓存:写操作首先写入内存中的 Memtable,这种方式能快速响应写请求,提高写入吞吐量。
- 后台合并:后台的合并和压实操作不会阻塞前台的写操作,进一步提高了写入吞吐量。
- 有效的磁盘利用:
- 压缩合并:定期的合并和压实操作能删除过期和重复的数据,优化磁盘空间使用。
- 压缩:在合并过程中,数据可以进行压缩,进一步提高存储效率。
- 适应写密集型工作负载:
- 大数据处理:适用于需要处理大量写入操作的场景,如日志记录、时间序列数据存储、实时数据分析等。
- 高可扩展性:
- 水平扩展:LSM 树可以很容易地在分布式系统中实现,支持集群扩展,满足大规模数据处理需求。
缺点
- 较慢的读取性能:
- 多级存储:读取操作需要检查多个层级的 SSTable 文件,增加了读取延迟。
- 随机读取性能:尽管 SSD 的随机读取性能较好,但 LSM 树在 HDD 上的随机读取性能较差。
- 读放大:
- 多次查找:读取一个键时,可能需要在多个 SSTable 中进行查找,导致读放大效应。
- 合并和压实开销:
- 资源消耗:合并和压实操作需要占用大量的 I/O 和 CPU 资源,可能会影响系统性能。
- 延迟问题:频繁的合并操作可能导致系统延迟增加,影响读取性能。
- 复杂性:
- 实现复杂:LSM 树的实现和维护相对复杂,需要处理内存缓存、写前日志、合并和压实等多种机制。
- 调优复杂:需要根据具体应用场景进行调优,如调整 Memtable 大小、压实策略等,以达到最佳性能。
- 内存消耗:
- 内存要求高:为了保证高效的写入性能,LSM 树需要较大的内存来存储 Memtable 和布隆过滤器,这对于内存资源有限的系统可能是一个挑战。
- 高写入性能:
LSM 的查询过程
LSM(Log-Structured Merge)树的查询过程相对复杂,主要是因为数据分布在多个不同层次的 SSTable 文件中。理解 LSM 树的查询过程可以帮助我们更好地优化其性能。以下是 LSM 树的典型查询过程:
### LSM 树查询步骤
1. **查询 Memtable**:
- 首先,在内存中的 Memtable 进行查找。Memtable 通常是一个有序的数据结构,如跳表或红黑树,可以快速定位到目标数据。如果在 Memtable 找到了目标数据,直接返回结果。
2. **查询 Immutable Memtable**:
- 如果在 Memtable 中没有找到数据,则继续查找最近刷新到磁盘但尚未合并的 Immutable Memtable。Immutable Memtable 也是存储在内存中的,但其内容已经准备好写入磁盘。如果在 Immutable Memtable 中找到了目标数据,返回结果。
3. **查询 Level 0 的 SSTable**:
- 接下来,在磁盘上的 Level 0 SSTable 文件中查找。Level 0 中的 SSTable 文件是按照写入时间排序的,没有合并操作,因此可能有多个文件需要查找。通常使用布隆过滤器来快速判断某个 SSTable 是否包含目标数据,以减少不必要的磁盘 I/O。如果在 Level 0 的某个 SSTable 找到数据,返回结果。
4. **查询更高层次的 SSTable**:
- 如果在 Level 0 中没有找到目标数据,继续在更高层次(Level 1 及以上)的 SSTable 文件中查找。每个更高层次的 SSTable 文件都是按键排序的,并且已经经过合并和压实操作。因此,这些层次的数据查询效率较高。每个层次的 SSTable 文件数量会逐渐减少,但文件规模会增大。
5. **返回结果**:
- 如果在所有层次的 SSTable 文件中都没有找到目标数据,则返回未找到的结果。
- 如果在某个层次找到了目标数据,则返回结果。
### 优化 LSM 树查询性能的方法
为了优化 LSM 树的查询性能,可以采用以下几种方法:
1. **使用布隆过滤器**:
- 布隆过滤器用于快速判断某个 SSTable 是否包含目标键。布隆过滤器存储在内存中,可以显著减少不必要的磁盘 I/O 操作。
2. **索引结构**:
- 在每个 SSTable 文件中维护稀疏索引结构,允许快速定位键在文件中的位置。索引结构可以显著提高查询效率。
3. **缓存机制**:
- 使用缓存机制(如 BlockCache)来存储最近访问过的数据块,从而减少磁盘访问次数。缓存机制可以显著提高查询性能。
4. **优化合并策略**:
- 合并和压实操作可以减少 SSTable 文件的数量,从而减少查询过程中需要查找的文件数量。合理配置合并策略可以平衡写入性能和查询性能。
5. **多线程查询**:
- 利用多线程并行查询多个 SSTable 文件,可以提高查询效率。特别是在具有高并发读请求的场景中,多线程查询可以显著提升系统性能。
### 查询示例代码
以下是一个简化的查询过程示例代码,用于展示查询步骤:
```
java
复制代码
public class LSMTree {
private Memtable memtable;
private Memtable immutableMemtable;
private List<SSTable> level0SSTables;
private List<List<SSTable>> higherLevelSSTables;
public Value query(Key key) {
// Step 1: Query Memtable
Value value = memtable.get(key);
if (value != null) {
return value;
}
// Step 2: Query Immutable Memtable
value = immutableMemtable.get(key);
if (value != null) {
return value;
}
// Step 3: Query Level 0 SSTables
for (SSTable sstable : level0SSTables) {
if (sstable.mightContain(key)) { // Using Bloom filter
value = sstable.get(key);
if (value != null) {
return value;
}
}
}
// Step 4: Query Higher Level SSTables
for (List<SSTable> levelSSTables : higherLevelSSTables) {
for (SSTable sstable : levelSSTables) {
if (sstable.mightContain(key)) { // Using Bloom filter
value = sstable.get(key);
if (value != null) {
return value;
}
}
}
}
// Step 5: Return not found
return null;
}
}
```
### 总结
LSM 树通过多级存储和批量写入优化了写性能,但查询过程涉及多个层次的 SSTable 文件查找,因此查询性能相对较慢。通过使用布隆过滤器、索引结构、缓存机制、优化合并策略和多线程查询,可以显著提升 LSM 树的查询性能。理解和优化 LSM 树的查询过程,有助于在实际应用中更好地平衡读写性能。
其他索引结构
- 多列索引
- 聚簇索引
- 覆盖索引
以上只是针对磁盘的数据存储。
扩展: 内存数据库
redis 、memcache
OLTP和OLAP
在线事务处理和在线分析处理
对比
| 属性 | 事务处理 OLTP | 分析系统 OLAP |
|---|---|---|
| 主要读取模式 | 查询少量记录,按键读取 | 在大批量记录上聚合 |
| 主要写入模式 | 随机访问,写入要求低延时 | 批量导入(ETL),事件流 |
| 主要用户 | 终端用户,通过Web应用 | 内部数据分析师,决策支持 |
| 处理的数据 | 数据的最新状态(当前时间点) | 随时间推移的历史事件 |
| 数据集尺寸 | GB ~ TB | TB ~ PB |
OLTP 侧重于可用性, 低延迟
ETL(extract-transfrom-load ) 和 OLTP OLAP(其实也是我们一般意义上的离线系统(但是也有实时流式部分))
分析模式,星型雪花型
星型: 主表+ 一堆各个属性的维度表
列存储
e.g
SELECT
dim_date.weekday,
dim_product.category,
SUM(fact_sales.quantity) AS quantity_sold
FROM fact_sales
JOIN dim_date ON fact_sales.date_key = dim_date.date_key
JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
WHERE
dim_date.year = 2013 AND
dim_product.category IN ('Fresh fruit', 'Candy')
GROUP BY
dim_date.weekday, dim_product.category;
如果是行式存储 那么困境: JOIN需要扫描的行数将非常堪忧,load+ filter
如果是列式存储 : 不要将所有来自一行的值存储在一起,而是将来自每一列 的所有值存储在一起。如果每个列存储在一个单独的文件中,查询只需要读取和解析查询中 使用的那些列,
列式存储示例:
列压缩
面对稀疏数据,
稀疏索引, 只取min和max (有点像是b+树)
总结
-
OLTP系统通常面向用户,这意味着他们可能会看到大量的请求。为了处理负载,应用程 序通常只触及每个查询中的少量记录。应用程序使用某种键来请求记录,存储引擎使用 索引来查找所请求的键的数据。磁盘寻道时间往往是这里的瓶颈。
-
数据仓库和类似的分析系统不太知名,因为它们主要由业务分析人员使用,而不是由最 终用户使用。它们处理比OLTP系统少得多的查询量,但是每个查询通常要求很高,需要 在短时间内扫描数百万条记录。磁盘带宽(不是查找时间)往往是瓶颈,列式存储是这 种工作负载越来越流行的解决方案。