行存储将每个数据点作为一行存储在数据库中,每行包含多个字段,例如时间戳、测量值等。行存储适合按照时间顺序查询整行数据。
列存储将数据按列存储在数据库中,每列表示一个指标的时间序列数据,包含多个时间戳和相应的值。列存储适合进行聚合、过滤和统计操作。
1)行存储的写入是一次完成。如果这种写入建立在操作系统的文件系统上,可以保证写入过程的成功或者失败,数据的完整性因此可以确定。
2)列存储由于需要把一行记录拆分成单列保存,写入次数明显比行存储多(意味着磁头调度次数多,而磁头调度是需要时间的,一般在1ms~10ms),再加上磁头需要在盘片上移动和定位花费的时间,实际时间消耗会更大。所以,行存储在写入上占有很大的优势。
3)还有数据修改,这实际也是一次写入过程。不同的是,数据修改是对磁盘上的记录做删除标记。行存储是在指定位置写入一次,列存储是将磁盘定位到多个列上分别写入,这个过程仍是行存储的列数倍。所以,数据修改也是以行存储占优。
1)数据读取时,行存储通常将一行数据完全读出,如果只需要其中几列数据的情况,就会存在冗余列,出于缩短处理时间的考量,消除冗余列的过程通常是在内存中进行的。
2)列存储每次读取的数据是集合的一段或者全部,不存在冗余性问题。
3) 两种存储的数据分布。由于列存储的每一列数据类型是同质的,不存在二义性问题。比如说某列数据类型为整型(int),那么它的数据集合一定是整型数据。这种情况使数据解析变得十分容易。相比之下,行存储则要复杂得多,因为在一行记录中保存了多种类型的数据,数据解析需要在多种数据类型之间频繁转换,这个操作很消耗CPU,增加了解析的时间。所以,列存储的解析过程更有利于分析大数据。
4)由于列存储中相同类型的数据在一列中连续存储,利于数据压缩算法的应用,因此列存储通常具有更高的压缩率。这可以节省存储空间,并且减少了磁盘读取的数据量,提高了查询性能。
① 数据是按行存储的。
② 没有索引的查询使用大量I/O。比如一般的数据库表都会建立索引,通过索引加快查询效率。
③ 建立索引和物化视图需要花费大量的时间和资源。
④ 面对查询需求,数据库必须被大量膨胀才能满足需求。
① 数据按列存储,即每一列单独存放。
② 数据即索引。
③ 只访问查询涉及的列,可以大量降低系统I/O。
④ 每一列由一个线程来处理,即查询的并发处理性能高。
⑤ 数据类型一致,数据特征相似,可以高效压缩。比如有增量压缩、前缀压缩算法都是基于列存储的类型定制的,所以可以大幅度提高压缩比,有利于存储和网络输出数据带宽的消耗。
列存储在更新频繁的场景下不太适用的原因如下:
- 写入开销:列存储将数据按列存储,每列都是单独存储的。当进行频繁的更新操作时,需要修改多个列的数据,这会导致大量的写入操作。由于每次更新都需要对多个列进行写入,写入开销较高,影响性能。
- 数据压缩:列存储通常使用列式压缩算法,例如基于字典的压缩、位图压缩等,以减小存储空间和提高查询效率。然而,在频繁更新的场景下,数据的压缩和解压缩操作会频繁发生,增加了系统的开销。
- 数据重建:由于列存储的特性,数据的更新可能需要进行数据重建或重排,以确保数据的有序性和压缩效果。频繁的数据更新会导致大量的数据重建操作,增加了系统的负担和复杂性。
- 事务支持限制:某些列存储系统在事务支持方面可能受限。由于频繁的更新操作可能涉及多个列,而列存储系统可能不支持跨列的原子性更新,这可能导致数据的一致性问题。
时序数据库中可以存储多种类型的数据,包括:
1. 数值型数据:例如温度、湿度、压力等传感器数据,股票价格等。
2. 布尔型数据:例如开关状态、报警状态等。
3. 字符串型数据:例如日志数据、设备状态等。
4. 地理位置数据:例如 GPS 数据、地震数据等。
5. 图像、音频、视频等多媒体数据:例如监控视频、音频信号等。
时序数据库可以根据不同的数据类型进行优化,例如对于数值型数据可以使用压缩算法来减少存储空间,对于地理位置数据可以使用地理索引和空间索引来加速查询等。
日志合并树是一个分层、有序、块存储的数据存储结构。LSM Tree的数据分由内存(Memory)+磁盘(Disk)存储,内存由一个MemTable(内存表)和一个或多个Immutable MemTable(不可变内存表)组成,磁盘由多个级别Level的SSTable组成。
在时序数据库中,WAL(Write-Ahead Log,预写日志)是一种常见的持久化机制。它用于确保数据的持久性和一致性,以应对系统故障或崩溃的情况。
WAL的基本原理是在将数据写入数据库之前,先将数据操作记录写入一个日志文件中。具体过程如下:
WAL的优势在于它提供了强大的数据恢复能力。通过使用WAL,时序数据库可以在故障发生时快速地从日志中重建数据,从而减少数据丢失的风险。
MemTable 通常是一个有序的数据结构,LevelDB 中主要使用跳表(Skiplist)或红黑树。这两种数据结构都能够在 O(log n) 的时间复杂度内实现插入、查找和删除操作,确保了写入和查询的高效性。当有新的数据需要写入 LevelDB 时,首先将数据写入 MemTable。由于 MemTable 存储在内存中,写入速度相对较快。这允许 LevelDB 实现高吞吐的写操作。
SSTable(Sorted String Table)是 LevelDB 中用于持久化存储有序键值对的一种数据文件格式。
SSTable由一组数据block和一组元数据block组成,元数据block存储了SSTable数据block的描述信息,如索引、BloomFilter(布隆过滤器)、压缩、统计等信息,索引采用二分数组结构。
为了加速查询操作,SSTable 可能会包含一个 Bloom Filter。Bloom Filter 是一个快速且占用空间较小的数据结构,用于判断一个键是否可能存在于 SSTable 中。这有助于减少不必要的磁盘 I/O 操作。
SSTable 通常会将数据划分为若干个块(block),每个块独立压缩和编码。这种块编码有助于减小磁盘 I/O 操作,因为对数据的读取通常是按块进行的。
一旦 SSTable 文件创建完成,它就是不可变的,即文件内容不会被修改。这种特性有助于简化并发操作和提高读取性能。任何对数据的修改都会生成一个新的 SSTable 文件。
数据写入Memtable前会以WAL(write-ahead-log)方式先写log到disk,以使Memtable中数据在机器故障后可根据log恢复。
写采用【先写内存+日志 攒一定量再 批量顺序append磁盘】故写性能非常高,而为提高查效率所做的改进:数据存储上按key排序组织使得可二分查找(单数据查询)或范围查询,且不用移动数据。
但客户端发来的数据并不会按key有序故若数据存储上按key排则持久化时可能需要移动数据从而影响写入性能,怎么办?但进一步做了【在内存对数据按key排序、数据批量append到文件、多层次文件组织、延迟更新】等处理。
所有写入Memtable或disk的数据均不会被修改:新的add、delete、update操作在内部实现上都是产生新的entry,而不会去修改同key的entry,因此存在冗余:同一个key有多个版本的数据、不同文件间有相同key的entry。
Level Based Compaction基于将数据按照不同的级别(Level)进行组织和管理。每个级别都包含一个或多个数据文件,其中数据文件的大小和数量在不同的级别之间逐渐增加。一般来说,级别越高,数据文件越大,而级别越低,数据文件越小。
周期性地,级别化合并会触发 Compaction 过程,即将不同级别之间的数据文件进行合并和压缩,以减少存储空间的占用和提高查询性能。在 Compaction 过程中,过时或重复的数据将被删除,数据文件将被重新组织,以提供更高效的数据访问
LSM树由多个层级组成,每个层级都包含一个或多个数据文件。在典型的LSM树实现中,包括以下几个主要层级:
- MemTable(内存表):MemTable是位于内存中的数据结构,用于缓存最新的写入操作。它是一种有序的键值对存储结构,支持高效的写入操作和快速的读取操作。
- 增量层(Level 0):增量层是LSM树中最底层的层级,通常由多个数据文件组成。这些数据文件用于存储刚刚从MemTable中刷写到磁盘的数据。增量层中的数据文件一般较小,并且按照时间戳顺序排序。
- 合并层(Level 1及以上):合并层是LSM树中的上层级,包含已经经过合并和压缩的数据文件。在合并过程中,从增量层的数据文件中选取一部分进行合并,生成一个新的更大的数据文件,并按照特定的规则进行排序和压缩。合并层中的数据文件越往上层级,文件的大小和数量越大。
- 查询层(Level N):查询层是LSM树的顶层级,其中包含最早写入的数据文件。这些文件通常具有较大的范围和较高的压缩比率,用于支持范围查询和扫描操作。
通过这种层级化的存储结构,LSM树实现了高写入性能和较高的读取性能。写入操作可以快速地写入到内存中的MemTable,而读取操作可以在不同层级的数据文件中进行快速的查找和查询。定期执行合并和压缩操作可以优化数据的存储和读取效率,并控制存储空间的使用。
read操作:先从Memtable中查,若未查到则对各disk文件按文件创建时间由新到旧对各文件遍历查。由于文件内部是按key有序的,故相比于纯日志型存储,LSM Tree查询更快(如可用二分查找);由于需要遍历各文件故查询速度相比于B+树差,且文件越多查的性能越慢;
布隆过滤器内部依赖哈希算法,当检测到莫一条数据是否见过时,有一定概率出现假阳性(False Positive),但一定不会出现假阴性(False Negative)。简而言之,布隆过滤器认为一条数据出现过,那么该条数据很可能出现过;但如果布隆过滤器认为一条数据没有出现过,那么该条数据一定没有出现过。这种特性刚好与此处的需求相契合,即检验某条数据是否缺失。
稀疏索引是指将有序数据切分成(固定大小的)块,仅对各个块开头的一条数据做索引。
全量索引对全部数据进行编制索引,其中的任意一条数据发生增删均需要更新索引。
全量索引查询效率更高,达到了理论极限O(log2n),但写入和删除效率低,每次数据增删均需要更新索引而消耗一次IO操作。通常关系型数据库,如MySQL等,其内部采用B tree作为索引结构,即全量索引。
为提高查询效率,措施:增加合并操作频率以减少文件数量、生成大的排序的文件;对每个文件使用布隆过滤器以可快速确定此文件是否有指定key的entry;Level Based Compaction使得可以记录每个level中key的范围(mainfest文件)从而查询时每个level只要查一个文件。
其他补充可参考我之前写过的一篇总结 Java物联网技术知识点总结#时序数据库(Time Series Database,TSDB)