更好地存储旧数据以便更快地访问

时间:2016-10-22 02:40:55

标签: mysql database optimization database-design

我们正在开发的应用程序每天写入大约4-5百万行数据。并且,我们需要在过去90天内保存这些数据。

user_data具有以下结构(简化):

id INT PRIMARY AUTOINCREMENT
dt TIMESTAMP CURRENT_TIMESTAMP
user_id varchar(20)
data varchar(20)

关于申请:

  • 不会写入/更新超过7天的数据。
  • 数据主要基于user_id进行访问(即所有查询都有WHERE user_id = XXX
  • 目前大约有13000名用户。
  • 用户仍然可以访问旧数据。但是,在访问旧数据时,我们可以限制他/她只能获得全天数据而不是时间范围。 (例如,如果用户试图获取2016-10-01的数据,他/她将获得一整天的数据,并且无法获取2016-10-01 13:00 - 2016-10的数据-01 14:00)。

目前,我们正在使用MySQL InnoDB存储最新数据(即7天及更新版本),并且工作正常并适合innodb_buffer_pool

对于较旧的数据,我们以user_data_YYYYMMDD的形式创建了较小的表格。过了一会儿,我们发现这些表格不适合innodb_buffer_pool而且开始变慢。

我们认为基于日期的分离/分片,基于user_id的分片会更好(即使用基于用户和日期的较小数据集,例如user_data_[YYYYMMDD]_[USER_ID])。这将使表格保持在更小的数字(最多只有大约10K行)。

经过研究后,我们发现有一些选择:

  • 使用mysql表存储每个用户的每个日期(即user_data_[YYYYMMDD]_[USER_ID])。
  • 为每个user_data_[YYYYMMDD]_[USER_ID]
  • 使用mongodb集合
  • 将旧数据(json编码)写入[USER_ID]/[YYYYMMDD].txt

我认为最大的问题是,当我们这样做时,我们将拥有大量的表/集合/文件(即13000 x 90 = 1.170.000)。我想知道我们是否在未来的可扩展性方面正确地接近这一点。或者,如果有其他标准化解决方案。

4 个答案:

答案 0 :(得分:1)

扩展数据库是应用程序的一个独特问题。大多数时候,其他人的方法无法使用,因为几乎所有应用程序都以自己的方式编写数据。因此,您必须弄清楚如何管理数据。

话虽如此,如果您的数据继续增长,最佳解决方案是您可以在不同服务器之间分发数据的阴影。只要绑定到单个服务器(如创建不同的表),您就会受到内存,存储和处理能力等资源限制的影响。那些不能无限制地增加。

如何分发您必须根据业务用例计算的数据。正如您所提到的,如果您没有获得有关旧数据的更多请求,那么这是在日期分发数据库的最佳方式。像2016年数据的DB,2015年的DB等等。稍后您可以清除或关闭具有更多旧数据的服务器。

答案 1 :(得分:0)

100万+桌子听起来不错。通过应用程序代码在运行时通过动态表命名进行分片对我来说也不是一个有利的模式。我对这类问题的首要考虑是分区。您可能不希望在一个未分区的表中有400M +行。在MySQL 5.7中,您甚至可以进行子分区(但这会变得更复杂)。我首先在你的日期字段上进行范围分区,每天一个分区。 user_id上的索引。如果你在5.7并且想要涉及子分区,我建议按日期进行范围分区,然后按user_id进行哈希子分区。作为起点,尝试16到32个散列桶。仍然索引user_id字段。

编辑:这是可以玩的东西:

CREATE TABLE user_data (
    id INT AUTO_INCREMENT
  , dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  , user_id VARCHAR(20)
  , data varchar(20)
  , PRIMARY KEY (id, user_id, dt)
  , KEY (user_id, dt)
) PARTITION BY RANGE (UNIX_TIMESTAMP(dt))
  SUBPARTITION BY KEY (user_id)
  SUBPARTITIONS 16 (
    PARTITION p1 VALUES LESS THAN (UNIX_TIMESTAMP('2016-10-25')),
    PARTITION p2 VALUES LESS THAN (UNIX_TIMESTAMP('2016-10-26')),
    PARTITION p3 VALUES LESS THAN (UNIX_TIMESTAMP('2016-10-27')),
    PARTITION p4 VALUES LESS THAN (UNIX_TIMESTAMP('2016-10-28')),
    PARTITION pMax VALUES LESS THAN MAXVALUE
);

-- View the metadata if you're interested
SELECT * FROM information_schema.partitions WHERE table_name='user_data';

答案 2 :(得分:0)

这是一张大桌子,但并非难以管理。

如果user_id + dt是UNIQUE,则将其设为PRIMARY KEY,并取消id,从而节省空间。 (更多内容......)

user_id规范化为SMALLINT UNSIGNED(2个字节),或者更安全MEDIUMINT UNSIGNED(3个字节)。这将节省大量空间。

节省空间对于大表的速度(I / O)很重要。

PARTITION BY RANGE(TO_DAYS(dt))

有92个分区 - 你需要的90个,加上1个等待DROPped,一个被填满。详情请见here

ENGINE=InnoDB

获取PRIMARY KEY群集。

PRIMARY KEY(user_id, dt)

如果这是“唯一”,那么它允许单个用户的任何时间范围的有效访问。注意:您可以删除“一天”限制。但是,必须制定查询,而不会在函数中隐藏dt。我建议:

WHERE user_id = ?
  AND dt >= ?
  AND dt  < ? + INTERVAL 1 DAY

此外,

PRIMARY KEY(user_id, dt, id),
INDEX(id)

即使(user_id,dt)不是唯一的,也会有效。在PK中增加id是为了使其独一无二;添加INDEX(id)是为了让AUTO_INCREMENT满意。 (不,UNIQUE(id)不是必需的。)

INT --> BIGINT UNSIGNED ??

INTSIGNED)将达到约20亿。这将在短短几年内发生。这可以吗?如果没有,您可能需要BIGINT(8字节对4)。

此分区设计并不关心您的7天规则。您可以选择保留规则并在您的应用中强制执行。

BY HASH

也不会。

SUBPARTITION

通常没用。

还有其他疑问吗?如果是这样,他们必须同时考虑

如果单个服务器的流量过多,则按user_id进行分片会很有用。 MySQL本身并没有(还)有一个分片解决方案。

答案 3 :(得分:0)

https://www.percona.com/software/mysql-database/percona-tokudb

尝试TokuDB引擎

归档数据非常适合TokuDB。与InnoDB相比,您需要大约六倍的磁盘空间来存储和处理数据集,或者比归档的myisam少2-3倍。