Задний план

Индустрия ценных бумаг производит больше данных и часто считывает и записывает.

Возьмем, к примеру, биржевую торговлю, есть тысячи акций. В году около 240 торговых дней, время торговли с 10:00 до 16:00.

1. Требования к записи данных:

Запись данных в реальном времени и объединение в реальном времени по измерению запроса (например, данные второго уровня записываются в реальном времени. Данные минутного уровня объединяются в реальном времени).

Данные делятся на данные с разделением времени разной степени детализации. (Данные точны до секунд, минут, часов, дней)

2. Размеры данных:

Каждая акция содержит более 10 индикаторов, таких как время, количество заказов, объем, средняя цена, самая высокая цена, самая низкая цена, начальная цена и конечная цена.

3. Хранение базы данных:

Все данные должны храниться, начиная с листинга каждой акции, со средним значением не менее десяти лет.

4. Требования к запросу данных:

Запрос данных в течение определенного периода времени; например, запросите поминутные данные по акции на 1 июля 2016 г.

5. Требования к анализу:

Например, линейный регрессионный анализ.

PostgreSQL предоставляет множество функций, чтобы лучше соответствовать этим требованиям.

1. Блочные индексы BRIN. Данные вставляются во временном порядке, поэтому поле времени имеет сильную линейную корреляцию с хранилищем HEAP. Использование блочных индексов BRIN может экспоненциально уменьшить размер индекса, обеспечивая при этом превосходную производительность запросов диапазона.

2. JSON, HSTORE и тип массива. Вы можете использовать запись для хранения диапазона данных, например запись для хранения данных о разделении времени за день. Таким образом, вы можете уменьшить количество записей, сканируемых базой данных, и это ускорит сканирование данных диапазона.

3. Тип диапазона и основной индекс. При использовании второй функции тип диапазона может использоваться для указания времени начала этой записи. Почему бы не использовать два поля для его представления? Потому что диапазонный тип более эффективен, и вы можете обратиться к следующей статье.
Разговор о проблеме МЕЖДУ И и решении

4. Требования к анализу, такие как линейная регрессия, библиотека машинного обучения (MADlib), многомерный анализ и расширение языка (plpgsql, plpython, plr). Потребности отрасли ценных бумаг в анализе данных могут быть хорошо удовлетворены.

Проект решения для требований OLTP 1

Выбор числовых типов

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

1. числовой (131 072 знака до запятой и 16 383 знака после запятой)

2. float8 (15 допустимых цифр)
Numeric является типом переменной длины и при использовании имеет palloc (перенос памяти), который менее эффективен, чем float8.

3. тип расширения decimal128, который чаще используется в финансовой сфере. (И производительность, и диапазон представления данных могут соответствовать требованиям)
PostgreSQL decimal64 decimal128 Efficient Numerical Type Extension

Дизайн структуры таблицы

Таблицы данных второго уровня, требующие быстрой вставки

(Каждый запас имеет 5 184 миллиона записей каждые 10 лет, поэтому разделение не рассматривается)

create table tbl_sec_股票代码   -- 每只股票一张表,可以达到最高的查询效率,弊端是表多,需要动态拼接表名,变更表结构时,需要调整较多的表(可以使用继承来管理,减少管理复杂度)。  
(  
  id serial8 primary key,   -- 序列(可选字段)  
  时间 timestamp(0),        -- 值的生成时间  
  指标1 numeric,            -- 数据指标列  
  指标2 numeric,     
  ...  
  指标10 numeric  
);  
  
create index idx_xx on tbl_sec_股票代码 (时间);  
或  
create index idx_xx on tbl_sec_股票代码 using brin (时间);

Таблицы данных минутного уровня, требующие быстрого запроса по периоду времени (меньше данных, не требуется разделов)

create table tbl_min_股票代码  
(  
  id serial8 primary key,   -- 序列(可选字段)  
  时间 timestamp(0),        -- 值的生成时间  
  指标1 numeric,            -- 数据指标列  
  指标2 numeric,              
  ...  
  指标10 numeric  
);  
  
create index idx_xx on tbl_min_股票代码 (时间);  
或  
create index idx_xx on tbl_min_股票代码 using brin (时间);

Индекс БРИН. Когда требования к запросам диапазона высоки, индекс BRIN может значительно уменьшить размер индекса при одновременном повышении производительности вставки. Примеры индексов BRIN следующие:

Кластерное хранилище PostgreSQL и индекс BRIN — пояснения к поведению с высокой степенью параллелизма и сценарию запроса данных с большой пропускной способностью

Передовая технология PostgreSQL для IoT — создание индекса (индекса BRIN) в виде дроби от нормального размера

Новая функция PostgreSQL 9.5 — индекс BRIN (индекс диапазона блоков)

Клиент обычно имеет кеш данных, поэтому частота запросов не очень высока. Для сценариев, где частота запроса диапазона (массивных данных) очень высока, можно использовать следующие оптимальные методы:

Снова агрегируйте по периоду времени.

Более экстремальный метод оптимизации (необязательно)

Если вы хотите часто выполнять запросы диапазона (например, запрашивать сведения о дне на уровне минут) и использовать предыдущий дизайн, вам потребуется диапазон из 360 записей. Для большей эффективности запросов можно использовать агрегацию (например, агрегацию по дням), тогда при запросе данных за один день возвращается только одна запись.

Таблицы данных минутного уровня, требующие быстрого запроса по периоду времени (таблица агрегации в реальном времени и таблица агрегации с задержкой, уменьшающая количество мусора)

create table tbl_min_股票代码_实时聚合  
(  
  id serial8 primary key,   -- 序列(可选字段)  
  时间 timestamp(0),        -- 表示当前记录的写入时间  
  指标1 numeric,            -- 数据指标列  
  指标2 numeric,              
  ...  
  指标10 numeric  
);  
  
create index idx_xx on tbl_min_股票代码_实时聚合 (时间);  
  
create table tbl_min_股票代码_延时聚合  
(  
  id serial8 primary key,   -- 序列(可选字段)  
  时间区间 tsrange,         -- 表示当前记录的时间区间  
  指标 jsonb                -- 数据指标列  
);  
  
create index idx_xx on tbl_min_股票代码_延时聚合 using gist(时间区间);

Индикаторы могут быть представлены массивом jsonbhstorearray, и их всегда можно выбирать свободно. Возьмите jsonb в качестве примера:

{指标1: {时间点1:value, 时间点2:value, ......} , 指标2: {时间点1:value, 时间点2:value, ......}, ......}

tsrange поддерживает индекс gist. Данные с 01.01.2017 по 05.01.2017 можно быстро (за миллисекунды) найти в записи с помощью индекса. Пример выглядит следующим образом:

Разговор о проблеме МЕЖДУ И и решении

Другие проекты таблиц данных с разделением времени аналогичны дизайну минутного уровня.

Замедленный процесс агрегации

Процесс агрегации

Таблица второго уровня -› (агрегация в реальном времени) таблица минутного уровня агрегации в реальном времени -› (задержка агрегирования на один день) таблица минутного агрегирования с задержкой

Процесс запроса

(Запрос 1 минутной таблицы агрегации в реальном времени) union all (Запрос 2 минутной таблицы агрегации с отложенной обработкой)

Рекомендации по разделению таблиц

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

10.0 в настоящее время поддерживает разделы диапазона и списка, но хеш-разделы еще не объединены в главную ветку.

https://www.postgresql.org/docs/devel/static/sql-createtable.html

Однако pg_pathman уже поддерживает хеш-разделы, и пользователи могут свободно выбирать.

https://github.com/postgrespro/pg_pathman

Шардинг PostgreSQL 9.6 на основе FDW и pg_pathman

Эффективная реализация секционированных таблиц в PostgreSQL 9.5+ — pg_pathman

Дизайн бизнес-логики

1. Вставьте

Данные второго уровня каждой акции вставляются в режиме реального времени.

2. Слияние в реальном времени

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

Например, после достижения точки 2017–01–01 11:00:00 данные второго уровня от 2017–01–01 10:59:00 до 2017–01–01 10:59:59 объединяются в сводная таблица в режиме реального времени на минутном уровне.

3. Отложенное слияние (необязательно)

Отложенное слияние, которое объединяет результаты слияния в реальном времени в одну запись по интервалу.

Например, каждая акция имеет 360 минутных записей в день, которые объединяются в одну запись в день и представлены jsonb.

Целью слияния является сокращение количества сканируемых записей для запросов диапазона, например возврат только одной записи в день.

4. Запрос

Точный запрос, указывающий время запроса записи.

Запрос диапазона, который указывает диапазон времени для запроса записей в этом диапазоне.

Стресс-тестирование

Стресс-тестирование ввода данных второго уровня

Создайте тестовую базовую таблицу

create table tbl_sec  
(  
  crt_time timestamp(0),    
  c1 float8,    
  c2 float8,     
  c3 float8,   
  c4 float8,   
  c5 float8,   
  c6 float8,   
  c7 float8,   
  c8 float8,   
  c9 float8,   
  c10 float8  
);  
  
create index idx_tbl_sec_time on tbl_sec using brin (crt_time) with (pages_per_range=1);

Создайте 3000 биржевых таблиц

do language plpgsql 
$$
  
declare  
  sql text;  
begin  
  for i in 1..3000 loop  
    sql := format('create table %I (like tbl_sec including all)', 'tbl_sec_'||lpad(i::text, 6, '0') );  
    execute sql;  
  end loop;  
end;  
$$
;

Тест производительности вставки данных для каждой акции

vi test.sql  
  
\set c1 random(1,1000)  
\set c2 random(1,1000)  
\set c3 random(1,1000)  
\set c4 random(1,1000)  
\set c5 random(1,1000)  
\set c6 random(1,1000)  
\set c7 random(1,1000)  
\set c8 random(1,1000)  
\set c9 random(1,1000)  
\set c10 random(1,1000)  
insert into tbl_sec_000001 values (now(),:c1,:c2,:c3,:c4,:c5,:c6,:c7,:c8,:c9,:c10);

Задержка вставки составляет 0,043 миллисекунды.

pgbench -M prepared -n -r -P 1 -f ./test.sql -c 1 -j 1 -T 10  
  
tps = 21714.908797 (including connections establishing)  
tps = 21719.144013 (excluding connections establishing)  
script statistics:  
 - statement latencies in milliseconds:  
         0.000  \set c1 random(1,1000)  
         0.000  \set c2 random(1,1000)  
         0.000  \set c3 random(1,1000)  
         0.000  \set c4 random(1,1000)  
         0.000  \set c5 random(1,1000)  
         0.000  \set c6 random(1,1000)  
         0.000  \set c7 random(1,1000)  
         0.000  \set c8 random(1,1000)  
         0.000  \set c9 random(1,1000)  
         0.000  \set c10 random(1,1000)  
         0.043  insert into tbl_sec_000001 values (now(),:c1,:c2,:c3,:c4,:c5,:c6,:c7,:c8,:c9,:c10);  
  
postgres=# select * from tbl_sec_000001 limit 10;  
      crt_time       | c1  | c2  | c3  | c4  | c5  | c6  | c7  | c8  | c9  | c10   
---------------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----  
 2017-04-17 14:14:00 | 480 |  60 | 918 | 563 | 168 | 457 | 129 | 887 | 870 | 457  
 2017-04-17 14:14:00 | 189 | 894 | 707 | 598 | 701 | 418 | 191 | 287 | 688 | 668  
 2017-04-17 14:14:00 | 492 | 423 | 972 | 101 |  28 | 847 | 919 | 698 | 594 | 430  
 2017-04-17 14:14:00 | 781 |  38 | 816 | 467 |  96 |   2 | 762 |   8 | 271 | 577  
 2017-04-17 14:14:00 | 225 | 126 | 828 | 158 | 447 |  12 | 691 | 693 | 272 | 995  
 2017-04-17 14:14:00 | 125 |  18 | 589 | 472 | 424 | 884 | 177 | 754 | 463 | 468  
 2017-04-17 14:14:00 | 156 | 412 | 784 |  40 | 126 | 100 | 727 | 851 |  80 | 513  
 2017-04-17 14:14:00 | 320 |  75 | 485 |  10 | 481 | 592 | 594 | 227 | 658 | 810  
 2017-04-17 14:14:00 | 678 | 199 | 155 | 325 | 212 | 977 | 170 | 696 | 895 | 679  
 2017-04-17 14:14:00 | 413 | 512 | 535 | 319 |  99 | 520 |  39 | 502 | 207 | 160  
(10 rows)

Производительность запроса диапазона временных меток второго уровня

Для одной акции введите 10 миллионов единиц данных второго уровня. Таким образом, 3000 акций содержат около 30 миллиардов тестовых данных.

postgres=# insert into tbl_sec_000001 select now()+(i||' sec')::interval , 1,1,1,1,1,1,1,1,1,1 from generate_series(1,10000000) t(i);  
INSERT 0 10000000  
  
postgres=# select * from tbl_sec_000001 limit 10;  
      crt_time       | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10   
---------------------+----+----+----+----+----+----+----+----+----+-----  
 2017-04-17 14:20:17 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:18 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:19 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:20 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:21 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:22 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:23 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:24 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:25 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
 2017-04-17 14:20:26 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1  
(10 rows)

Размер индекса и размер таблицы составляют 4808 МБ и 1116 КБ соответственно.

Индекс BRIN экономит много места.

public | tbl_sec_000001   | table | postgres | 1116 MB    |   
  
  
 public | tbl_sec_000001_crt_time_idx       | index | postgres | tbl_sec_000001   | 4808 kB    |

Запрос данных за 1 минуту (60 штук) и 2 часа (7200 штук) занимает около 47 миллисекунд.

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl_sec_000001 where crt_time between '2017-04-17 14:21:00' and '2017-04-17 14:21:59';  
                                                                                        QUERY PLAN                                                                                         
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tbl_sec_000001  (cost=777.40..778.72 rows=1 width=88) (actual time=46.612..46.628 rows=60 loops=1)
   Output: crt_time, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
   Recheck Cond: ((tbl_sec_000001.crt_time >= '2017-04-17 14:21:00'::timestamp without time zone) AND (tbl_sec_000001.crt_time <= '2017-04-17 14:21:59'::timestamp without time zone))
   Rows Removed by Index Recheck: 80
   Heap Blocks: lossy=2
   Buffers: shared hit=809
   ->  Bitmap Index Scan on idx_tbl_sec_000001_time  (cost=0.00..777.40 rows=1 width=0) (actual time=46.597..46.597 rows=20 loops=1)
         Index Cond: ((tbl_sec_000001.crt_time >= '2017-04-17 14:21:00'::timestamp without time zone) AND (tbl_sec_000001.crt_time <= '2017-04-17 14:21:59'::timestamp without time zone))
         Buffers: shared hit=807
 Planning time: 0.077 ms
 Execution time: 46.664 ms
(11 rows)
postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl_sec_000001 where crt_time between '2017-04-17 14:21:00' and '2017-04-17 16:20:59';  
                                                                                        QUERY PLAN                                                                                         
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.tbl_sec_000001  (cost=834.57..7710.56 rows=5578 width=88) (actual time=46.194..47.437 rows=7200 loops=1)
   Output: crt_time, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
   Recheck Cond: ((tbl_sec_000001.crt_time >= '2017-04-17 14:21:00'::timestamp without time zone) AND (tbl_sec_000001.crt_time <= '2017-04-17 16:20:59'::timestamp without time zone))
   Rows Removed by Index Recheck: 80
   Heap Blocks: lossy=104
   Buffers: shared hit=911
   ->  Bitmap Index Scan on idx_tbl_sec_000001_time  (cost=0.00..833.18 rows=5578 width=0) (actual time=46.182..46.182 rows=1040 loops=1)
         Index Cond: ((tbl_sec_000001.crt_time >= '2017-04-17 14:21:00'::timestamp without time zone) AND (tbl_sec_000001.crt_time <= '2017-04-17 16:20:59'::timestamp without time zone))
         Buffers: shared hit=807
 Planning time: 0.060 ms
 Execution time: 47.862 ms
(11 rows)

Сравнение B-дерева и индекса Брина по занимаемой площади и эффективности

Индекс BRIN — это индекс блочного уровня, поэтому он занимает немного места и очень подходит для сценариев, в которых значения полей хорошо коррелируют с физическим порядком хранения таблицы HEAP.

Однако BRIN не является точным индексом, поэтому эффективность запроса может быть не такой высокой, как у B-Tree.

Пользователи могут использовать различные методы индексации в соответствии с реальными бизнес-требованиями.

Сравнение выглядит следующим образом:

postgres=# drop index tbl_sec_000001_crt_time_idx;
cDROP INDEX
postgres=# create index tbl_sec_000001_crt_time_idx on tbl_sec_000001(crt_time);
CREATE INDEX
 public | tbl_sec_000001_crt_time_idx       | index | postgres | tbl_sec_000001   | 214 MB     |

1. Сравнение занимаемой площади

Количество записейTableB-TreeBRIN10 миллионов1116 МБ214 МБ4,8 МБ

2. Сравнение эффективности запросов

Количество возвращенных записейB-TreeBRIN60 штук0,04 мс46,7 мс7 200 штук1,96 мс47,9 мс

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl_sec_000001 where crt_time between '2017-04-17 14:21:00' and '2017-04-17 14:21:59';  
                                                                                     QUERY PLAN                                                                                      
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using tbl_sec_000001_crt_time_idx on public.tbl_sec_000001  (cost=0.43..4.11 rows=54 width=88) (actual time=0.007..0.022 rows=60 loops=1)
   Output: crt_time, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
   Index Cond: ((tbl_sec_000001.crt_time >= '2017-04-17 14:21:00'::timestamp without time zone) AND (tbl_sec_000001.crt_time <= '2017-04-17 14:21:59'::timestamp without time zone))
   Buffers: shared hit=5
 Planning time: 0.095 ms
 Execution time: 0.040 ms
(6 rows)
postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl_sec_000001 where crt_time between '2017-04-17 14:21:00' and '2017-04-17 16:20:59'; 
                                                                                     QUERY PLAN                                                                                      
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using tbl_sec_000001_crt_time_idx on public.tbl_sec_000001  (cost=0.43..252.61 rows=6609 width=88) (actual time=0.022..1.539 rows=7200 loops=1)
   Output: crt_time, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
   Index Cond: ((tbl_sec_000001.crt_time >= '2017-04-17 14:21:00'::timestamp without time zone) AND (tbl_sec_000001.crt_time <= '2017-04-17 16:20:59'::timestamp without time zone))
   Buffers: shared hit=126
 Planning time: 0.119 ms
 Execution time: 1.957 ms
(6 rows)

Проект решения для требований OLTP 2

Бессхемные решения

Каждая акция имеет таблицу с суффиксом кода акции. Вы можете объединить имена таблиц на стороне приложения или использовать FUNCTION для инкапсуляции объединенного кода в базу данных.

Время торговли на рынке обычно составляет 4 часа в день, и рынок не работает в праздничные дни. Если акции создают одну запись в секунду, будет 4 6060 525 = 3,744 миллиона записей за один день и только 37,44 миллиона записей за 10 лет.

Поэтому, если вы разделяете по акции, измерение времени совершенно не нужно.

1. Базовая таблица выглядит следующим образом:

create table tbl 
(  
  gid text,  
  crt_time timestamp,    
  c1 float8,    
  c2 float8,     
  c3 float8,   
  c4 float8,   
  c5 float8,   
  c6 float8,   
  c7 float8,   
  c8 float8,   
  c9 float8,   
  c10 float8  
) PARTITION BY list (gid)
;  
  
-- create index idx_tbl_sec_time on tbl_sec using btree (crt_time);

2. Оценка данных

-- CREATE TABLE tbl_000000 PARTITION OF tbl FOR VALUES IN ('000000') PARTITION BY RANGE (crt_time);
-- 4*60*60*52*5=374.4万,10年才3744万条记录。

3. Создайте 3000 биржевых таблиц

do language plpgsql 
$$
  
declare  
  sql text;  
begin  
  for i in 1..3000 loop  
    sql := format('create table %I PARTITION OF tbl for values in (%L)', 'tbl_'||lpad(i::text, 6, '0'),  lpad(i::text, 6, '0'));  
    execute sql;  
    sql := format('create index %I on %I (crt_time)', 'idx_tbl_'||lpad(i::text, 6, '0')||'_1', 'tbl_'||lpad(i::text, 6, '0'));  
    execute sql;
  end loop;  
end;  
$$
;

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

create or replace function ins_tbl(
  i_gid text, 
  i_crt_time timestamp, 
  i_c1 float8,    
  i_c2 float8,     
  i_c3 float8,   
  i_c4 float8,   
  i_c5 float8,   
  i_c6 float8,   
  i_c7 float8,   
  i_c8 float8,   
  i_c9 float8,   
  i_c10 float8 
) returns void as 
$$
declare
begin
  execute format('insert into %I values (%L, %L, %L, %L, %L, %L, %L, %L, %L, %L, %L, %L)', 'tbl_'||i_gid, i_gid, i_crt_time, i_c1, i_c2, i_c3, i_c4, i_c5, i_c6, i_c7, i_c8, i_c9, i_c10);
end;
$$
 language plpgsql strict;

5. Нагрузочное тестирование показывает, что скорость записи составляет около 220 000/с, что полностью соответствует бизнес-требованиям.

vi test.sql
\set id random(1,3000)
select ins_tbl(lpad(:id, 6, '0'), now()::timestamp, 1::float8,1::float8,1::float8,1::float8,1::float8,1::float8,1::float8,1::float8,1::float8,1::float8);

nohup pgbench -M prepared -n -r -P 1 -f ./test.sql -c 64 -j 64 -T 15000 > /dev/null 2>&1 &

6. Производительность запросов

Мы не рекомендуем использовать секционированную таблицу PG для прямого запроса, а время выполнения и планирования немного больше.

postgres=# explain (analyze) select * from tbl where gid='000001' and crt_time between '2017-07-17 15:17:42.336503' and '2017-07-17 15:38:08.981317';
                                                                                QUERY PLAN                                                                                 
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Append  (cost=0.29..20.47 rows=286 width=95) (actual time=0.017..0.033 rows=30 loops=1)
   ->  Index Scan using idx_tbl_000001_1 on tbl_000001  (cost=0.29..20.47 rows=286 width=95) (actual time=0.016..0.030 rows=30 loops=1)
         Index Cond: ((crt_time >= '2017-07-17 15:17:42.336503'::timestamp without time zone) AND (crt_time <= '2017-07-17 15:38:08.981317'::timestamp without time zone))
         Filter: (gid = '000001'::text)
 Planning time: 141.484 ms
 Execution time: 0.116 ms
(6 rows)

Запрашивать разделы напрямую очень быстро.

postgres=# explain (analyze) select * from tbl_000001 where gid='000001' and crt_time between '2017-07-17 15:17:42.336503' and '2017-07-17 15:38:08.981317';
                                                                             QUERY PLAN                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using idx_tbl_000001_1 on tbl_000001  (cost=0.29..21.60 rows=290 width=95) (actual time=0.009..0.016 rows=30 loops=1)
   Index Cond: ((crt_time >= '2017-07-17 15:17:42.336503'::timestamp without time zone) AND (crt_time <= '2017-07-17 15:38:08.981317'::timestamp without time zone))
   Filter: (gid = '000001'::text)
 Planning time: 0.199 ms
 Execution time: 0.036 ms
(5 rows)

Код запроса инкапсулируется, и имя таблицы автоматически объединяется для запроса в соответствии с введенным кодом запаса.

create or replace function sel_tbl(                                                         
  i_gid text, 
  begin_crt_time timestamp, 
  end_crt_time timestamp 
) returns setof tbl as 
$$
declare
begin
  return query execute format('select * from %I where crt_time between %L and %L', 'tbl_'||i_gid, begin_crt_time, end_crt_time);
end;
$$
 language plpgsql strict;

Производительность использования UDF для запроса данных очень высока и занимает 0,298 миллисекунды.

postgres=# select * from sel_tbl('000001', '2017-07-17 15:17:42.336503', '2017-07-17 15:38:08.981317');
  gid   |          crt_time          | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 
--------+----------------------------+----+----+----+----+----+----+----+----+----+-----
 000001 | 2017-07-17 15:17:42.336503 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:47.083672 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:53.633412 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:54.092175 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:55.452835 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:55.55255  |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:59.689178 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:04.051391 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:11.255866 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:12.217447 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:12.456304 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:19.640116 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:22.022434 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:27.141344 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:33.709304 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:34.285168 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:52.501981 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:19:21.891636 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:19:36.091745 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:19:37.481345 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:37:43.894333 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:37:44.921234 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:37:45.317703 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.799772 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.897194 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.938029 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.953457 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.954542 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.959182 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.981317 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
(30 rows)
postgres=# explain (analyze,verbose,timing,costs,buffers) select * from sel_tbl('000001', '2017-07-17 15:17:42.336503', '2017-07-17 15:38:08.981317');
                                                                           QUERY PLAN                                                                           
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on postgres.sel_tbl  (cost=0.25..10.25 rows=1000 width=120) (actual time=0.277..0.279 rows=30 loops=1)
   Output: gid, crt_time, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
   Function Call: sel_tbl('000001'::text, '2017-07-17 15:17:42.336503'::timestamp without time zone, '2017-07-17 15:38:08.981317'::timestamp without time zone)
   Buffers: shared hit=9
 Planning time: 0.030 ms
 Execution time: 0.298 ms
(6 rows)

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

select * from tbl where gid in ('000001','002999','001888') and crt_time between ? and ?

UDF также можно использовать для удовлетворения запросов пользователей.

create or replace function sel_tbl(                                                         
  i_gid text[], 
  begin_crt_time timestamp, 
  end_crt_time timestamp 
) returns setof tbl as 
$$
declare
  v_gid text;
begin
  foreach v_gid in array i_gid
  loop
    return query execute format('select * from %I where crt_time between %L and %L', 'tbl_'||v_gid, begin_crt_time, end_crt_time);
  end loop;
end;
$$
 language plpgsql strict;

Производительность также превосходна и занимает 0,662 миллисекунды.

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from sel_tbl(array['000001','002999','001888'], '2017-07-17 15:17:42.336503', '2017-07-17 15:38:08.981317');
                                                                                    QUERY PLAN                                                                                    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on postgres.sel_tbl  (cost=0.25..10.25 rows=1000 width=120) (actual time=0.632..0.638 rows=86 loops=1)
   Output: gid, crt_time, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
   Function Call: sel_tbl('{000001,002999,001888}'::text[], '2017-07-17 15:17:42.336503'::timestamp without time zone, '2017-07-17 15:38:08.981317'::timestamp without time zone)
   Buffers: shared hit=30
 Planning time: 0.048 ms
 Execution time: 0.662 ms
(6 rows)
postgres=# select * from sel_tbl(array['000001','002999','001888'], '2017-07-17 15:17:42.336503', '2017-07-17 15:38:08.981317');
  gid   |          crt_time          | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 
--------+----------------------------+----+----+----+----+----+----+----+----+----+-----
 000001 | 2017-07-17 15:17:42.336503 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:47.083672 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:53.633412 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:54.092175 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:55.452835 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:55.55255  |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:17:59.689178 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:04.051391 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:11.255866 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:12.217447 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:12.456304 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:19.640116 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:22.022434 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:27.141344 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:33.709304 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:34.285168 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:18:52.501981 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:19:21.891636 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:19:36.091745 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:19:37.481345 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 000001 | 2017-07-17 15:37:43.894333 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:37:44.921234 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:37:45.317703 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.799772 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.897194 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.938029 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.953457 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.954542 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.959182 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 000001 | 2017-07-17 15:38:08.981317 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:18:04.116816 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:08.720714 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:11.021059 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:13.17118  |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:19.349304 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:20.525734 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:38.480529 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:42.462302 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:18:42.81403  |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:19:11.211989 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:19:14.861736 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:19:20.240403 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:19:32.747798 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:19:35.191558 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:29:58.143158 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 002999 | 2017-07-17 15:38:08.800312 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.801949 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.824119 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.835612 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.860339 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.918502 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.9365   |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.944578 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.951397 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.963564 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.980547 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 002999 | 2017-07-17 15:38:08.980656 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:17:42.353113 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:17:43.15402  |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:17:46.316366 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:17:51.982603 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:07.32869  |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:16.798675 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:36.947117 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:39.629393 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:42.56243  |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:48.777822 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:50.850458 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:51.693084 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:18:55.660418 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:19:07.735869 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:19:32.331744 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:19:34.409026 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:29:56.634906 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |    
 001888 | 2017-07-17 15:38:08.749017 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.801824 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.829437 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.855895 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.857959 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.858431 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.882241 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.930556 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.938661 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.942828 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.9459   |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
 001888 | 2017-07-17 15:38:08.966001 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |   1
(86 rows)

7. Для каждой акции записывается десять миллионов записей, всего 30 миллиардов тестовых данных.

postgres=# select count(*) from tbl_000001;
  count   
----------
 10135995
(1 row)

Тест запрашивает записи акции за определенный период времени, а время отклика находится в пределах 1 мс.

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from sel_tbl('000001', '2017-07-17 15:17:42.336503', '2017-07-17 15:38:08.981317');
                                                                           QUERY PLAN                                                                           
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on postgres.sel_tbl  (cost=0.25..10.25 rows=1000 width=120) (actual time=0.303..0.305 rows=30 loops=1)
   Output: gid, crt_time, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
   Function Call: sel_tbl('000001'::text, '2017-07-17 15:17:42.336503'::timestamp without time zone, '2017-07-17 15:38:08.981317'::timestamp without time zone)
   Buffers: shared hit=12
 Planning time: 0.040 ms
 Execution time: 0.328 ms
(6 rows)

8. Реализовать бессхемное решение до конца — Бессхемная реализация PostgreSQL (коллекция классов Mongodb)

Обзор преимуществ бессхемного решения

Решение без схемы решает проблему, связанную с масштабированием ввода-вывода HEAP PAGE (каждый датчик активен, поэтому проверка нескольких записей идентификатора датчика фактически требует сканирования столько же HEAP PAGE, сколько возвращается записей). При использовании бессхемного решения данные идентификатора датчика перераспределяются и сохраняются, что напрямую решает проблему увеличения ввода-вывода.

Используя индекс BRIN, можно решить проблему с большим индексом BTREE (но при этом можно продолжать использовать индекс BTREE).

Стресс-тестирование минутных запросов данных

Это похоже на требование запроса для данных второго уровня, поэтому тест игнорируется.

Проект решения для требований OLAP 1

Для сценариев с ценными бумагами в дополнение к общим требованиям к запросу данных, вставке и слиянию существуют более строгие требования к анализу данных.

PostgreSQL предоставляет множество функций анализа:

1. Агрегация

Агрегатная функция PostgreSQL 1: универсальные агрегатные функции

Агрегатная функция PostgreSQL 2: агрегатные функции для статистики

Агрегатная функция PostgreSQL 3: агрегатные функции для упорядоченного набора

Агрегатная функция PostgreSQL 4: агрегатные функции гипотетического множества

2. Запрос окна

https://www.postgresql.org/docs/9.6/static/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS

https://www.postgresql.org/docs/9.6/static/functions-window.html

3. Многомерный анализ

Новая функция PostgreSQL 9.5 — поддержка GROUPING SETS, CUBE и ROLLUP.

4. MADlib (библиотека машинного обучения)

Посмотрите, что MADlib может сделать с помощью изображения

Примеры анализа данных

Прогноз данных линейной регрессии

Использование линейного регрессионного анализа в PostgreSQL — реализация прогнозирования данных

Линейная регрессия PostgreSQL — прогноз цены акций 1

Использование линейного регрессионного анализа в PostgreSQL для прогнозирования — пример 2 для прогнозирования цены закрытия акции в ближайшие несколько дней

Множественная линейная регрессия PostgreSQL — 1 MADLib, установленный в PostgreSQL 9.2

Множественная линейная регрессия PostgreSQL — фондовый прогноз 2

Другие расширенные функции

Другие функции, предоставляемые PostgreSQL, которые могут использоваться в сфере ценных бумаг и финансов:

В дополнение к некоторым базовым функциям онлайновой базы данных транзакций, упомянутым ранее, PostgreSQL также обладает мощными аналитическими возможностями, такими как возможность потоковой передачи данных. Он также может эффективно обрабатывать большие объемы данных (включая хранение столбцов, многоядерные параллельные вычисления ЦП, JIT, использование инструкций векторных вычислений ЦП и подключаемые модули данных временных рядов).

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

Приложение PostgreSQL StreamCompute

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

Секреты механизмов ускорения анализа — LLVM, Column-Store, Multi-Core Parallel, Operator Multiplex Integrate — Open the Treasure Chest of PostgreSQL

Ускорение OLAP в 10 раз с помощью расширения векторизованного выполнения PostgreSQL (плиточная реализация)

PostgreSQL также имеет соответствующие подключаемые модули, которые могут эффективно обрабатывать данные временных рядов.

Каковы характеристики базы данных временных рядов? Введение в базу данных временных рядов TimescaleDB

Резюме

1. При расчете с использованием данных о запасах за десять лет получается около 30 миллиардов единиц данных.

Мы достигли времени ответа на запрос около 0,04 мс для любой акции в любой период времени с помощью этого решения и скорости ответа около 0,2 мс с помощью бессхемной инкапсуляции UDF.

С такими показателями производительности вам все еще нужна база данных временных рядов? PostgreSQL сама по себе является базой данных временных рядов верхнего уровня.

2. Скорость записи стоковых данных составляет около 220 000 строк/с. Это намного превышает требования бизнеса.

3. Требования к анализу. JIT PostgreSQL и многоядерные параллельные вычисления обеспечивают надежную поддержку вычислительной мощности для бизнеса AP. Многие пользователи уже используют параллельные вычисления PG для решения смешанных задач OLTP+OLAP.

4. Для масштабирования ввода-вывода это также можно решить с помощью метода, аналогичного таблице агрегации (сканирование только индекса), который не влияет на бизнес. См.:
PostgreSQL IoT Интернет транспортных средств (IoV) — Траектория в реальном времени — Практика путешествий 2

Ссылка: https://www.alibabacloud.com/blog/postgresql-time-series-best-practices-stock-exchange-system-database_594815?spm=a2c41.12889560.0.0