11

我们需要每秒存储来自多个设备的 500 个测量值。每个度量都包含一个时间戳、一个数量类型和几个向量值。现在每个测量有 8 个向量值,我们可以认为这个数字是恒定的,以满足我们原型项目的需要。我们正在使用 HNibernate。测试在 SQLite 中完成(磁盘文件 db,而不是在内存中),但生产可能是 MsSQL。

我们的 Measurement 实体类是一个包含单个测量值的实体类,如下所示:

public class Measurement
{
    public virtual Guid Id { get; private set; }
    public virtual Device Device { get; private set; }
    public virtual Timestamp Timestamp { get; private set; }
    public virtual IList<VectorValue> Vectors { get; private set; }
}

矢量值存储在一个单独的表中,因此它们中的每一个都通过外键引用其父测量值。

我们做了几件事来确保生成的 SQL(合理)高效:我们使用 Guid.Comb 生成 ID,我们在单个事务中刷新大约 500 个项目,ADO.Net 批量大小设置为 100(我认为 SQLIte 不支持批量更新?但以后可能会有用)。

问题

现在我们可以每秒插入 150-200 个测量值(这还不够快,尽管这是我们正在谈论的 SQLite)。查看生成的 SQL,我们可以看到我们在单个事务中插入(如预期的那样):

  • 1 个时间戳
  • 1 次测量
  • 8 个向量值

这意味着我们实际上做了 10 倍的单表插入:每秒 1500-2000 次。

如果我们将所有内容(所有 8 个向量值和时间戳)放入测量表(添加 9 个专用列),似乎我们可以将插入速度提高 10 倍。

切换到 SQL Server 将提高性能,但我们想知道是否有办法避免与当前数据库组织方式相关的不必要的性能成本。

[编辑]

使用内存中的 SQLite,我可以获得大约 350 个项目/秒(3500 个单表插入),我相信这与 NHibernate 一样好(以这篇文章作为参考:http ://ayende.com/Blog/archive/ 2009/08/22/nhibernate-perf-tricks.aspx)。

但我不妨切换到 SQL Server 并停止假设,对吧?我会在测试后立即更新我的帖子。

[更新]

我已经转移到 SQL Server 并扁平化了我的层次结构,我通过存储 3000 次测量/秒数小时对其进行了测试,它似乎工作正常。

4

10 回答 10

10

就个人而言,我会说去吧:去规范化,然后创建一个 ETL 流程,将这些数据转换为更规范化的格式,以供分析/常规使用。

基本上,对您来说理想的情况可能是拥有一个单独的数据库(如果需要,甚至可以在同一数据库中使用单独的表),它将数据的获取视为与您需要处理的格式完全不同的事情它。

这并不意味着您需要丢弃围绕当前数据库结构创建的实体:只是您还应该创建那些非规范化表并制作 ETL 以将它们引入。您可以使用 SSIS(尽管它仍然是非常错误和烦躁)定期将数据带入您的规范化表集,甚至是 C# 应用程序或其他批量加载过程。

编辑:当然,这是假设您的分析不需要实时完成:只需收集数据。很多时候,人们不需要(有时实际上更不想拥有)分析数据的实时更新。这是在纸上听起来不错的事情之一,但在实践中它是不必要的。

如果分析此数据的某些人需要实时访问,如果需要,您可以针对“裸机”非规范化事务数据构建一个工具集:但是当您真正深入研究需求时,执行分析的人并不需要真正的实时(在某些情况下,他们更愿意使用一组更静态的数据!):在这种情况下,定期 ETL 会很好地工作。您只需与目标用户聚在一起,找出他们真正需要的东西。

于 2010-05-03T11:16:41.833 回答
4

好吧,这将取决于。8 个向量值是一个永远不会改变的硬性数字吗?然后在您的情况下进行非规范化可能是有意义的(但只有在您使用的真实硬件和数据库上进行测试才会知道)。如果下周可能是 9 次测量,请不要这样做。

我会说你需要先切换到 SQL 服务器和你将要运行的设备,然后再决定要做什么。

一旦你切换了运行探查器。nHibernate 完全有可能没有为您的插入创建性能最佳的 SQl。

您有一组可能在插入时被拆分的向量这一事实可能是您的性能问题的一部分。最好有 8 个单独的变量,而不是必须拆分的集合。

你说的是每天超过 4000 万条记录,这将需要一些主要的硬件和设计良好的数据库。关系数据库也可能不是最好的选择(我不知道你想如何使用这些数据量)。您将这些数据保留多长时间,这里的大小将很快失控。

是否可以改为每分钟一次批量插入组中的记录?批量插入比逐行插入快得多。

您的设计必须考虑您如何使用数据以及如何插入数据。通常,为加快插入速度所做的事情会减慢选择速度,反之亦然。您可能需要一个每天加载一次以进行分析的数据仓库(以及一个能够显示原始数据直至第二个数据的快速查询)。

于 2010-05-03T17:55:37.797 回答
3

首先,移动到目标数据库;基于 SqlLite 的性能可能不代表基于 MsSql 的性能

其次,衡量性能瓶颈在哪里;顺便说一句,我敢说它是磁盘,而内存数据库的性能会好得多。

然后在必要时使用上面建议的 ETL 过程进行非规范化。

事件流处理有一句话:“如果你撞到磁盘,你就死定了。 ” ;-)

于 2010-05-03T11:38:32.290 回答
2

您是否考虑过使用 SqlBulkCopy?它的工作速度非常快。我已经在生产环境中使用了它,并且使用 sql server 2005 机器在不到一秒的时间内在单个表上实现了 10.000+ 次插入。您只需要准备要在应用程序中批量插入的 DataTable(s)。这是一个示例。

        public static void SQLBulkCopyInsert(DataTable dtInsertRows, string destinationTableName, string[] columnMappings)
    {
        using (SqlBulkCopy sbc = new SqlBulkCopy(DBHelper.Secim2009DB.ConnectionString, SqlBulkCopyOptions.UseInternalTransaction))
        {                
            sbc.DestinationTableName = destinationTableName;
            // Number of records to be processed in one go
            sbc.BatchSize = 30000;
            // Map the Source Column from DataTabel to the Destination Columns in SQL Server 2005 Person Table

            foreach (string columnMapping in columnMappings)
            {
                sbc.ColumnMappings.Add(columnMapping, columnMapping);
            }

            // Number of records after which client has to be notified about its status
            sbc.NotifyAfter = dtInsertRows.Rows.Count;
            // Event that gets fired when NotifyAfter number of records are processed.
            sbc.SqlRowsCopied += new SqlRowsCopiedEventHandler(sbc_SqlRowsCopied);
            // Finally write to server
            sbc.WriteToServer(dtInsertRows);
            sbc.Close();
        }
    }

    public static void sbc_SqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
    {            

    }
于 2010-05-05T14:00:19.180 回答
1

不要只是非规范化。使用有用的设计模式为结果而设计。有时,一种有用的性能设计模式会提供与遵循规范化规则所获得的设计不同的设计。

我认为非规范化不会对您的情况有所帮助。几乎所有提倡非规范化的人都说,当你存储新数据时,性能提升不会到来。当您检索数据时,它们就会出现。您必须弄清楚这如何适用于您的案件。

我可以告诉你这么多。如果您最终通过多个并发进程进行存储,您的设计将导致严重的瓶颈,并且可能比规范化设计运行得更慢。

但不要相信我的话。实验。分析。学习。繁荣。

于 2010-05-03T11:18:06.380 回答
1

“我们需要每秒存储 500 个测量值,来自多个设备。”

不要使用 DBMS 来存储这种数据。

人们使用 DBMS 的原因是什么?

(a) 他们可以对您尝试注册的数据实施限制。但你没有。测量数据就是它们,它们需要被接受。没有约束。

(b) 在 (1) 违反约束和 (2) 严重的系统故障(例如磁盘 I/O 错误)的情况下,它们可以确保您宝贵的业务数据的一致性和完整性。但是由于您没有约束,因此(1)不适用。至于 (2),如果磁盘 I/O 错误导致无法记录,您会如何处理测量结果?无论如何,您的测量结果都会丢失。

所以imo,你没有任何理由使用DBMS。将您的测量负载转储到一个平面文件中,并根据需要进行处理。

于 2010-05-03T20:48:38.567 回答
1

您可能会考虑其他数据库替代方案。MSSQL 提供了很多功能,但这增加了一些开销。

http://highscalability.com/是高性能处理(如您正在尝试做的事情)的绝佳资源

他们的案例研究之一是将数千台设备统计信息存储在数据库中。解决方案是多个 MYSQL 数据库并根据设备 ID 路由请求。总体而言 - 该网站可以提供出色的案例研究。也许你可以在那里找到一个可能的解决方案。

蒂穆尔

于 2010-05-04T01:40:16.317 回答
1

使用正确的 DBMS 和硬件。在具有不同硬件的另一个平台上进行测试不会告诉您有关性能的任何信息。

非规范化不太可能有助于写入性能,因为根据定义,它意味着您正在创建冗余数据,因此您将为每次写入做更多的工作,而不是更少。

您引用的数据对于流数据场景来说并不例外,并且可以使用正确的硬件完全实现,但我认为 nHibernate 将成为您的主要限制因素。我认为 nHib 不太可能是这类事情的明智选择。

您是否考虑过使用一些为流数据源和 CEP 提供特殊支持的技术?例如:OSISoft PI、Microsoft StreamInsight 和 SQL Server 的文件流功能。

于 2010-05-04T10:20:45.913 回答
1

你必须问自己,“我们为什么要标准化?”

主要原因有以下三个:

  1. 数据一致性
  2. 更新速度
  3. 尺寸

数据一致性

下拉列表和所有表示相同事物的行具有相同的 FK,这很好,对吧?很明显。这对于具有多个数据“编辑器”的数据库来说非常重要。但这仅与我们的流程一样好。假设它是一个航班数据库,并且有一个华盛顿特区国家机场的条目......有些人为华盛顿特区的里根国家机场添加了一个新条目...... FK 将在那里,并在儿童表中使用但赢了不值多少钱……不过这样做还是好事……

更新速度

我们应该做的是用新名称更新国家机场的行。因为只有一个父行,所以更改非常简单。如果我的航班表有文本,我会更新数百万行。

尺寸

如果我确实在每张唱片上都存储了“里根国家机场”,那么它会比 FK 占用更多的空间,比如 19。大小曾经是一个非常重要的问题,但 SAN 使它变得无关紧要。


结论

好的,那么您是否担心您的 SOLO 数据收集应用程序无法保持乐器名称的正确性?数据一致性是否会成为挑战?

好的,那么您认为您会更改仪器或数据点的名称多少次?我的意思是溶解氧就是溶解氧,浊度就是浊度,对吧?但是,如果您确实需要进行大规模更新,我敢打赌,您将在两次运行之间有停机时间来执行此操作。所以这不是问题。

好的,那么尺寸,当然……这是很多测量值;但是,不要测量“溶解氧”,DO2 很好......这比“7”之类的 FK 大多少?花空间来节省时间。

不要规范化,因为您总是被告知优秀的数据库设计人员会做的事情。知道你为什么这样做,为什么你要选择你所选择的。

于 2010-05-04T14:36:35.820 回答
0

是的。我会考虑通过非规范化(数据展平)和按时间分块数据来减少插入的开销。我会设计我的数据库,以便每条记录在每个设备上存储一整秒的数据:

public class Measurement 
{ 
    public Guid ID { get; private set; } 
    public Device Device { get; private set; }
    public Sample[] { get; private set; }

    public DateTime FirstTimestamp { get; private set; } 
    public DateTime LastTimestamp { get; private set; } 
} 

public class Sample
{ 
    public DateTime Timestamp { get; private set; } 
    public VectorValue[] Vectors { get; private set; } 
}

有多种方法可以在单个记录中存储复杂类型(例如在这种情况下为列表的列表)。XML 列CLR 用户定义类型是两个示例。

于 2010-05-03T14:31:18.927 回答