TIME-SERIES. ПАРТИЦИИ. NO INDEX. ПАРАЛЛЕЛЬНОЕ ЧТЕНИЕ.

добавлено: 22 ноя 15
понравилось:0
просмотров: 1669
комментов: 1

теги:

Автор: AlexeiF


Современный бизнес все больше и больше требует фиксации тех или иных событий в более точные моменты времени. К примеру в торговых операциях на биржах уже вводят time series, когда время транзакции учитывается с точностью до нано-секунд. Это приводит в драматическому увеличению количества данных ,с одной стороны, и более внимательному отношения к вопросу создания индексов на таблицу,с другой, и т.д.
Хотелось бы сделать важное дополнение и именно в начале. Уже в результате тестов на реальных данных оказалось, что ниже приведенный подход является единственно возможным решением для проблемы INSERT данных в таблицу при определенных условиях. Т.к. заполнение таблицы в ситуациях когда есть высокая скорость генерации данных возможно только при использовании multi-threaded с batch/bulk INSERT. При наличии индекса на колонку со временем, не важно это Java Time in nonosecond или ORACLE TIMESTAMP(9), будет сразу наблюдаться проблема с WAIT Events "enq: TX - index contention" и/или "buffer busy waits" на блоки хранящие данные для индекса.
SELECT event, object_name , count(*) Count_Waits, sum(TIME_WAITED)
  FROM DBA_HIST_ACTIVE_SESS_HISTORY a, dba_objects b
 WHERE TO_CHAR (SAMPLE_TIME, 'dd-mon-yyyy') = '&date'
 and CURRENT_OBJ#= object_id
AND TO_CHAR (SAMPLE_TIME, 'hh24:mi:ss') >= '&start_time'
AND TO_CHAR (SAMPLE_TIME, 'hh24:mi:ss') <= '&end_time'
AND USER_ID IN (SELECT DBA_USERS.USER_ID
                 FROM DBA_USERS
                 WHERE USERNAME = 'username')
and event is not null
and SQL_OPNAME ='INSERT'
group by  event, object_name 
order by 4 desc;

EVENT				OBJECT_NAME	COUNT(*)	SUM(TIME_WAITED)
buffer busy waits			T1_I2		6,155		205,783,582 
buffer busy waits			T2_I2		6,091		203,179,256
enq: TX - index contention		T2_I2		2,943		92,015,679
enq: TX - index contention		T1_I2		2,832		78,973,463
enq: CT - CTWR process start/stop	T2_I2		74		66,759,638
enq: CT - CTWR process start/stop	T1_I2		59		53,363,220
block change tracking buffer space	T1_I2		743		51,947,549
buffer busy waits			T3_I7		23,974		51,702,292
.....
T1_I2 -- Индекс на Java Time  ORACLE NUMBER(20)
T3_I7 -- Индекс на колонку хранящую время в формате ORACLE TIMESTAMP (9) 


Даная идея состоит в разделении времени, скажем с 7 утра до 16 на короткие промежутки времени , скажем 5 секунд, назначении каждому временному интервалу своей партиции по используя определенную хэш функцию, которую можно использовать и для чтения данных, причем делаю это параллельно. Т.к. зачастую при чтении данных в time series используют SQL для поиск данных за определенный промежуток времени. Для этого создается INDEX на колонку time series и оптимайзер используя RANGE INDEX SCAN ищет данные. Не забываем , что в этом случае ORACLE, по крайней мере, читает данным поблочно и последовательно и если промежуток времени относительно большой и данных много то операция по времени и ресурсам будет затратная.
В данном подходе INDEX не используется, что дает определенный дополнительные преимущества при заполнении таблицы, т.к. не надо перестраивать INDEX каждый раз, и чтение можно производить используя параллельные процессы и чтение само по себе будет много-блочным, т.к. будет чтение целой партиции, т.е. FULL TABLE SCAN.
Ниже приведена данная идея представленная графически.


Картинка с другого сайта.

Одна из возможных хэш функций , что может быть использованная
ROUND((T{now} - T{start_time})/((T{end_time} -T{start_time})/(Nbr_Part - 1)) +1
где
T{start_time} - Начало времени заполнения таблицы в секундах, к примеру 7:00=3600*7
T{end_time} - Конец времени заполнения таблицы секундах, к примеру 16:00=16*3600
T{now} - текущее время в секундах, к примеру 9:56:35.545624354= 9*3600+56*60+35
Nbr_Part - количество патриций, к примеры 6000

Очевидно , что используя данную фикцию можно легко определить номера партиций которые необходимо прочитать, чтобы получить заданный промежуток времени.

Ниже приведены результату теста:
1. Поиск данных используя ИНДЕКС
SQL> select *
  2   from SQH_Af1
  3   where CREAT_REC_TIME >= to_timestamp('9/8/2015 4:34:00.000000','mm/dd/yyyy HH24:MI:SS.FF')
  4   and  CREAT_REC_TIME < to_timestamp('9/8/2015 4:35:09.000000','mm/dd/yyyy HH24:MI:SS.FF') ;

1807513 rows selected.
Elapsed: 00:04:03.23


Statistics
----------------------------------------------------------
        133  recursive calls
          0  db block gets
    1479340  consistent gets
    1359494  physical reads
          0  redo size
  168128440  bytes sent via SQL*Net to client
    1325972  bytes received via SQL*Net from client
     120502  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
    1807513  rows processed

SQL>

Plan
SELECT STATEMENT  ALL_ROWS Cost: 180,617  Bytes: 154,445,652  Cardinality: 259,137  		
	2 TABLE ACCESS BY INDEX ROWID TABLE PERF01_SBT_JOBS.SQH_AF1 Cost: 180,617  Bytes: 154,445,652  Cardinality: 259,137  	
		1 INDEX RANGE SCAN INDEX AF_I1 Cost: 189  Cardinality: 259,137  
		
2. Поиск данных используя вышеизложенный подход

SQL> select *
  2   from SBT_QUOTE_HIST_Af2 a
  3   where PART_VALUE in (194);

153163 rows selected.

Elapsed: 00:00:05.15

Execution Plan
----------------------------------------------------------
Plan hash value: 2871801456

------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name               | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                    |   161K|    27M|   160   (7)| 00:00:02 |       |       |
|   1 |  PARTITION LIST SINGLE|                    |   161K|    27M|   160   (7)| 00:00:02 |   194 |   194 |
|   2 |   TABLE ACCESS FULL   | SBT_QUOTE_HIST_AF2 |   161K|    27M|   160   (7)| 00:00:02 |   194 |   194 |
------------------------------------------------------------------------------------------------------------



Statistics
----------------------------------------------------------
       4310  recursive calls
          1  db block gets
      35548  consistent gets
       2597  physical reads
          0  redo size
   14482002  bytes sent via SQL*Net to client
     112781  bytes received via SQL*Net from client
      10212  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
     153163  rows processed		

Необходимо отметить, что основная идея состоит в том что бы выбирать данные используя параллельные работы (PARALLEL THREADS), поэтому тут приведена статистика ТОЛЬКО для одной из работ (THREAD). Поэтому количество выбранных рядов разнится , 1807513 в первом тесте и 153163 во втором. Т.е. если будет использовано 12 параллельных работ, то будет выбрано тоже самое количество рядов при том же времени в 5 секунд , вместо 4 минут, используя индекс.

Комментарии




Необходимо войти на сайт, чтобы оставлять комментарии