私は若い開発者であり、データベース(PostgreSQL 9.3)の使用にあまり熟練していないので、本当に助けが必要なプロジェクトでいくつかの問題に遭遇しました。
私のプロジェクトは、デバイス(最大1000以上のデバイス)からデータを収集することです。ここで、すべてのデバイスが毎秒1つのデータブロックを送信しており、1時間あたり約300万行になります。
現在、すべてのデバイスの受信データを格納する1つの大きなテーブルがあります。
CREATE TABLE data_block(
id bigserial
timestamp timestamp
mac bigint
)
データブロックに含めることができる(またはできない)データにはいくつかの種類があるため、data_block
テーブルを参照する他のテーブルがあります。
CREATE TABLE dataA(
data_block_id bigserial
data
CONSTRAINT fkey FOREIGN KEY (data_block_id) REFERENCES data_block(id);
);
CREATE TABLE dataB(...);
CREATE TABLE dataC(...);
CREATE INDEX index_dataA_block_id ON dataA (data_block_id DESC);
...
1つのdata_blockに3x dataA、1x dataBがあるが、dataCがない可能性があります。
データは数週間保持されるので、このテーブルには約50億行が含まれます。現時点では、テーブルに6億行まであり、クエリには非常に長い時間がかかります。そのため、timestamp
とmac
のインデックスを作成することにしました。これは、selectステートメントが常にクエリを実行し、多くの場合、time + macもクエリを実行するためです。
CREATE INDEX index_ts_mac ON data_block (timestamp DESC, mac);
...しかし、クエリにはまだ時間がかかります。たとえば、1日と1つのMacのデータをクエリしました。
SELECT * FROM data_block
WHERE timestamp>'2014-09-15'
AND timestamp<'2014-09-17'
AND mac=123456789
Index Scan using index_ts_mac on data_block (cost=0.57..957307.24 rows=315409 width=32) (actual time=39.849..334534.972 rows=285857 loops=1)
Index Cond: ((timestamp > '2014-09-14 00:00:00'::timestamp without time zone) AND (timestamp < '2014-09-16 00:00:00'::timestamp without time zone) AND (mac = 123456789))
Total runtime: 334642.078 ms
クエリを実行する前に完全なバキュームを行いました。大きなテーブルでこのような問題を解決して10秒未満のクエリを実行するエレガントな方法はありますか?
パーティション分割について読みましたが、dataA、dataB、dataCがdata_block_idを参照している場合は機能しませんか?それが何らかの方法で機能する場合、時間をかけて、またはMacでパーティションを作成する必要がありますか?
インデックスを別の方向に変更しました。最初のMAC、次にタイムスタンプ、そしてそれは多くのパフォーマンスを獲得します。
CREATE INDEX index_mac_ts ON data_block (mac, timestamp DESC);
ただし、クエリには30秒以上かかります。特に、データテーブルでLEFT JOIN
を実行する場合は特にそうです。以下は、新しいインデックスを使用したクエリのEXPLAIN ANALYZE
です。
EXPLAIN ANALYZE SELECT * FROM data_block WHERE mac = 123456789 AND timestamp < '2014-10-05 00:00:00' AND timestamp > '2014-10-04 00:00:00'
Bitmap Heap Scan on data_block (cost=1514.57..89137.07 rows=58667 width=28) (actual time=2420.842..32353.678 rows=51342 loops=1)
Recheck Cond: ((mac = 123456789) AND (timestamp < '2014-10-05 00:00:00'::timestamp without time zone) AND (timestamp > '2014-10-04 00:00:00'::timestamp without time zone))
-> Bitmap Index Scan on index_mac_ts (cost=0.00..1499.90 rows=58667 width=0) (actual time=2399.291..2399.291 rows=51342 loops=1)
Index Cond: ((mac = 123456789) AND (timestamp < '2014-10-05 00:00:00'::timestamp without time zone) AND (timestamp > '2014-10-04 00:00:00'::timestamp without time zone))
Total runtime: 32360.620 ms
残念ながら、私のハードウェアは厳しく制限されています。 Intel i3-2100 @ 3.10Ghz、4GB RAMを使用しています。私の現在の設定は次のとおりです:
default_statistics_target = 100
maintenance_work_mem = 512MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 4GB
work_mem = 512MB
wal_buffers = 16MB
checkpoint_segments = 32
shared_buffers = 2GB
max_connections = 20
random_page_cost = 2
これは私のMS SQLバイアスを反映しているかもしれませんが、timestamp
でテーブルをクラスター化してみます。特定の期間にわたって頻繁にデータをプルしている場合、データが物理的に隣接して保存されるため、これは役立ちます。システムは、開始点までシークし、範囲の最後までスキャンして、完了することができます。特定の時間をクエリする場合、それは3,600,000レコードにすぎません。
クエリ(つまり...?)が特定のマシンに対するものである場合、Postgresはこれらの3.6 Mレコードの99.9%を除外する必要があります。この1000分の1フィルターが一般的な日付範囲フィルターよりも選択的である場合は、より選択的なmac
フィールドをインデックスの最初のコンポーネントとして使用する必要があります。それでもクラスタリングする価値があるかもしれません。
それでもそれができない場合は、インデックスを作成しているのと同じフィールド(timestamp
またはmac
)でパーティション分割します。
データ型を指定しませんでした。それらはデータに適していますか?たとえば、日付をテキストとして保存すると、テーブルが不必要に肥大化します。