23

您将如何解决以下存储和检索问题?

每天(365 天/年)将添加大约 2.000.000 行,每行包含以下信息:

  • id(唯一的行标识符)
  • entity_id(取值介于 1 和 2.000.000 之间)
  • date_id(每天递增一个 - 取值在 1 到 3.650 之间(十年:1*365*10))
  • value_1(取值介于 1 和 1.000.000 之间)
  • value_2(取值介于 1 和 1.000.000 之间)

entity_id 与 date_id 结合是唯一的。因此,每个实体和日期最多可以添加一行到表中。数据库必须能够保存 10 年的每日数据(7.300.000.000 行 (3.650*2.000.000))。

上面描述的是写入模式。读取模式很简单:所有查询都将针对特定的 entity_id 进行。即检索所有描述 entity_id = 12345 的行。

不需要事务支持,但存储解决方案必须是开源的。理想情况下,我想使用 MySQL,但我愿意接受建议。

现在 - 你将如何解决所描述的问题?

更新:我被要求详细说明读写模式。每天将在一批中完成对表的写入,其中将一次性添加新的 2M 条目。读取将连续进行,每秒读取一次。

4

7 回答 7

28

“现在——你将如何解决所描述的问题?”

使用简单的平面文件。

这就是为什么

“所有查询都将针对特定的 entity_id 进行。即检索所有描述 entity_id = 12345 的行。”

您有 2.000.000 个实体。基于实体编号的分区:

level1= entity/10000
level2= (entity/100)%100
level3= entity%100

每个数据文件是level1/level2/level3/batch_of_data

然后,您可以读取目录给定部分中的所有文件以返回样本进行处理。

如果有人想要一个关系数据库,那么将给定 entity_id 的文件加载到数据库中供他们使用。


编辑 日期数。

  1. date_id/entity_id唯一性规则不是必须处理的。它(a)微不足道地强加在文件名上,并且(b)与查询无关。

  2. date_id翻转”没有任何意义——没有查询,所以没有必要重命名任何东西。应该从date_id纪元日期开始不受约束地增长。如果要清除旧数据,请删除旧文件。

由于没有查询依赖于date_id,因此无需对其进行任何操作。它可以是所有重要的文件名。

要将 包含date_id在结果集中,请将其与文件每一行中的其他四个属性一起写入文件中。


编辑打开/关闭

对于写作,您必须让文件保持打开状态。您会定期刷新(或关闭/重新打开)以确保这些东西真的会进入磁盘。

对于您的作家的架构,您有两种选择。

  1. 有一个单一的“编写者”进程来整合来自不同来源的数据。如果查询相对频繁,这将很有帮助。您需要为在写入时合并数据付费。

  2. 同时打开多个文件进行写入。查询时,将这些文件合并为一个结果。这很有帮助,因为查询相对较少。您需要为在查询时合并数据付费。

于 2009-03-20T10:44:49.787 回答
13

使用分区。使用您的读取模式,您希望按entity_id哈希进行分区。

于 2009-03-20T10:36:56.187 回答
5

你可能想看看这些问题:

大主键:1+ 十亿行 MySQL + InnoDB?

大型 MySQL 表

就个人而言,我还考虑计算你的行宽,让你了解你的表有多大(根据第一个链接中的分区说明)。

HTH.,

小号

于 2009-03-20T10:36:42.537 回答
4

您的应用程序似乎与我的应用程序具有相同的特征。我写了一个 MySQL 自定义存储引擎来有效地解决这个问题。描述here

想象一下,您的数据以 2M 固定长度条目(每个实体一个)的数组形式排列在磁盘上,每个条目包含 3650 行(每天一个)20 字节(每天一个实体的行)。

您的阅读模式读取一个实体。它在磁盘上是连续的,因此需要 1 次搜索(大约 8 毫升秒)并以 100MB/秒的速度读取 3650x20 = 大约 80K ......所以它在几分之一秒内完成,轻松满足您每秒 1 次查询的读取图案。

更新必须在磁盘上 2M 个不同的位置写入 20 个字节。在最简单的情况下,这需要 2M 次查找,每次查找大约需要 8 毫秒,因此需要 2M*8ms = 4.5 小时。如果将数据分布在 4 个“raid0”磁盘上,则可能需要 1.125 小时。

然而,这些地方相距仅 80K。这意味着在 16MB 块(典型的磁盘缓存大小)内有 200 个这样的位置,因此它可以以高达 200 倍的速度运行。(1 分钟)现实介于两者之间。

我的存储引擎就是按照这种理念运行的,尽管它比固定长度的数组更通用一点。

您可以完全按照我的描述编写代码。将代码放入 MySQL 可插拔存储引擎意味着您可以使用 MySQL 通过各种报告生成器等查询数据。

顺便说一句,您可以从存储的行中删除日期和实体 id(因为它们是数组索引)并且可能是唯一的 id - 如果您真的不需要它,因为 (entity id, date) 是唯一的,并且将 2 个值存储为 3 字节 int。那么您存储的行是 6 个字节,每 16M 有 700 次更新,因此插入速度更快,文件更小。

编辑与平面文件比较

我注意到评论普遍偏爱平面文件。不要忘记目录只是文件系统实现的索引,它们通常针对相对较少的相对较大的项目进行优化。对文件的访问通常经过优化,因此它期望打开的文件数量相对较少,并且打开和关闭以及每个打开的文件的开销都相对较高。所有这些“相对”都与数据库的典型使用有关。

使用文件系统名称作为我认为是 1 到 2Million 的非稀疏整数的实体 ID 的索引是违反直觉的。例如,在编程中,您将使用数组,而不是哈希表,并且您不可避免地会为昂贵的访问路径带来大量开销,而访问路径可能只是数组 indeing 操作。

因此,如果您使用平面文件,为什么不只使用一个平面文件并对其进行索引呢?

性能编辑

该应用程序的性能将取决于磁盘寻道时间。我在上面所做的计算确定了你能做的最好的事情(尽管你可以通过减慢 SELECT 来加快 INSERT - 你不能让它们都变得更好)。无论您使用数据库、平面文件还是一个平面文件都没有关系,只是您可以添加更多您并不真正需要的搜索并进一步减慢速度。例如,与“查找数组”相比,索引(无论是文件系统索引还是数据库索引)会导致额外的 I/O,这些会减慢您的速度。

编辑基准测量

我有一张看起来很像你的桌子(或者几乎完全像你的一个分区)。它是 64K 实体而不是 2M(你的 1/32)和 2788 个“天”。该表的创建顺序与您的插入顺序相同,并且具有相同的索引 (entity_id,day)。一个实体上的 SELECT 需要 20.3 秒来检查 2788 天,这大约是预期的每秒 130 次寻道(在 8 毫秒平均寻道时间的磁盘上)。SELECT 时间将与天数成正比,而不太依赖于实体的数量。(它在具有更快寻道时间的磁盘上会更快。我在 RAID0 中使用了一对 SATA2,但这并没有太大区别)。

如果您将表重新排序为实体顺序 ALTER TABLE x ORDER BY (ENTITY,DAY) 则相同的 SELECT 需要 198 毫秒(因为它是在单个磁盘访问中读取订单实体)。但是,ALTER TABLE 操作需要 13.98 天才能完成(对于 182M 行)。

测量结果告诉您其他一些事情 1. 您的索引文件将与您的数据文件一样大。此示例表为 3GB。这意味着(在我的系统上)所有索引都以磁盘速度而不是内存速度。

2.您的 INSERT 率将呈对数下降。插入数据文件是线性的,但插入索引的键是 log。在 180M 记录时,我每秒获得 153 次 INSERT,这也非常接近搜索速率。它表明 MySQL 正在为几乎每个 INSERT 更新一个叶索引块(正如您所期望的,因为它是在实体上编制索引但按天顺序插入的。)。因此,您正在查看 2M/153 秒 = 3.6 小时来完成 2M 行的每日插入。(除以您可以通过跨系统或磁盘分区获得的任何效果)。

于 2009-03-21T03:51:33.143 回答
2

我有类似的问题(尽管规模更大 - 关于您每天的年度使用量)

使用一张大桌子让我停下来——你可以拉几个月,但我想你最终会把它分区。

不要忘记为表建立索引,否则每次查询都会弄乱微小的数据;哦,如果您想进行大量查询,请使用平面文件

于 2009-03-20T10:46:29.143 回答
1

您对阅读模式的描述不充分。您需要描述将检索的数据量、查询的频率和偏差程度。

这将允许您考虑对某些列进行压缩。

还要考虑归档和分区。

于 2009-03-20T10:43:17.497 回答
0

如果要处理数百万行的海量数据,可以认为它类似于时间序列数据库,它记录时间并将数据保存到数据库中。一些存储数据的方法是使用 InfluxDB 和 MongoDB。

于 2014-10-24T04:07:42.883 回答