Il n'y a vraiment pas de «meilleure façon» de stocker des données de séries chronologiques, et cela dépend honnêtement d'un certain nombre de facteurs. Cependant, je vais me concentrer sur deux facteurs principalement, à savoir:
(1) Quelle est la gravité de ce projet qu'il mérite vos efforts pour optimiser le schéma?
(2) À quoi ressembleront réellement vos modèles d'accès aux requêtes ?
Avec ces questions à l'esprit, discutons quelques options de schéma.
Table plate
L'option d'utiliser une table plate a beaucoup plus à voir avec la question (1) , où si ce n'est pas un projet sérieux ou à grande échelle, vous trouverez beaucoup plus facile de ne pas trop penser au schéma, et utilisez simplement une table plate, comme:
CREATE flat_table(
trip_id integer,
tstamp timestamptz,
speed float,
distance float,
temperature float,
,...);
Il n'y a pas beaucoup de cas où je recommanderais ce cours, seulement si c'est un petit projet qui ne mérite pas beaucoup de votre temps.
Dimensions et faits
Donc, si vous avez surmonté l'obstacle de la question (1) et que vous souhaitez un schéma plus performant, c'est l'une des premières options à considérer. Il comprend une normalisation de base, mais en extrayant les quantités «dimensionnelles» des quantités «factuelles» mesurées.
Essentiellement, vous voudrez un tableau pour enregistrer des informations sur les voyages,
CREATE trips(
trip_id integer,
other_info text);
et une table pour enregistrer les horodatages,
CREATE tstamps(
tstamp_id integer,
tstamp timestamptz);
et enfin tous vos faits mesurés, avec des références de clés étrangères aux tables de dimension (c'est-à-dire des meas_facts(trip_id)
références trips(trip_id)
et des meas_facts(tstamp_id)
références tstamps(tstamp_id)
)
CREATE meas_facts(
trip_id integer,
tstamp_id integer,
speed float,
distance float,
temperature float,
,...);
Cela peut ne pas sembler très utile au début, mais si vous avez par exemple des milliers de voyages simultanés, ils peuvent tous prendre des mesures une fois par seconde, le deuxième. Dans ce cas, vous devrez réenregistrer l'horodatage à chaque fois pour chaque trajet, plutôt que de simplement utiliser une seule entrée dans le tstamps
tableau.
Cas d'utilisation: ce cas sera bon s'il y a de nombreux trajets simultanés pour lesquels vous enregistrez des données, et cela ne vous dérange pas d'accéder à tous les types de mesure tous ensemble.
Étant donné que Postgres lit par lignes, chaque fois que vous voulez, par exemple, les speed
mesures sur une plage de temps donnée, vous devez lire la ligne entière de la meas_facts
table, ce qui ralentira certainement une requête, bien que si l'ensemble de données avec lequel vous travaillez est pas trop grand, vous ne remarquerez même pas la différence.
Diviser vos faits mesurés
Pour étendre la dernière section un peu plus loin, vous pouvez diviser vos mesures en tableaux séparés, où par exemple je vais montrer les tableaux de vitesse et de distance:
CREATE speed_facts(
trip_id integer,
tstamp_id integer,
speed float);
et
CREATE distance_facts(
trip_id integer,
tstamp_id integer,
distance float);
Bien sûr, vous pouvez voir comment cela pourrait être étendu aux autres mesures.
Cas d'utilisation: cela ne vous donnera donc pas une vitesse énorme pour une requête, peut-être seulement une augmentation linéaire de la vitesse lorsque vous interrogez sur un type de mesure. En effet, lorsque vous souhaitez rechercher des informations sur la vitesse, il vous suffit de lire les lignes de la speed_facts
table, plutôt que toutes les informations supplémentaires inutiles qui seraient présentes dans une ligne de la meas_facts
table.
Donc, vous devez lire d'énormes quantités de données sur un seul type de mesure, vous pourriez en retirer des avantages. Avec votre cas proposé de 10 heures de données à une seconde d'intervalle, vous ne liriez que 36 000 lignes, de sorte que vous ne trouveriez jamais vraiment un avantage significatif à le faire. Cependant, si vous deviez consulter les données de mesure de la vitesse pour 5000 trajets qui duraient environ 10 heures, vous envisagez maintenant de lire 180 millions de lignes. Une augmentation linéaire de la vitesse pour une telle requête pourrait apporter certains avantages, tant que vous n'avez besoin d'accéder qu'à un ou deux des types de mesure à la fois.
Matrices / HStore / & TOAST
Vous n'avez probablement pas à vous soucier de cette partie, mais je connais des cas où cela importe. Si vous avez besoin d'accéder à d' énormes quantités de données de séries chronologiques et que vous savez que vous devez accéder à toutes ces données dans un bloc énorme, vous pouvez utiliser une structure qui utilisera les tables TOAST , qui stockent essentiellement vos données dans des fichiers plus grands et compressés. segments. Cela conduit à un accès plus rapide aux données, tant que votre objectif est d'accéder à toutes les données.
Un exemple de mise en œuvre pourrait être
CREATE uber_table(
trip_id integer,
tstart timestamptz,
speed float[],
distance float[],
temperature float[],
,...);
Dans ce tableau, tstart
stockerait l'horodatage pour la première entrée dans le tableau, et chaque entrée suivante serait la valeur d'une lecture pour la seconde suivante. Cela vous oblige à gérer l'horodatage pertinent pour chaque valeur de tableau dans un logiciel d'application.
Une autre possibilité est
CREATE uber_table(
trip_id integer,
speed hstore,
distance hstore,
temperature hstore,
,...);
où vous ajoutez vos valeurs de mesure sous forme de paires (clé, valeur) de (horodatage, mesure).
Cas d'utilisation: il s'agit d'une implémentation qu'il vaut probablement mieux laisser à quelqu'un qui est plus à l'aise avec PostgreSQL, et seulement si vous êtes sûr que vos modèles d'accès doivent être des modèles d'accès en masse.
Conclusions?
Wow, cela a pris beaucoup plus de temps que prévu, désolé. :)
Essentiellement, il existe un certain nombre d'options, mais vous obtiendrez probablement le meilleur rapport qualité-prix en utilisant la deuxième ou la troisième, car elles conviennent au cas plus général.
PS: Votre question initiale impliquait que vous chargeriez vos données en bloc une fois qu'elles auront toutes été collectées. Si vous diffusez les données dans votre instance PostgreSQL, vous devrez effectuer un travail supplémentaire pour gérer à la fois l'ingestion de données et la charge de travail des requêtes, mais nous laisserons cela pour une autre fois. ;)