2016-09-23 2 views
2

웹 기반 모니터링 프로젝트에 대한 실시간 데이터 및 내역 데이터가 표시되었습니다. 샘플 주파수가 50Hz 인 거의 16 개의 센서가 있습니다. 센서의 모든 원시 데이터는 데이터베이스에 저장되어야하며, 초당 거의 900 개의 데이터에 도달합니다. 그리고 데이터는 최소 3 년 동안 저장되어야합니다. 데이터베이스는 Oracle 11g입니다.실시간 데이터 테이블 - 내역 데이터 테이블에 대한 데이터 손실

내 직업은 데이터 수집 프로그램을 작성하고 데이터베이스에 데이터를 저장할 센서 하드웨어 회사의 엔지니어를위한 데이터베이스 구조를 설계하는 것입니다.

실시간 데이터 테이블 및 내역 데이터 테이블이 설계되었습니다. 실시간 데이터 테이블과 히스토리에서 실시간 데이터를 읽습니다. 데이터는 히스토리 데이터 테이블에서 읽습니다.

실제 데이터 테이블은 1 분 데이터 만 저장 한 다음과 같습니다.

    :

    Create Table real_data(
    record_time timestamp(3), 
    ac_1 Float, 
    ac_2 Float, 
    ac_3 Float, 
    ac_4 Float, 
    ac_5 Float, 
    ac_6 Float, 
    ac_7 Float, 
    ac_8 Float, 
    ac_9 Float, 
    ac_10 Float, 
    ac_11 Float, 
    ac_12 Float, 
    ac_13 Float, 
    ac_14 Float, 
    ac_15 Float, 
    ac_16 Float 
    ) 
    Tablespace data_test; 
    

    는 히스토리 데이터 테이블의 구조는 간격 파티션 두 가지 이유 때문에 선택

    Create Table history_data(
    record_time timestamp(3), 
    ac_1 Float, 
    ac_2 Float, 
    ac_3 Float, 
    ac_4 Float, 
    ac_5 Float, 
    ac_6 Float, 
    ac_7 Float, 
    ac_8 Float, 
    ac_9 Float, 
    ac_10 Float, 
    ac_11 Float, 
    ac_12 Float, 
    ac_13 Float, 
    ac_14 Float, 
    ac_15 Float, 
    ac_16 Float 
    ) 
    Tablespace data_test 
    PARTITION BY RANGE(record_time) 
    INTERVAL(numtodsinterval(1,'day')) 
    ( 
        PARTITION P1 VALUES LESS THAN (TO_DATE('2016-08-01', 'YYYY-MM-DD')) 
    ); 
    
    alter table history_data add constraint RECORD_DATE primary key (RECORD_TIME); 
    

    기본 키와 파티션 구성 실제 데이터와 동일

  1. SQL 쿼리는 웹 클라이언트의 시간 기록을 기반으로합니다 (예 :

    ). 선택 ac_1에서 ac_test 여기서 record_time> = to_timestamp ('2016-08-01 00:00:00', 'yyyy-mm-dd hh24 : mi : ss') 그리고 record_time < = to_timestamp ('2016-08-01 00 : 30 : 00 ','yyyy-mm-dd hh24 : mi : ss ');

  2. 간격 파티션은 일에서 원거리입니다. 하루 동안 데이터를 테스트하는 동안 하루에 430 만 데이터에 대해 40 초가 소요되었습니다.

작업이 실행되어 1 분당 실제 데이터 - 이력 데이터 테이블이 전송됩니다. 전송 프로세스는 oracle 프로 시저에 의해 수행되고, 전송 시간은 다른 테이블 real_data_top_backup_date에 의해 기록됩니다.

create or replace procedure copy_to_history_test is 
d_top_backup_date timestamp(3); 
begin 

select top_backup_date into d_top_backup_date from real_data_top_backup_date; 

Insert Into history_data Select * From real_data where record_time <d_top_backup_date; 

delete from real_data where record_time <d_top_backup_date; 

Update real_data_top_backup_date Set top_backup_date=(d_top_backup_date+1/24/60); 

commit; 

end copy_to_history_test; 

시뮬레이션 데이터를 수집하고 삽입하는 시뮬레이션 프로그램이 작성되었습니다.

Declare 
time_index Number; 
start_time Timestamp(3); 
tmp_time Timestamp(3); 
tmp_value1 Float; 
tmp_value2 Float; 
tmp_value3 Float; 
tmp_value4 Float; 
tmp_value5 Float; 
tmp_value6 Float; 
tmp_value7 Float; 
tmp_value8 Float; 
tmp_value9 Float; 
tmp_value10 Float; 
tmp_value11 Float; 
tmp_value12 Float; 
tmp_value13 Float; 
tmp_value14 Float; 
tmp_value15 Float; 
tmp_value16 Float; 


Begin 

--initiaze the variable 
time_index:=0;  
SELECT to_timestamp('2016-08-01 00:00:00:000', 'yyyy-mm-dd h24:mi:ss:ff') Into start_time FROM DUAL; 

     While time_index<(50*60*60*24*7) 
     Loop 
     -- add 20 millionseconds 
     SELECT start_time+numtodsinterval((0.02*time_index),'SECOND') Into tmp_time FROM DUAL; 
     -- dbms_output.put_line(tmp_time); 
     -- create random number 
     select dbms_random.value Into tmp_value1 from dual ; 
     select dbms_random.value Into tmp_value2 from dual ; 
     select dbms_random.value Into tmp_value3 from dual ; 
     select dbms_random.value Into tmp_value4 from dual ; 
     select dbms_random.value Into tmp_value5 from dual ; 
     select dbms_random.value Into tmp_value6 from dual ; 
     select dbms_random.value Into tmp_value7 from dual ; 
     select dbms_random.value Into tmp_value8 from dual ; 
     select dbms_random.value Into tmp_value9 from dual ; 
     select dbms_random.value Into tmp_value10 from dual ; 
     select dbms_random.value Into tmp_value11 from dual ; 
     select dbms_random.value Into tmp_value12 from dual ; 
     select dbms_random.value Into tmp_value13 from dual ; 
     select dbms_random.value Into tmp_value14 from dual ; 
     select dbms_random.value Into tmp_value15 from dual ; 
     select dbms_random.value Into tmp_value16 from dual ; 
     --dbms_output.put_line(tmp_value); 

     -- Insert Into ac_data (sensor_id,data,record_time) Values(sensor_index,tmp_value,tmp_time); 
     Insert Into real_data Values(tmp_time,tmp_value1,tmp_value2,tmp_value3,tmp_value4,tmp_value5,tmp_value6,tmp_value7,tmp_value8,tmp_value9,tmp_value10,tmp_value11,tmp_value12,tmp_value13,tmp_value14,tmp_value15,tmp_value16); 
     if mod(time_index,50)=0 then 
     commit; 
     dbms_lock.sleep(1); 
     End If; 

     time_index:=time_index+1; 
     End Loop; 

-- dbms_output.put_line(c); 
    Exception 
    WHEN OTHERS THEN 
    log_write('insert data failure!'); 
End; 

문제는 전송 데이터 절차 중에 거의 0.1 % 양의 센서 데이터가 손실된다는 것입니다. 나는 데이터 전송 (데이터 삽입 및 데이터 삭제)의 병렬 작업이 데이터 손실로 이어진다 고 생각합니다. 문제를 해결하는 방법?

또한이 시나리오에서 데이터베이스 구조가 실현 가능합니까? 데이터베이스를위한 또 다른 더 좋은 디자인이 있습니까?

+0

데이터가 손실되었음을 어떻게 알았습니까? –

+0

@ EvgeniyK.나는 하루 동안 4316850 센서 데이터가 있다는 것을 알았고 howerver는 432000 데이터로 구성됩니다. – skyspeed

답변

0

매우 가능한 "약 0.1 % 량 센서 데이터는 손실 될 것이다." 기본적으로 Oracle은 문 수준이 인 읽기 커밋 된 격리 모델을 사용합니다. 분리 레벨은 다른 세션이 테이블에 추가 한 레코드가 프로 시저에서 삽입 한 레코드 세트에 포함되지 않는다는 것을 의미합니다.그러나 다른 세션이 delete 문 범위에있을 행을 커밋 한 경우에 한합니다. 이 현상을 '팬텀 읽기'라고합니다.

그래서 요점은 레코드를 "실시간"테이블에 삽입하는 것입니다. 테스트 하네스에서 인서트는 일괄 처리로 mod(time_index,50)=0으로 커밋됩니다. copy_to_history_test()이 실행 중일 때 한 세션에서 커밋이 발생하면 레코드가 넘칠 수있는 구멍이 생깁니다. 아마도 프로덕션 환경에서 프로세스가 달라질 수 있습니다.

"문제를 해결하는 방법"

이 문제에 대한 표준 접근법은 SERIALIZABLE 격리 수준을 사용하는 것입니다. copy_to_history_test()을 실행하는 세션에 대해 이것을 설정하면 모든 명령문이 트랜잭션 기간 동안 동일한 데이터 상태를 사용하여 실행됩니다. 제공된 기록이 실시간 테이블에 삽입되는 경우에만이 접근법은 슬픔을주지 않습니다. (다른 프로세스 갱신 또는 다음 레코드를 삭제하면 당신은 더 큰 구조적 문제가있다.)

그래서 당신의 절차는 다음과 같아야한다 : 나는 또한 real_data_top_backup_date 테이블을 잠근

create or replace procedure copy_to_history_test is 
    d_top_backup_date timestamp(3); 
begin 

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- isolate all these statements 

    select top_backup_date into d_top_backup_date from real_data_top_backup_date 
    FOR UPDATE OF top_backup_date; -- lock the table to any other session 
    Insert Into history_data Select * From real_data where record_time <d_top_backup_date; 
    delete from real_data where record_time <d_top_backup_date; 
    Update real_data_top_backup_date Set top_backup_date=(d_top_backup_date+1/24/60); 

    commit; -- reverts the isolation level 

end copy_to_history_test; 

참고. 다중 사용자 환경에서는 충돌로 인한 트랜잭션 실패를 막기 위해 업데이트 레코드를 예약하는 것이 좋습니다.

설명서에는 격리 수준이 자세히 설명되어 있습니다. Find out more.

"데이터베이스를 디자인 할 때 더 좋은 점은 무엇입니까?"

글쎄요. 달성하려는 대상에 따라 다릅니다. "실시간"테이블의 요점은 무엇입니까? 1 분 동안 기록 만 남기고있는 것처럼 보입니다. 그래서 적절한 질문은, 왜 그냥 파티션 테이블에 삽입하지 않는 것입니까? 그렇게 많은 노력을 정당화하기 위해 두 개의 테이블을 갖는 것에서 얻을 수있는 가치는 무엇입니까?

"센서 데이터가 히스토리 데이터 테이블에만 삽입 된 경우 실시간 그래픽 표시는 데이터 테이블이 커지면 데이터 검색 속도가 느려질 수 있으므로 보장 할 수 없습니다."

입증 할만한 벤치마킹을 했습니까? 기본 키에 대해 인덱스 범위 스캔을 실행하면 1 분 분량의 데이터를 선택하는 것이 상당히 안정적이어야합니다. 당신의 마음은 두 개의 테이블 구조에 설정되어있는 경우

어쨌든, 난 당신이 INSERT 모든 기능을 사용하여 동시에 두 테이블에 레코드를 삽입 제안 :

insert all 
    into real_data values (....) 
    into history_data values (....) 
select .... 

다중 테이블 구문 삽입을 요구를 ... SELECT 구조 그러나 우리는 DUAL 또는 귀하의 유스 케이스에 맞는 어떤 것으로부터 로컬 변수를 선택할 수 있습니다. Find out more.

이미 history_data에 기록이 있으므로 copy_to_history_test()에서 이전을 제거하고 real_data 테이블에서 삭제를 수행 할 수 있습니다.

+0

답장을 보내 주셔서 감사합니다. 나는 당신의 제안에 따라 몇 가지 검사를 할 것입니다. – skyspeed

+0

두 테이블 (real_data 및 history_data 테이블)을 디자인하는 이유는 웹 클라이언트가 초당 실시간 데이터 그래픽을 표시하고 데이터베이스를 통해 기록 데이터를 쿼리해야하기 때문입니다. 센서 데이터가 히스토리 데이터 테이블에 삽입 된 경우에만 실시간 그래픽 디스플레이가 보장되지 않을 수 있습니다. 데이터 테이블이 커질수록 데이터 검색 속도가 느려지기 때문입니다. 따라서 두 개의 테이블 구조가 설계되었습니다. 실시간 테이블에는 약 54000 개의 데이터 만 포함됩니다. 따라서 실시간 데이터를 검색하는 것이 보장 될 수 있습니다. – skyspeed

+0

대단히 고마워! 나는 한 테이블에 삽입하기위한 데이터 검색 시간을 테스트하지 않았다. 데이터를 삽입하는 데이터가 천천히 될 수 있도록 색인이 테이블에 존재하기 때문에 천천히 될 것이라고 생각합니다. 그리고 나는 그것을 시도 할 것이다. 또한,이 시나리오에 대한 모든 기능을 삽입 해 주셔서 감사합니다. – skyspeed