시계열 데이터를 저장하는 방법


22

관련 값이 많은 시계열 데이터 세트 (잘못된 경우 수정하십시오)라고 생각합니다.

예를 들어 여행 중에 자동차를 모델링하고 다양한 속성을 추적하는 것이 있습니다. 예를 들면 다음과 같습니다.

타임 스탬프 | 속도 | 거리 여행 | 온도 | 기타

웹 애플리케이션이 필드를 효율적으로 쿼리하여 최대, 최소값을 찾고 시간에 따라 각 데이터 세트를 플롯 할 수 있도록이 데이터를 저장하는 가장 좋은 방법은 무엇입니까?

데이터 덤프를 파싱하고 결과를 캐싱하여 저장하지 않아도되는 순진한 접근 방식을 시작했습니다. 그러나 약간의 재생 후에는 메모리 제한으로 인해이 솔루션이 장기적으로 확장되지 않는 것으로 보이며 캐시를 지우려면 모든 데이터를 다시 구문 분석하고 다시 캐시해야합니다.

또한 10 시간 이상의 데이터 세트가 드물게 데이터가 1 ​​초마다 추적된다고 가정 할 때 일반적으로 N 초마다 샘플링하여 데이터 세트를 자르는 것이 좋습니다.

답변:


31

시계열 데이터를 저장하는 '최상의 방법'은 실제로 없으며 여러 가지 요인에 따라 정직하게 결정됩니다. 그러나 두 가지 요소에 주로 중점을 두겠습니다.

(1)이 프로젝트가 스키마를 최적화하려는 노력을 기울여야 할만큼 얼마나 심각합니까?

(2) 쿼리 액세스 패턴은 실제로 어떤 모양입니까?

이러한 질문을 염두에두고 몇 가지 스키마 옵션을 살펴 보겠습니다.

플랫 테이블

플랫 테이블을 사용하는 옵션은 question (1) 과 관련이 있습니다.이 프로젝트가 심각하거나 대규모 프로젝트가 아닌 경우 스키마에 대해 너무 많이 생각하지 않는 것이 훨씬 쉽습니다. 평평한 테이블을 사용하십시오.

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

이 과정을 추천하는 경우는 많지 않습니다. 시간이 많이 걸리지 않는 작은 프로젝트 인 경우에만 가능합니다.

차원과 사실

따라서 (1) 의 문제를 해결하고 더 많은 성능 스키마를 원한다면 이것이 가장 먼저 고려해야 할 옵션 중 하나입니다. 여기에는 기본 규범화가 포함되지만 측정 된 'fact'수량에서 '치수'수량을 추출합니다.

기본적으로 여행에 대한 정보를 기록하는 테이블이 필요합니다.

CREATE trips(
  trip_id integer,
  other_info text);

타임 스탬프를 기록하는 테이블,

CREATE tstamps(
  tstamp_id integer,
  tstamp timestamptz);

마지막으로 차원 테이블에 대한 외래 키 참조 ( meas_facts(trip_id)참조 trips(trip_id)meas_facts(tstamp_id)참조 tstamps(tstamp_id)) 와 함께 모든 측정 사실

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

처음에는 그다지 도움이되지 않는 것처럼 보일 수 있지만, 예를 들어 수천 번의 동시 트립이있는 경우 두 번째에서 초당 한 번씩 측정을 수행 할 수 있습니다. 이 경우 tstamps테이블 의 단일 항목을 사용하지 않고 각 여행에 대해 매번 타임 스탬프를 다시 기록해야 합니다.

사용 사례 : 데이터를 기록하는 동시 트립이 많고 모든 측정 유형에 모두 액세스하는 것이 마음에 들지 않으면이 경우가 좋습니다.

Postgres는 행 단위로 읽으므로 원하는 speed시간 범위에 대한 측정 과 같이 원하는 시간마다 meas_facts테이블 에서 전체 행을 읽어야합니다.이 경우 작업하는 데이터 세트가 너무 크지 않으면 차이를 느끼지 못할 것입니다.

측정 된 사실 나누기

마지막 섹션을 조금 더 확장하기 위해 측정 값을 별도의 테이블로 분리 할 수 ​​있습니다. 예를 들어 속도와 거리에 대한 테이블을 표시하겠습니다.

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

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

물론 이것이 다른 측정으로 어떻게 확장되는지 알 수 있습니다.

사용 사례 : 따라서 쿼리 속도가 엄청나게 향상되지는 않으며, 한 가지 측정 유형에 대해 쿼리 할 때 속도가 선형 적으로 증가 할 수도 있습니다. 속도에 대한 정보를 찾으려면 speed_facts테이블의 행에있는 불필요한 정보가 아닌 테이블 에서 행을 읽어야 하기 때문 meas_facts입니다.

따라서 하나의 측정 유형에 대해서만 방대한 양의 데이터를 읽어야하므로 이점을 얻을 수 있습니다. 1 초 간격으로 10 시간 분량의 데이터를 제안하면 36,000 개의 행만 읽을 수 있으므로이 작업을 수행하면 큰 이점을 얻을 수 없습니다. 그러나 약 10 시간 동안 5,000 회 여행에 대한 속도 측정 데이터를 살펴 보려면 이제 1 억 8 천만 행을 읽는 중입니다. 한 번에 하나 또는 두 개의 측정 유형에만 액세스해야하는 한, 이러한 쿼리에 대한 선형 속도 증가는 몇 가지 이점을 제공 할 수 있습니다.

어레이 / HStore / 및 TOAST

이 부분에 대해 걱정할 필요는 없지만 중요한 부분은 알고 있습니다. 당신이 액세스해야하는 경우 거대한 시계열 데이터의 양, 당신은 당신이 하나 개의 거대한 블록에 모두 접근 할 필요가 알고, 당신은 사용하게하는 구조를 사용할 수 있습니다 TOAST 테이블 압축 본질적으로 더 큰에서 데이터를 저장, 세그먼트. 따라서 모든 데이터에 액세스하는 것이 목표 인 한 데이터에 더 빠르게 액세스 할 수 있습니다.

한 가지 구현 예는 다음과 같습니다.

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

이 표 tstart에서 첫 번째 항목의 타임 스탬프를 배열에 저장하고 이후의 각 항목은 다음 초의 값이됩니다. 이를 위해서는 애플리케이션 소프트웨어에서 각 어레이 값에 대한 관련 타임 스탬프를 관리해야합니다.

또 다른 가능성은

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

여기서 측정 값을 (키, 값) 쌍의 (타임 스탬프, 측정)으로 추가합니다.

유스 케이스 : 이것은 PostgreSQL에 더 익숙한 사람에게 더 나은 구현 일 것입니다. 액세스 패턴이 벌크 액세스 패턴이어야하는 것이 확실한 경우에만 가능합니다.

결론?

와우, 이건 내가 예상했던 것보다 훨씬 길었어, 미안 :)

기본적으로 여러 가지 옵션이 있지만 더 일반적인 경우에 따라 두 번째 또는 세 번째를 사용하여 벅에 가장 큰 타격을 줄 것입니다.

추신 : 첫 번째 질문은 데이터가 모두 수집 된 후에 데이터를 대량로드한다는 것을 암시합니다. PostgreSQL 인스턴스에 데이터를 스트리밍하는 경우 데이터 수집 및 쿼리 워크로드를 모두 처리하기 위해 몇 가지 추가 작업을 수행해야하지만 다른 시간에는 그대로 두겠습니다. ;)


와우 자세한 답변 감사합니다, 크리스! 옵션 2 또는 3을 사용하겠습니다.
guest82

당신에게 행운을 빕니다!
크리스

와우, 가능하다면이 답변을 1000 번 투표 할 것입니다. 자세한 설명을 주셔서 감사합니다.
kikocorreoso 19

1

2019 이 문제는 업데이트 된 대답을 가치가있다.

  • 접근 방식이 최선인지 아닌지를 벤치마킹하고 테스트 해 보도록하겠습니다. 그러나 여기에 접근 방식이 있습니다.
  • timescaledb 라는 데이터베이스 확장명을 사용하십시오.
  • 이것은 표준 PostgreSQL에 설치된 확장이며 합리적으로 시계열을 저장하는 동안 발생하는 몇 가지 문제를 처리합니다.

예를 들어, 먼저 PostgreSQL에서 간단한 테이블을 만듭니다.

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
) 

2 단계

  • 이것을 timescaledb 세계에서 하이퍼 테이블 이라고 부르는 것으로 바꾸 십시오 .
  • 간단히 말해서, 그것은 일정한 시간 간격의 작은 테이블로 연속적으로 분할되는 큰 테이블입니다. 예를 들어 각 미니 테이블은 청크라고합니다.
  • 이 미니 테이블은 쿼리를 실행할 때 명확하지 않지만 쿼리에 포함하거나 제외 할 수 있습니다.

    SELECT create_hypertable ( 'trip', 'ts', chunk_time_interval => 간격 '1 시간', if_not_exists => TRUE);

  • 위에서 한 것은 트립 테이블을 가져 와서 열 'ts'를 기준으로 매 시간마다 미니 청크 테이블로 나누는 것입니다. 타임 스탬프를 10:00에서 10:59로 추가하면 1 덩어리에 추가되지만 11:00은 새 덩어리에 삽입되며 무한대로 진행됩니다.

  • 데이터를 무한정 저장하지 않으려면 3 개월이 지난 청크를 DROP 할 수도 있습니다.

    SELECT drop_chunks (간격 '3 개월', '여행');

  • 다음과 같은 쿼리를 사용하여 날짜까지 작성된 모든 청크 목록을 얻을 수도 있습니다.

    chunk_table, table_bytes, index_bytes, total_bytes FROM chunk_relation_size ( 'trip')을 선택하십시오.

  • 이렇게하면 날짜까지 생성 된 모든 미니 테이블 목록이 표시되며이 목록에서 원하는 경우 마지막 미니 테이블에서 쿼리를 실행할 수 있습니다

  • 청크를 포함하거나 제외하거나 마지막 N 청크에서만 작동하도록 쿼리를 최적화 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.