存储数据的最佳方式:对于每天 10,000 个新行的情况,多列与多行
Best way to store data : Many columns vs many rows for a case of 10,000 new rows a day
在检查了很多关于 Whosebug 的类似问题后,似乎上下文会告诉哪种方式最好地保存数据...
简而言之,我在一个仅包含 3 列的非常简单的 table 中添加了 10,000 多行新数据。我永远不会更新行,只会进行选择、分组和取平均值。我正在寻找存储此数据的最佳方式,以尽可能快地进行平均计算。
为了让您了解上下文,我正在使用 FFT 分析录制的音频文件(混音工作室中的粉红噪声回放)。单个音频文件的结果始终采用相同的格式:频率仓的 ID(整数)及其以分贝为单位的值(浮点值)。我想将这些值存储在 PostgreSQL 数据库中。
频率(宽度 = 8Hz)的每个箱(带)获得以分贝为单位的振幅。第一个 bin 被忽略,所以它是这样的(不是实际的 dB 值):
- bin 1:8Hz-16Hz,-85.0dB
- bin 2:16Hz-32Hz,-73.0dB
- bin 3:32Hz-40Hz,-65.0dB
- ...
- bin 2499:20,000Hz-20,008Hz,-49.0dB
目标是存储从 8Hz 到 20,008Hz 的每个 bin 的振幅(1 个 bin 覆盖 8Hz)。
行数接近
对于每个分析的音频文件,将有 2,499 行 3 列:"Analysis UID"、"Bin ID" 和 "dB"。
对于每个工作室 (4),每天有一个记录要追加到数据库中(即 4 乘以 2,499 = 每天 9,996 新行)。
在一个录音室录音后,新的 2,499 行用于显示频率响应图。
我担心的是,我们还需要绘制单个工作室中每个 bin 的平均 dB 值 5-30 天的图,以查看频率响应是否会随时间发生显着变化(从而告诉我们需要在工作室进行校准)。
我为多行方法提出了以下数据结构:
"analysis" table:
- analysisUID(串行)
- studioUID(外键)
- 分析时间戳
"analysis_results" table:
- analysisUID(外键)
- freq_bin_id(整数)
- amplitude_dB(浮动)
这是存储数据的最佳方式吗?一个 table 每天持有近 10,000 个新行并平均进行 5 次或更多次分析,按 analysisUID 和 freq_bin_id 分组?这将给我 2,499 行(每行对应一个 bin 并给出平均 dB 值)。
多列方法:
我想我可以反过来做,在 4 table 秒内打破频率区间(低、中低、中高、高)。由于 Postgres 文档说列限制是 "250 - 1600,具体取决于列类型",因此制作包含大约 625 列的 4 table 是现实的 (2,499 / 4)每个代表一个 bin 并包含 "dB" 值,如下所示:
"low" table:
- analysisUID(外键)
- freq_bin_id_1_amplitude_dB(浮动)
- freq_bin_id_2_amplitude_dB(浮动)
- ...
- freq_bin_id_625_amplitude_dB(浮动)
"med_low" table:
- analysisUID(外键)
- freq_bin_id_626_amplitude_dB(浮动)
- freq_bin_id_627_amplitude_dB(浮动)
- ...
- freq_bin_id_1250_amplitude_dB(浮动)
等...
如果服务器只需按 analysisUID 分组并对每列取平均值,是否会更快地计算平均值?
行不会成为问题,但是,插入所述行的方式可能会成为问题。如果插入时间是主要问题之一,请确保您可以批量插入它们,或者选择行数较少的格式。
您可以将所有数据存储为 jsonb 格式,特别是因为您不会对数据进行任何更新——一次将所有数据存储在一个 table 中可能会很方便,但是性能可能会降低。
无论如何,由于您没有更新数据,填充因子(通常是默认值)100 是合适的。
我不会使用 "many column" 方法,因为
您所说的数据量实际上并没有那么多。使用 2 table 和几列的第一个示例很可能是获得结果的最佳方式。
索引以下列可能会有用:
analysis_results.freq_bin_id
analysis.analysisTimestamp
至于将数据分成不同的部分,这将取决于您的查询类型 运行。如果您正在查看所有频率段,使用多个 table 只会很麻烦,而且什么也得不到。
如果一次只查询一些 freq_bin,理论上它可能会有所帮助,但是,您基本上是在进行 table 分区,一旦您搬进那片土地,您不妨为每个频段做一个分区
如果我是你,我会创建你的第一个 table 结构,用 30 天的数据填充它并进行查询。您可能(正如我们经常做的那样)过度分析了情况。 Postgres 可以非常非常快。
请记住,您正在分析的原始数据大约是每天几兆(5 或更少)的绝对最大值。如果索引和存储正确,分析 150 MB 的数据对于 DB 运行 现代硬件来说并不费力。
优化器将在 "smaller" table 中找到正确的行,非常非常快并且可能缓存所有这些行,然后去寻找子行,它会知道正是要搜索的 ID 和范围。如果您的数据全部按时间顺序插入,则很有可能它会在很少的读取和很少的查找中读取所有数据。
我主要关心的是插入速度,因为如果您不进行批量插入,则执行 10,000 次插入可能需要一段时间。
由于测量结果看起来很好,您可以使用数组,使用 freq_bin 作为索引(注意:索引在 sql 中从 1 开始)
这具有将数组存储在烘烤存储中的额外优势,从而使物理 table 较小。
CREATE TABLE herrie
( analysisUID serial NOT NULL PRIMARY KEY
, studioUID INTEGER NOT NULL REFERENCES studio(studioUID)
, analysisTimestamp TIMESTAMP NOT NULL
, decibels float[] -- array with 625 measurements
, UNIQUE (studioUID,analysisTimestamp)
);
在检查了很多关于 Whosebug 的类似问题后,似乎上下文会告诉哪种方式最好地保存数据...
简而言之,我在一个仅包含 3 列的非常简单的 table 中添加了 10,000 多行新数据。我永远不会更新行,只会进行选择、分组和取平均值。我正在寻找存储此数据的最佳方式,以尽可能快地进行平均计算。
为了让您了解上下文,我正在使用 FFT 分析录制的音频文件(混音工作室中的粉红噪声回放)。单个音频文件的结果始终采用相同的格式:频率仓的 ID(整数)及其以分贝为单位的值(浮点值)。我想将这些值存储在 PostgreSQL 数据库中。
频率(宽度 = 8Hz)的每个箱(带)获得以分贝为单位的振幅。第一个 bin 被忽略,所以它是这样的(不是实际的 dB 值):
- bin 1:8Hz-16Hz,-85.0dB
- bin 2:16Hz-32Hz,-73.0dB
- bin 3:32Hz-40Hz,-65.0dB
- ...
- bin 2499:20,000Hz-20,008Hz,-49.0dB
目标是存储从 8Hz 到 20,008Hz 的每个 bin 的振幅(1 个 bin 覆盖 8Hz)。
行数接近
对于每个分析的音频文件,将有 2,499 行 3 列:"Analysis UID"、"Bin ID" 和 "dB"。
对于每个工作室 (4),每天有一个记录要追加到数据库中(即 4 乘以 2,499 = 每天 9,996 新行)。
在一个录音室录音后,新的 2,499 行用于显示频率响应图。
我担心的是,我们还需要绘制单个工作室中每个 bin 的平均 dB 值 5-30 天的图,以查看频率响应是否会随时间发生显着变化(从而告诉我们需要在工作室进行校准)。
我为多行方法提出了以下数据结构:
"analysis" table:
- analysisUID(串行)
- studioUID(外键)
- 分析时间戳
"analysis_results" table:
- analysisUID(外键)
- freq_bin_id(整数)
- amplitude_dB(浮动)
这是存储数据的最佳方式吗?一个 table 每天持有近 10,000 个新行并平均进行 5 次或更多次分析,按 analysisUID 和 freq_bin_id 分组?这将给我 2,499 行(每行对应一个 bin 并给出平均 dB 值)。
多列方法:
我想我可以反过来做,在 4 table 秒内打破频率区间(低、中低、中高、高)。由于 Postgres 文档说列限制是 "250 - 1600,具体取决于列类型",因此制作包含大约 625 列的 4 table 是现实的 (2,499 / 4)每个代表一个 bin 并包含 "dB" 值,如下所示:
"low" table:
- analysisUID(外键)
- freq_bin_id_1_amplitude_dB(浮动)
- freq_bin_id_2_amplitude_dB(浮动)
- ...
- freq_bin_id_625_amplitude_dB(浮动)
"med_low" table:
- analysisUID(外键)
- freq_bin_id_626_amplitude_dB(浮动)
- freq_bin_id_627_amplitude_dB(浮动)
- ...
- freq_bin_id_1250_amplitude_dB(浮动)
等...
如果服务器只需按 analysisUID 分组并对每列取平均值,是否会更快地计算平均值?
行不会成为问题,但是,插入所述行的方式可能会成为问题。如果插入时间是主要问题之一,请确保您可以批量插入它们,或者选择行数较少的格式。
您可以将所有数据存储为 jsonb 格式,特别是因为您不会对数据进行任何更新——一次将所有数据存储在一个 table 中可能会很方便,但是性能可能会降低。
无论如何,由于您没有更新数据,填充因子(通常是默认值)100 是合适的。
我不会使用 "many column" 方法,因为 您所说的数据量实际上并没有那么多。使用 2 table 和几列的第一个示例很可能是获得结果的最佳方式。
索引以下列可能会有用: analysis_results.freq_bin_id analysis.analysisTimestamp
至于将数据分成不同的部分,这将取决于您的查询类型 运行。如果您正在查看所有频率段,使用多个 table 只会很麻烦,而且什么也得不到。
如果一次只查询一些 freq_bin,理论上它可能会有所帮助,但是,您基本上是在进行 table 分区,一旦您搬进那片土地,您不妨为每个频段做一个分区
如果我是你,我会创建你的第一个 table 结构,用 30 天的数据填充它并进行查询。您可能(正如我们经常做的那样)过度分析了情况。 Postgres 可以非常非常快。
请记住,您正在分析的原始数据大约是每天几兆(5 或更少)的绝对最大值。如果索引和存储正确,分析 150 MB 的数据对于 DB 运行 现代硬件来说并不费力。
优化器将在 "smaller" table 中找到正确的行,非常非常快并且可能缓存所有这些行,然后去寻找子行,它会知道正是要搜索的 ID 和范围。如果您的数据全部按时间顺序插入,则很有可能它会在很少的读取和很少的查找中读取所有数据。
我主要关心的是插入速度,因为如果您不进行批量插入,则执行 10,000 次插入可能需要一段时间。
由于测量结果看起来很好,您可以使用数组,使用 freq_bin 作为索引(注意:索引在 sql 中从 1 开始) 这具有将数组存储在烘烤存储中的额外优势,从而使物理 table 较小。
CREATE TABLE herrie
( analysisUID serial NOT NULL PRIMARY KEY
, studioUID INTEGER NOT NULL REFERENCES studio(studioUID)
, analysisTimestamp TIMESTAMP NOT NULL
, decibels float[] -- array with 625 measurements
, UNIQUE (studioUID,analysisTimestamp)
);