如何对用于存储日志的 Azure 表进行分区
How to partition Azure tables used for storing logs
我们最近更新了我们的日志记录以使用 Azure table 存储,由于其低成本和按行和分区查询时的高性能非常适合此目的。
我们正在努力遵循文档 Designing a Scalable Partitioning Strategy for Azure Table Storage 中给出的指导方针。由于我们正在对此 table 进行大量插入(并希望随着我们的扩展,数量会增加),我们需要确保我们不会达到我们的限制导致日志丢失。我们的设计结构如下:
我们每个环境(开发、测试、生产)都有一个 Azure 存储帐户。
我们每个产品有一个 table。
我们正在为行键使用 TicksReversed+GUID,这样我们就可以
查询特定时间之间的结果块
表现。
我们本来选择用Logger对table进行分区,对我们来说
是产品的广泛领域,例如 API、应用程序、性能
和缓存。但是,由于分区数量较少,我们
担心这会导致所谓的 "hot" 分区,其中许多
在给定时间段内对一个分区执行插入。所以
我们更改为根据上下文分区(对我们来说,class 名称或 API
资源)。
然而,在实践中我们发现这不太理想,因为当我们一眼看日志时,我们希望它们按时间顺序出现。相反,我们最终得到的是按上下文分组的结果块,如果我们想按时间对它们进行排序,我们将不得不获取所有分区。
我们的一些想法是
对分区键使用时间块(比如 1 小时)按时间排序(导致热分区 1 小时)
使用一些分区键的随机 GUID 来尝试分发日志(我们失去了快速查询 Context 等功能的能力)。
由于这是 Azure table 存储的常见应用,因此必须有某种标准程序。 分区用于存储日志的 Azure table 的最佳做法是什么?
解约束
使用便宜的 Azure 存储(Table 存储似乎是显而易见的选择)
快速、可扩展的写入
丢失日志的可能性很小(即超过 Azure table 存储中每秒 2000 个实体的分区写入速率)。
阅读按日期排序,最近的在前。
如果可能的话,在对查询有用的东西(比如产品区域)上进行分区。
有一个非常通用的技巧可以在写入时避免热点,同时增加读取成本。
定义N个分区(比如10个左右)。写入一行时,将其填充到随机分区中。分区可以在内部按时间排序。
读取时需要从所有N个分区读取(可能是按时间过滤排序),合并查询结果。
这将写入可伸缩性提高了 N 倍,并使查询成本增加了相同数量的往返和查询。
此外,您可以考虑将日志存储在其他地方。对 Azure 产品的非常严格的人为限制会导致您在其他情况下不会产生人工成本。
选择 N 高于 达到每个帐户每秒 20,000 次操作限制所需的值,这样就不太可能出现随机热点。选择 N 是最低需要的两倍就足够了。
对你的问题并不是一个具体的答案,但这是我的一些想法:
您真正需要考虑的是如何查询数据并基于此设计数据 storage/partitioning 策略(牢记分区策略 guid)。例如,
- 如果您需要查看给定 date/time 范围内所有记录器的日志,那么您当前的方法可能不合适,因为您需要并行查询多个分区。
- 如果您想查询给定 date/time 范围内的特定记录器,您当前的方法会起作用。
- 向我建议的另一件事是适当使用 blob 存储和 table 存储。如果有一些不需要经常查询的数据,您可以简单地将这些数据推送到 blob 存储中(想想旧日志 - 如果您不打算将它们保存在 tables 中查询他们太频繁了)。每当您需要此类数据时,您只需从 blob 存储中提取它,将其推送到 table 存储中,然后 运行 针对该数据进行临时查询。
可能的解决方案
一种可能的解决方案是存储相同数据的多个副本并相应地使用这些副本。由于存储成本低,您可以保存相同数据的两个副本。在第一个副本中,您可以拥有 PK = Date/Time 和 RK = 无论您决定什么,在第二个副本中,您可以拥有 PK = Logger 和 RK = TicksReversed+GUID。然后当你想获取所有日志而不考虑记录器时,你可以简单地查询第一个副本(PK = Date/Time),如果你想查询特定记录器类型的日志,你可以简单地查询第二个副本(PK = 记录器,RK >= Date/Time 开始 & RK <= Date/Time 结束)。
您可能还会发现此 link 有帮助:http://azure.microsoft.com/en-us/documentation/articles/storage-table-design-guide/
我遇到过类似的情况,根据我的经验我可以说:
每当对 Azure 存储 table 触发查询时,如果未提供正确的分区键,它就会进行完整的 table 扫描。换句话说,存储 table 是在分区键上建立索引的,正确地对数据进行分区是获得快速结果的关键。
就是说,现在您必须考虑要在 table 上触发什么样的查询。例如某个时间段内发生的日志,某个产品等
一种方法是使用精确到小时的反向刻度,而不是使用精确刻度作为分区键的一部分。这样就可以根据这个分区键查询一个小时的数据。根据落入每个分区的行数,您可以将精度更改为一天。此外,将相关数据存储在一起是明智的,这意味着每个产品的数据将转到不同的 table。这样您就可以减少分区数和每个分区中的行数。
基本上,确保您事先知道分区键(准确或范围)并针对此类特定分区键发起查询以更快地获得结果。
要加快写入 table 的速度,您可以使用批处理操作。要小心,好像批处理中的一个实体失败,整个批处理操作失败。适当的重试和错误检查可以节省您的时间。
同时,您可以使用blob存储来存储大量相关数据。这个想法是将一大块相关的序列化数据存储为一个 blob。您可以点击一个这样的 blob 来获取其中的所有数据,并在客户端进行进一步的投影。例如,一个产品的一个小时的数据将进入一个 blob,您可以设计一个特定的 blob 前缀命名模式并在需要时命中确切的 blob。这将帮助您非常快速地获取数据,而不是对每个查询进行 table 扫描。
我使用了 blob 方法并且已经使用了几年,没有遇到任何问题。我将我的集合转换为 IList<IDictionary<string,string>>
并使用二进制序列化和 Gzip 来存储每个 blob。我使用基于 Reflection.Emmit 的辅助方法来非常快速地访问实体属性,因此序列化和反序列化不会对 CPU 和内存造成影响。
将数据存储在 blob 中有助于我以更少的成本存储更多的数据并更快地获取数据。
如果我没看错问题,这里是解决方案的限制条件:
- 使用Table存储
- 大规模写入
- 按产品区域分开
- 按时间自动排序
已经提出了几个很好的解决方案,但我不认为有一个答案可以完美地满足所有的限制条件。
usr 提供了似乎最接近满足您的限制条件的解决方案。将您的产品区域划分为 N,但不要使用 GUID,只需使用一个数字 (ProductArea-5)。使用 GUID 会使查询问题变得更加困难。如果使用数字,则可以在单个查询中甚至并行查询产品区域的所有分区。然后继续对RowKey使用TicksReversed+GUID。
单一查询:PartitionKey ge 'ProductArea' and PartitionKey le 'ProductArea-~' and RowKey ge 'StartDateTimeReverseTicks' and RowKey le 'EndDateTimeReverseTicks'
并行查询:PartitionKey ge 'ProductArea-1' 和 RowKey ge 'StartDateTimeReverseTicks' 和 RowKey le 'EndDateTimeReverseTicks'
...
PartitionKey ge 'ProductArea-N' and RowKey ge 'StartDateTimeReverseTicks' and RowKey le 'EndDateTimeReverseTicks'
此解决方案不满足 'automatically ordered by time',但您可以按 RowKey 进行客户端排序以按顺序查看它们。如果必须对客户端进行排序对您来说没问题,那么此解决方案应该可以满足其余约束条件。
我们最近更新了我们的日志记录以使用 Azure table 存储,由于其低成本和按行和分区查询时的高性能非常适合此目的。
我们正在努力遵循文档 Designing a Scalable Partitioning Strategy for Azure Table Storage 中给出的指导方针。由于我们正在对此 table 进行大量插入(并希望随着我们的扩展,数量会增加),我们需要确保我们不会达到我们的限制导致日志丢失。我们的设计结构如下:
我们每个环境(开发、测试、生产)都有一个 Azure 存储帐户。
我们每个产品有一个 table。
我们正在为行键使用 TicksReversed+GUID,这样我们就可以 查询特定时间之间的结果块 表现。
我们本来选择用Logger对table进行分区,对我们来说 是产品的广泛领域,例如 API、应用程序、性能 和缓存。但是,由于分区数量较少,我们 担心这会导致所谓的 "hot" 分区,其中许多 在给定时间段内对一个分区执行插入。所以 我们更改为根据上下文分区(对我们来说,class 名称或 API 资源)。
然而,在实践中我们发现这不太理想,因为当我们一眼看日志时,我们希望它们按时间顺序出现。相反,我们最终得到的是按上下文分组的结果块,如果我们想按时间对它们进行排序,我们将不得不获取所有分区。
我们的一些想法是
对分区键使用时间块(比如 1 小时)按时间排序(导致热分区 1 小时)
使用一些分区键的随机 GUID 来尝试分发日志(我们失去了快速查询 Context 等功能的能力)。
由于这是 Azure table 存储的常见应用,因此必须有某种标准程序。 分区用于存储日志的 Azure table 的最佳做法是什么?
解约束
使用便宜的 Azure 存储(Table 存储似乎是显而易见的选择)
快速、可扩展的写入
丢失日志的可能性很小(即超过 Azure table 存储中每秒 2000 个实体的分区写入速率)。
阅读按日期排序,最近的在前。
如果可能的话,在对查询有用的东西(比如产品区域)上进行分区。
有一个非常通用的技巧可以在写入时避免热点,同时增加读取成本。
定义N个分区(比如10个左右)。写入一行时,将其填充到随机分区中。分区可以在内部按时间排序。
读取时需要从所有N个分区读取(可能是按时间过滤排序),合并查询结果。
这将写入可伸缩性提高了 N 倍,并使查询成本增加了相同数量的往返和查询。
此外,您可以考虑将日志存储在其他地方。对 Azure 产品的非常严格的人为限制会导致您在其他情况下不会产生人工成本。
选择 N 高于 达到每个帐户每秒 20,000 次操作限制所需的值,这样就不太可能出现随机热点。选择 N 是最低需要的两倍就足够了。
对你的问题并不是一个具体的答案,但这是我的一些想法:
您真正需要考虑的是如何查询数据并基于此设计数据 storage/partitioning 策略(牢记分区策略 guid)。例如,
- 如果您需要查看给定 date/time 范围内所有记录器的日志,那么您当前的方法可能不合适,因为您需要并行查询多个分区。
- 如果您想查询给定 date/time 范围内的特定记录器,您当前的方法会起作用。
- 向我建议的另一件事是适当使用 blob 存储和 table 存储。如果有一些不需要经常查询的数据,您可以简单地将这些数据推送到 blob 存储中(想想旧日志 - 如果您不打算将它们保存在 tables 中查询他们太频繁了)。每当您需要此类数据时,您只需从 blob 存储中提取它,将其推送到 table 存储中,然后 运行 针对该数据进行临时查询。
可能的解决方案
一种可能的解决方案是存储相同数据的多个副本并相应地使用这些副本。由于存储成本低,您可以保存相同数据的两个副本。在第一个副本中,您可以拥有 PK = Date/Time 和 RK = 无论您决定什么,在第二个副本中,您可以拥有 PK = Logger 和 RK = TicksReversed+GUID。然后当你想获取所有日志而不考虑记录器时,你可以简单地查询第一个副本(PK = Date/Time),如果你想查询特定记录器类型的日志,你可以简单地查询第二个副本(PK = 记录器,RK >= Date/Time 开始 & RK <= Date/Time 结束)。
您可能还会发现此 link 有帮助:http://azure.microsoft.com/en-us/documentation/articles/storage-table-design-guide/
我遇到过类似的情况,根据我的经验我可以说:
每当对 Azure 存储 table 触发查询时,如果未提供正确的分区键,它就会进行完整的 table 扫描。换句话说,存储 table 是在分区键上建立索引的,正确地对数据进行分区是获得快速结果的关键。
就是说,现在您必须考虑要在 table 上触发什么样的查询。例如某个时间段内发生的日志,某个产品等
一种方法是使用精确到小时的反向刻度,而不是使用精确刻度作为分区键的一部分。这样就可以根据这个分区键查询一个小时的数据。根据落入每个分区的行数,您可以将精度更改为一天。此外,将相关数据存储在一起是明智的,这意味着每个产品的数据将转到不同的 table。这样您就可以减少分区数和每个分区中的行数。
基本上,确保您事先知道分区键(准确或范围)并针对此类特定分区键发起查询以更快地获得结果。
要加快写入 table 的速度,您可以使用批处理操作。要小心,好像批处理中的一个实体失败,整个批处理操作失败。适当的重试和错误检查可以节省您的时间。
同时,您可以使用blob存储来存储大量相关数据。这个想法是将一大块相关的序列化数据存储为一个 blob。您可以点击一个这样的 blob 来获取其中的所有数据,并在客户端进行进一步的投影。例如,一个产品的一个小时的数据将进入一个 blob,您可以设计一个特定的 blob 前缀命名模式并在需要时命中确切的 blob。这将帮助您非常快速地获取数据,而不是对每个查询进行 table 扫描。
我使用了 blob 方法并且已经使用了几年,没有遇到任何问题。我将我的集合转换为 IList<IDictionary<string,string>>
并使用二进制序列化和 Gzip 来存储每个 blob。我使用基于 Reflection.Emmit 的辅助方法来非常快速地访问实体属性,因此序列化和反序列化不会对 CPU 和内存造成影响。
将数据存储在 blob 中有助于我以更少的成本存储更多的数据并更快地获取数据。
如果我没看错问题,这里是解决方案的限制条件:
- 使用Table存储
- 大规模写入
- 按产品区域分开
- 按时间自动排序
已经提出了几个很好的解决方案,但我不认为有一个答案可以完美地满足所有的限制条件。
usr 提供了似乎最接近满足您的限制条件的解决方案。将您的产品区域划分为 N,但不要使用 GUID,只需使用一个数字 (ProductArea-5)。使用 GUID 会使查询问题变得更加困难。如果使用数字,则可以在单个查询中甚至并行查询产品区域的所有分区。然后继续对RowKey使用TicksReversed+GUID。
单一查询:PartitionKey ge 'ProductArea' and PartitionKey le 'ProductArea-~' and RowKey ge 'StartDateTimeReverseTicks' and RowKey le 'EndDateTimeReverseTicks'
并行查询:PartitionKey ge 'ProductArea-1' 和 RowKey ge 'StartDateTimeReverseTicks' 和 RowKey le 'EndDateTimeReverseTicks' ... PartitionKey ge 'ProductArea-N' and RowKey ge 'StartDateTimeReverseTicks' and RowKey le 'EndDateTimeReverseTicks'
此解决方案不满足 'automatically ordered by time',但您可以按 RowKey 进行客户端排序以按顺序查看它们。如果必须对客户端进行排序对您来说没问题,那么此解决方案应该可以满足其余约束条件。