본문 바로가기
연구노트

Row Generation 성능 비교 - SingleStore, Oracle, MySQL, PostgreSQL

by 에이플랫폼 [Team SingleStore Korea] 2024. 1. 12.

🎯 개요

이전 포스트에서 Row Generation 방법을 소개해 드렸습니다.

이번 포스트에서는 각 DB 마다 Row Generation 성능을 간단하게 비교하도록 하겠습니다.

비교 테스트에 사용한 장비는 8vCPU, 16GB 의 사양으로 Hands-On 테스트에 사용했던 장비를 그대로 사용하였고 여기에 Oracle XE 21c 를 추가로 설치했습니다.

🎯 Oracle

Oracle XE 21c는 SGA+PGA 합쳐서 2GB 를 넘지 못하도록 제약이 걸려 있습니다. 따라서 불가피하게 PGA가 3000MB 로 설정되어 있는 Autonomous Database 에서도 같이 테스트를 진행했습니다.

Oracle XE 21c - 8cpu, pga_aggregate_target=512m

1억건은 메모리 부족으로 에러가 발생합니다.

에러가 안나는 최대 row 갯수는 220만건 정도로 0.5 초 정도 소요됩니다.

Recursive With 구문으로 천만건 생성하는데 2분 7초 정도 소요되었습니다.

 

SQL> select count(*) from (select rownum from dual connect by level <= 100000000);
select count(*) from (select rownum from dual connect by level <= 100000000)
                                         *
ERROR at line 1:
ORA-30009: Not enough memory for CONNECT BY operation

SQL> select count(*) from (select rownum from dual connect by level <= 2200000);

  COUNT(*)
----------
   2200000

Elapsed: 00:00:00.54

SQL> with t(n) as (
  2    select 1 as n from dual
  3    union all
  4    select n+1 as n from t where n < 10000000
  5  )
  6  select count(*) from t;

  COUNT(*)
----------
  10000000

Elapsed: 00:02:07.19
 

Oracle Autonomous Database 19c - pga_aggregate_target=3000m

Oracle Autonomous Database 에서는 재귀 쿼리로 천만건 생성하는데 3.21 초 정도 소요되었습니다.

Recursive With 구문으로는 천만건 생성에 1분 40초 정도 소요되었습니다.

 

SQL> show parameter pga_aggregate_target;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
pga_aggregate_target                 big integer 3000M

SQL> select count(*) from (select rownum from dual connect by level <= 10000000);

  COUNT(*)
----------
  10000000

Elapsed: 00:00:03.21

SQL> with t(n) as (
  2    select 1 as n from dual
  3    union all
  4    select n+1 as n from t where n < 10000000
  5  )
  6  select count(*) from t;

  COUNT(*)
----------
  10000000

Elapsed: 00:01:40.69
 

🎯 MySQL

MySQL 에서는 cte max recursive depth 를 기본 1000 에서 1억으로 우선 늘립니다.

Recursive CTE 를 이용해서 천만건 row 를 생성하는데 1.73초 수행되었습니다.

Recursive CTE 를 이용해서 1억건 row 를 생성하는데 1분 3초가 소요되었습니다.

mysql> set @@cte_max_recursion_depth=100000000;
Query OK, 0 rows affected (0.00 sec)

mysql> with recursive t(n) as (
    ->   select 1 as n
    ->   union all
    ->   select n+1 as n from t where n < 10000000
    -> )
    -> select count(*) from t;
+----------+
| count(*) |
+----------+
| 10000000 |
+----------+
1 row in set (1.73 sec)

mysql> with recursive t(n) as (
    ->   select 1 as n
    ->   union all
    ->   select n+1 as n from t where n < 100000000
    -> )
    -> select count(*) from t;
+-----------+
| count(*)  |
+-----------+
| 100000000 |
+-----------+
1 row in set (1 min 3.85 sec)

 

🎯 PostgreSQL

PostgreSQL 은 generate_series 를 이용해 천만건 생성하는데 1.5초가 소요되었습니다.

동일한 방법으로 1억건 생성할 때는 15.4초가 걸렸습니다.

Recursive CTE 를 이용해서 천만건 row 를 생성하는데는 4.6초, 1억건 생성할 경우는 47초가 소요되었습니다.

 

postgres=# select count(*) from generate_series(1, 10000000);
  count
----------
 10000000
(1 row)

Time: 1583.327 ms (00:01.583)

postgres=# select count(*) from generate_series(1, 100000000);
   count
-----------
 100000000
(1 row)

Time: 15474.199 ms (00:15.474)

postgres=# with recursive t(n) as (
postgres(#   select 1 as n
postgres(#   union all
postgres(#   select n+1 as n from t where n < 10000000
postgres(# )
postgres-# select count(*) from t;
  count
----------
 10000000
(1 row)

Time: 4682.084 ms (00:04.682)

postgres=# with recursive t(n) as (
postgres(#   select 1 as n
postgres(#   union all
postgres(#   select n+1 as n from t where n < 100000000
postgres(# )
postgres-# select count(*) from t;
   count
-----------
 100000000
(1 row)

Time: 47140.642 ms (00:47.141)
 

🎯 SingleStore

이제 SingleStore 차례입니다. SingleStore 는 바로 1억건부터 시작해서 1억건 단위로 올려보겠습니다.

1억건에 0.86초 ~ 6억건에 6.4초정도 소요되었습니다.

7억건 생성시 메모리 부족 에러가 발생했습니다.

 

singlestore> select count(*) from gen_rows(100000000);
+-----------+
| count(*)  |
+-----------+
| 100000000 |
+-----------+
1 row in set (0.86 sec)


singlestore> select count(*) from gen_rows(200000000);
+-----------+
| count(*)  |
+-----------+
| 200000000 |
+-----------+
1 row in set (1.85 sec)

singlestore> select count(*) from gen_rows(300000000);
+-----------+
| count(*)  |
+-----------+
| 300000000 |
+-----------+
1 row in set (2.60 sec)

singlestore> select count(*) from gen_rows(400000000);
+-----------+
| count(*)  |
+-----------+
| 400000000 |
+-----------+
1 row in set (4.03 sec)

singlestore> select count(*) from gen_rows(500000000);
+-----------+
| count(*)  |
+-----------+
| 500000000 |
+-----------+
1 row in set (4.44 sec)

singlestore> select count(*) from gen_rows(600000000);
+-----------+
| count(*)  |
+-----------+
| 600000000 |
+-----------+
1 row in set (6.40 sec)

singlestore> select count(*) from gen_rows(700000000);
ERROR 1712 (HY000): Memory used by MemSQL (8328.62 Mb) has reached the 'maximum_memory' setting (14141 Mb) on this node. 
Possible causes include (1) available query execution memory has been used up for table memory (in use table memory: 8058.62 Mb) and (2) the query is large and complex and requires more query execution memory than is available (in use query execution memory 0.00 Mb). 
See https://docs.memsql.com/troubleshooting/latest/memory-errors for additional information.
 

🎯 마무리

실제 운영환경에서 임의의 Row 를 몇억건씩 만들어 사용할 일은 그리 많지 않을 것입니다. 하지만 대량의 데이터를 만들고 수정하고 삭제하는 테스트는 매우 빈번하게 일어납니다.

SingleStore 는 다른 데이터베이스보다 수십배 많은 데이터를 처리하면서도 빠른 응답이 가능해 이런 테스트마저도 쾌적하게 진행할 수 있습니다.