Cara menyimpan data deret waktu

22

Saya memiliki apa yang saya yakini sebagai rangkaian data deret waktu (mohon perbaiki jika saya salah) yang memiliki banyak nilai terkait.

Contohnya adalah memodelkan mobil dan melacak berbagai atributnya selama perjalanan. Sebagai contoh:

timestamp | kecepatan | jarak tempuh | suhu | dll

Apa yang akan menjadi cara terbaik untuk menyimpan data ini sehingga aplikasi web dapat secara efisien meminta bidang untuk menemukan maks, menit, dan plot setiap set data dari waktu ke waktu?

Saya memulai pendekatan naif mem-parsing data dump dan menyimpan hasilnya agar tidak perlu disimpan. Namun, setelah bermain sedikit, tampaknya solusi ini tidak akan menskala jangka panjang karena kendala memori dan jika cache harus dihapus, maka semua data harus diurai dan di-cache ulang.

Juga, dengan asumsi bahwa data dilacak setiap detik dengan kemungkinan langka set data 10+ jam, apakah umumnya disarankan untuk memotong set data dengan mengambil sampel setiap N detik?

tamu82
sumber

Jawaban:

31

Benar-benar tidak ada 'cara terbaik' untuk menyimpan data deret waktu, dan itu jujur ​​tergantung pada sejumlah faktor. Namun, saya akan fokus pada dua faktor terutama, dengan mereka adalah:

(1) Seberapa seriuskah proyek ini sehingga layak Anda upayakan untuk mengoptimalkan skema?

(2) Apa yang pola akses query Anda benar-benar akan menjadi seperti?

Dengan pertanyaan-pertanyaan itu dalam pikiran, mari kita bahas beberapa opsi skema.

Meja datar

Opsi untuk menggunakan tabel datar lebih banyak terkait dengan pertanyaan (1) , di mana jika ini bukan proyek serius atau skala besar, Anda akan merasa jauh lebih mudah untuk tidak terlalu memikirkan skema, dan cukup gunakan tabel datar, seperti:

CREATE flat_table(
  trip_id integer,
  tstamp timestamptz,
  speed float,
  distance float,
  temperature float,
  ,...);

Tidak banyak kasus di mana saya akan merekomendasikan kursus ini, hanya jika ini adalah proyek kecil yang tidak menjamin banyak waktu Anda.

Dimensi dan Fakta

Jadi, jika Anda telah mengatasi rintangan pertanyaan (1) , dan Anda menginginkan skema kinerja yang lebih banyak, ini adalah salah satu opsi pertama yang perlu dipertimbangkan. Ini mencakup beberapa normailisasi dasar, tetapi mengekstraksi kuantitas 'dimensi' dari kuantitas 'fakta' yang terukur.

Pada dasarnya, Anda akan menginginkan tabel untuk merekam info tentang perjalanan,

CREATE trips(
  trip_id integer,
  other_info text);

dan meja untuk mencatat cap waktu,

CREATE tstamps(
  tstamp_id integer,
  tstamp timestamptz);

dan akhirnya semua fakta terukur Anda, dengan referensi kunci asing ke tabel dimensi (yaitu meas_facts(trip_id)referensi trips(trip_id)& meas_facts(tstamp_id)referensi tstamps(tstamp_id))

CREATE meas_facts(
  trip_id integer,
  tstamp_id integer,
  speed float,
  distance float,
  temperature float,
  ,...);

Ini mungkin kelihatannya tidak terlalu membantu pada awalnya, tetapi jika Anda memiliki misalnya ribuan perjalanan bersamaan, maka mereka semua mungkin melakukan pengukuran sekali per detik, pada yang kedua. Dalam hal ini, Anda harus merekam ulang cap waktu setiap kali untuk setiap perjalanan, daripada hanya menggunakan satu entri dalam tstampstabel.

Use case: Case ini akan baik jika ada banyak perjalanan bersamaan yang Anda rekam data, dan Anda tidak keberatan mengakses semua tipe pengukuran secara bersamaan.

Karena Postgres dibaca oleh baris, kapan pun Anda inginkan, misalnya, speedpengukuran pada rentang waktu tertentu, Anda harus membaca seluruh baris dari meas_factstabel, yang pasti akan memperlambat kueri, meskipun jika kumpulan data yang Anda kerjakan adalah tidak terlalu besar, maka Anda bahkan tidak akan melihat perbedaannya.

Memisahkan Fakta Yang Terukur Anda

Untuk memperpanjang bagian terakhir hanya sedikit lebih jauh, Anda dapat memecah pengukuran Anda menjadi tabel yang terpisah, di mana misalnya saya akan menunjukkan tabel untuk kecepatan dan jarak:

CREATE speed_facts(
  trip_id integer,
  tstamp_id integer,
  speed float);

dan

CREATE distance_facts(
  trip_id integer,
  tstamp_id integer,
  distance float);

Tentu saja, Anda dapat melihat bagaimana ini dapat diperluas ke pengukuran lainnya.

Use case: Jadi ini tidak akan memberi Anda kecepatan luar biasa untuk sebuah kueri, mungkin hanya peningkatan linear dalam kecepatan ketika Anda bertanya tentang satu tipe pengukuran. Ini karena ketika Anda ingin mencari info tentang kecepatan, Anda hanya perlu membaca baris dari speed_factstabel, daripada semua info tambahan yang tidak dibutuhkan yang akan hadir di deretan meas_factstabel.

Jadi, Anda perlu membaca bulks besar data tentang satu jenis pengukuran saja, Anda bisa mendapatkan beberapa manfaat. Dengan kasus data 10 jam yang diusulkan pada interval satu detik, Anda hanya akan membaca 36.000 baris, sehingga Anda tidak akan pernah benar-benar menemukan manfaat yang signifikan dari melakukan ini. Namun, jika Anda ingin melihat data pengukuran kecepatan untuk 5.000 perjalanan yang semuanya sekitar 10 jam, sekarang Anda sedang membaca 180 juta baris. Peningkatan kecepatan linear untuk kueri semacam itu dapat menghasilkan beberapa manfaat, asalkan Anda hanya perlu mengakses satu atau dua tipe pengukuran sekaligus.

Array / HStore / & TOAST

Anda mungkin tidak perlu khawatir tentang bagian ini, tetapi saya tahu kasus di mana itu penting. Jika Anda perlu mengakses sejumlah besar data deret waktu, dan Anda tahu Anda perlu mengakses semuanya dalam satu blok besar, Anda dapat menggunakan struktur yang akan menggunakan TOAST Tables , yang pada dasarnya menyimpan data Anda dalam ukuran yang lebih besar, terkompresi segmen. Ini mengarah ke akses yang lebih cepat ke data, selama tujuan Anda adalah untuk mengakses semua data.

Salah satu contoh implementasi bisa

CREATE uber_table(
  trip_id integer,
  tstart timestamptz,
  speed float[],
  distance float[],
  temperature float[],
  ,...);

Dalam tabel ini, tstartakan menyimpan cap waktu untuk entri pertama dalam array, dan setiap entri berikutnya akan menjadi nilai pembacaan untuk detik berikutnya. Ini mengharuskan Anda untuk mengelola cap waktu yang relevan untuk setiap nilai array dalam perangkat lunak aplikasi.

Kemungkinan lain adalah

CREATE uber_table(
  trip_id integer,
  speed hstore,
  distance hstore,
  temperature hstore,
  ,...);

di mana Anda menambahkan nilai pengukuran Anda sebagai (kunci, nilai) pasangan (cap waktu, pengukuran).

Use case: Ini adalah implementasi yang mungkin lebih baik diserahkan kepada seseorang yang lebih nyaman dengan PostgreSQL, dan hanya jika Anda yakin tentang pola akses Anda yang membutuhkan pola akses massal.

Kesimpulannya?

Wow, ini jauh lebih lama dari yang saya harapkan, maaf. :)

Pada dasarnya, ada sejumlah opsi, tetapi Anda mungkin akan mendapatkan keuntungan terbesar dengan menggunakan yang kedua atau ketiga, karena cocok dengan case yang lebih umum.

PS: Pertanyaan awal Anda menyiratkan bahwa Anda akan memuat data Anda secara massal setelah semuanya dikumpulkan. Jika Anda mengalirkan data ke instance PostgreSQL Anda, Anda perlu melakukan beberapa pekerjaan lebih lanjut untuk menangani baik konsumsi data maupun beban kerja kueri, tetapi kami akan membiarkannya untuk lain waktu. ;)

Chris
sumber
Wow, terima kasih atas jawaban terinci, Chris! Saya akan melihat ke dalam menggunakan opsi 2 atau 3.
guest82
Semoga beruntung untukmu!
Chris
Wow, saya akan memilih jawaban ini 1000 kali jika saya bisa. Terima kasih untuk penjelasan rinci.
kikocorreoso
1

Ini 2019 dan pertanyaan ini layak mendapat jawaban yang diperbarui.

  • Apakah pendekatan itu yang terbaik atau tidak adalah sesuatu, saya akan membiarkan Anda melakukan tolok ukur dan menguji tetapi inilah pendekatannya.
  • Gunakan ekstensi basis data yang disebut timescaledb
  • Ini adalah ekstensi yang dipasang pada PostgreSQL standar dan menangani beberapa masalah yang ditemui saat menyimpan deret waktu dengan cukup baik

Mengambil contoh Anda, pertama buat tabel sederhana di PostgreSQL

Langkah 1

CREATE TABLE IF NOT EXISTS trip (
    ts TIMESTAMPTZ NOT NULL PRIMARY KEY,
    speed REAL NOT NULL,
    distance REAL NOT NULL,
    temperature REAL NOT NULL
) 

Langkah 2

  • Ubah ini menjadi apa yang disebut hipertensi dalam dunia skala waktu b.
  • Dengan kata sederhana, ini adalah meja besar yang terus menerus dibagi menjadi tabel-tabel kecil dengan interval waktu tertentu, katakanlah hari di mana setiap meja mini disebut sebagai chunk
  • Tabel mini ini tidak jelas saat Anda menjalankan kueri meskipun Anda dapat memasukkan atau mengecualikannya dalam kueri Anda

    SELECT create_hypertable ('trip', 'ts', chunk_time_interval => interval '1 jam', if_not_exists => TRUE);

  • Apa yang telah kami lakukan di atas adalah mengambil tabel perjalanan kami, membaginya ke dalam tabel chunk mini setiap jam berdasarkan kolom 'ts'. Jika Anda menambahkan stempel waktu dari 10:00 hingga 10:59 mereka akan ditambahkan ke 1 chunk tetapi 11:00 akan dimasukkan ke dalam chunk baru dan ini akan berlangsung tanpa batas.

  • Jika Anda tidak ingin menyimpan data tanpa batas, Anda juga dapat DROP potongan yang lebih lama dari 3 bulan menggunakan

    SELECT drop_chunks (interval '3 bulan', 'perjalanan');

  • Anda juga bisa mendapatkan daftar semua potongan yang dibuat hingga tanggal menggunakan kueri suka

    SELECT chunk_table, table_bytes, index_bytes, total_bytes DARI chunk_relation_size ('trip');

  • Ini akan memberi Anda daftar semua tabel mini yang dibuat hingga tanggal dan Anda dapat menjalankan kueri pada tabel mini terakhir jika Anda inginkan dari daftar ini

  • Anda dapat mengoptimalkan kueri Anda untuk memasukkan, mengecualikan potongan atau hanya beroperasi pada potongan N terakhir dan seterusnya

PirateApp
sumber