Infra/리눅스

[timescaledb] 시계열 DB Memory 모니터링 (어디서 메모리를 잡아먹는지 분석하기..)

Nellie Kim 2025. 1. 3. 15:33
728x90

 

 

Kafka → Hadoop , timescaleDB, Redis, EMQX 스트리밍 부하 테스트를 하는데 유난히 timescaledb가 메모리를 많이 잡아먹었다.. 

초당 1만건 전송하여 확인한 기록이다.

 

내가 직접 만든 Grafana로 계속 모니터링을 하는 중인데,

Spark와 timescaledb에서 Ram을 많이 잡아먹고, OOM에러도 종종 일어난다.

 

저 RAM 부분이 노란색이 되면 뭔가 예민해진다. 저러고 OOM 터져서 죽은적이 너무 많음 ㅜㅜ 

뭔가 맘에 안드는 모니터링 상태

 

리눅스 서버에서 docker stats로 메모리 사용량도 같이 확인을 해봄. 

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     29.35MiB / 30.59GiB   0.09%     38.4MB / 1.1GB    148MB / 0B        38
4107b7119324   wowza            0.69%     1.82GiB / 30.59GiB    5.95%     803kB / 5.92MB    735MB / 1.93MB    1135
9e8c3d2b6438   redis-slave-3    6.19%     20.61MiB / 30.59GiB   0.07%     21.1GB / 20.1GB   6.93GB / 461GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      25.76%    6.946GiB / 30.59GiB   22.71%    26.6GB / 1.46GB   2.22GB / 72.5GB   11**
3b06e5f352cb   kafka2           2.71%     3.287GiB / 30.59GiB   10.74%    30GB / 121GB      14.8GB / 197GB    184
0e0d47ccd151   zookeeper2       0.19%     827.2MiB / 30.59GiB   2.64%     516MB / 763MB     568MB / 50.3MB    155
5db984913c9b   datanode1        26.76%    3.957GiB / 30.59GiB   12.93%    579GB / 325GB     19.9GB / 85.2GB   597

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     29.35MiB / 30.59GiB   0.09%     38.4MB / 1.1GB    148MB / 0B        38
4107b7119324   wowza            0.78%     1.82GiB / 30.59GiB    5.95%     803kB / 5.92MB    735MB / 1.93MB    1135
9e8c3d2b6438   redis-slave-3    7.34%     20.74MiB / 30.59GiB   0.07%     21.1GB / 20.1GB   6.93GB / 461GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      24.51%    6.949GiB / 30.59GiB   22.72%    26.6GB / 1.46GB   2.22GB / 72.5GB   11**
3b06e5f352cb   kafka2           3.57%     3.287GiB / 30.59GiB   10.75%    30GB / 121GB      14.8GB / 197GB    184
0e0d47ccd151   zookeeper2       0.22%     827.2MiB / 30.59GiB   2.64%     516MB / 763MB     568MB / 50.3MB    155
5db984913c9b   datanode1        254.77%   4.162GiB / 30.59GiB   13.61%    579GB / 325GB     19.9GB / 85.2GB   596

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     29.02MiB / 30.59GiB   0.09%     38.4MB / 1.1GB    148MB / 0B        38
4107b7119324   wowza            1.45%     1.82GiB / 30.59GiB    5.95%     803kB / 5.92MB    735MB / 1.93MB    1135
9e8c3d2b6438   redis-slave-3    6.94%     20.78MiB / 30.59GiB   0.07%     21.5GB / 20.5GB   6.93GB / 464GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      25.46%    7.227GiB / 30.59GiB   23.63%    27.4GB / 1.48GB   2.22GB / 74.8GB   11**
3b06e5f352cb   kafka2           2.93%     3.363GiB / 30.59GiB   11.00%    30.2GB / 121GB    14.8GB / 197GB    184
0e0d47ccd151   zookeeper2       0.20%     827.2MiB / 30.59GiB   2.64%     516MB / 763MB     568MB / 50.3MB    155
5db984913c9b   datanode1        0.37%     3.931GiB / 30.59GiB   12.85%    579GB / 325GB     19.9GB / 85.2GB   543

CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O        PIDS
a02167556cba   node_exporter   0.00%     31.12MiB / 30.59GiB   0.10%     38.7MB / 1.11GB   151MB / 0B       38
4107b7119324   wowza           1.52%     1.808GiB / 30.59GiB   5.91%     808kB / 5.95MB    736MB / 1.94MB   1135
9e8c3d2b6438   redis-slave-3   4.71%     20.65MiB / 30.59GiB   0.07%     27.1GB / 27GB     6.99GB / 490GB   7
**35511d11ca3c   timescaledb     24.73%    8.224GiB / 30.59GiB   26.89%    53.1GB / 2.32GB   2.99GB / 128GB   11**
3b06e5f352cb   kafka2          2.79%     3.396GiB / 30.59GiB   11.10%    36.4GB / 129GB    17.1GB / 199GB   184
0e0d47ccd151   zookeeper2      0.21%     795.9MiB / 30.59GiB   2.54%     524MB / 770MB     568MB / 50.3MB   155
5db984913c9b   datanode1       0.96%     8.905GiB / 30.59GiB   29.11%    614GB / 349GB     22GB / 90.3GB    775

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     28.73MiB / 30.59GiB   0.09%     38.8MB / 1.12GB   156MB / 0B        38
4107b7119324   wowza            1.06%     1.784GiB / 30.59GiB   5.83%     808kB / 5.95MB    736MB / 1.94MB    1135
9e8c3d2b6438   redis-slave-3    6.52%     19.71MiB / 30.59GiB   0.06%     35.3GB / 34.2GB   7.01GB / 524GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      24.32%    8.174GiB / 30.59GiB   26.72%    67.1GB / 2.78GB   4.04GB / 150GB    11**
3b06e5f352cb   kafka2           5.25%     4.813GiB / 30.59GiB   15.73%    40.4GB / 139GB    17.7GB / 199GB    184
0e0d47ccd151   zookeeper2       0.25%     729.2MiB / 30.59GiB   2.33%     526MB / 772MB     571MB / 50.3MB    155
5db984913c9b   datanode1        255.36%   7.367GiB / 30.59GiB   24.08%    621GB / 351GB     22.2GB / 91.2GB   780

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    2.48%     28.55MiB / 30.59GiB   0.09%     38.9MB / 1.12GB   156MB / 0B        38
4107b7119324   wowza            0.82%     1.766GiB / 30.59GiB   5.77%     811kB / 5.96MB    736MB / 1.95MB    1135
9e8c3d2b6438   redis-slave-3    5.40%     18.86MiB / 30.59GiB   0.06%     42.8GB / 40.7GB   7.02GB / 555GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      26.67%    8.148GiB / 30.59GiB   26.64%    80GB / 3.2GB      4.85GB / 173GB    11**
3b06e5f352cb   kafka2           3.52%     5.395GiB / 30.59GiB   17.64%    44GB / 149GB      18GB / 200GB      184
0e0d47ccd151   zookeeper2       0.07%     654.9MiB / 30.59GiB   2.09%     528MB / 773MB     589MB / 50.3MB    155
5db984913c9b   datanode1        0.47%     6.959GiB / 30.59GiB   22.75%    628GB / 352GB     22.2GB / 91.9GB   772

CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter   0.00%     29.14MiB / 30.59GiB   0.09%     38.9MB / 1.12GB   160MB / 0B        38
4107b7119324   wowza           1.41%     1.765GiB / 30.59GiB   5.77%     811kB / 5.96MB    736MB / 1.95MB    1135
9e8c3d2b6438   redis-slave-3   0.37%     18.62MiB / 30.59GiB   0.06%     44.2GB / 42GB     7.02GB / 561GB    7
**35511d11ca3c   timescaledb     24.86%    8.141GiB / 30.59GiB   26.61%    83GB / 3.3GB      5.18GB / 179GB    11**
3b06e5f352cb   kafka2          2.73%     5.608GiB / 30.59GiB   18.33%    44.7GB / 151GB    18.1GB / 200GB    184
0e0d47ccd151   zookeeper2      0.10%     650.2MiB / 30.59GiB   2.08%     528MB / 774MB     595MB / 50.3MB    155
5db984913c9b   datanode1       285.56%   6.961GiB / 30.59GiB   22.76%    629GB / 353GB     22.2GB / 92.3GB   779

 

멀쩡해 보이는데 timescaledb가 점점 메모리 사용량이 높아지며 신경쓰이게 했다.

어떤 놈이 메모리를 먹고있는건지 분석해봤다....

 

 

timescaledb 컨테이너 접속 및 db명 조회

[platform@CES ~]$ docker exec -it timescaledb bash
postgres=# \\l
                                                   List of databases
   Name    |  Owner   | Encoding | Locale Provider | Collate |  Ctype  | ICU Locale | ICU Rules |   Access privileges
-----------+----------+----------+-----------------+---------+---------+------------+-----------+-----------------------
 postgres  | postgres | UTF8     | libc            | C.UTF-8 | C.UTF-8 |            |           |
 template0 | postgres | UTF8     | libc            | C.UTF-8 | C.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |         |         |            |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | C.UTF-8 | C.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |         |         |            |           | postgres=CTc/postgres
(3 rows)

 

이건 별거 없고,,, 

 

postgres db 접속 및 현재 진행 중인 쿼리 확인

postgres=# psql -U postgres -d postgres

postgres=# SELECT pid, state, query
FROM pg_stat_activity
WHERE state != 'idle';
  pid  | state  |          query
-------+--------+--------------------------
 90418 | active | SELECT pid, state, query+
       |        | FROM pg_stat_activity   +
       |        | WHERE state != 'idle';
(1 row)

확인해보니 방금 작성한 쿼리말고는 실행되는 쿼리는 없다.

 

Autovacuum 및 Dead Tuple(삭제되었지만 정리되지 않은 데이터)을 확인

postgres=# SELECT relname, last_autovacuum, n_dead_tup
FROM pg_stat_user_tables
WHERE n_dead_tup > 0;

             relname             |        last_autovacuum        | n_dead_tup
---------------------------------+-------------------------------+------------
 _hyper_24_1447_chunk            | 2024-12-24 09:10:45.824003+00 |         17
 chunk_constraint                |                               |          4
 dimension_slice                 |                               |         55
 bgw_job_stat                    |                               |          4
 tb_realtime_vehicle_data_250103 | 2025-01-03 03:20:18.333171+00 |      31878
 chunk_index                     |                               |          4
 chunk                           |                               |          2
 _hyper_24_1376_chunk            | 2024-12-24 09:06:15.627968+00 |       1263
(8 rows)

 

뭔가 꺼림칙한 놈이 나옴.

죽은 튜플들을 정리를 안하고 있나? 싶어서 분석해 봄

 

    • Dead Tuple 개수가 상대적으로 많음:
      • _hyper_24_1376_chunk: 1,263개
      • _hyper_24_1447_chunk: 17개
    • Hypertable에서 생성된 Chunk 테이블로, TimescaleDB의 시계열 데이터를 저장
    • Autovacuum이 작동했지만 완전히 정리되지 않았을 가능성이 있음해당 결과는 pg_stat_user_tables에서 Autovacuum 및 Dead Tuple(삭제되었지만 정리되지 않은 데이터)을 확인한 것이다.
      1. 주요 내용
      • relname: 테이블 이름
      • last_autovacuum: 해당 테이블에서 Autovacuum이 마지막으로 실행된 시간
      • n_dead_tup: 테이블 내에서 삭제되었지만 VACUUM이 수행되지 않아 여전히 존재하는 레코드(Dead Tuple) 수

      2. 확인 사항
      • Dead Tuple 개수: 31,878개
      • 마지막 Autovacuum 실행 시간: 2025-01-03 03:20:18
      • 이 테이블은 최근 Autovacuum이 실행되었지만 여전히 Dead Tuple이 많다.
      _hyper_24_1376_chunk와 _hyper_24_1447_chunk

    3. 문제 원인
    • Dead Tuple이 많은 이유:
      1. 빈번한 데이터 변경 작업: 대량의 INSERT, UPDATE, DELETE 작업이 Dead Tuple을 생성.
      2. Autovacuum 설정 미흡: 기본 Autovacuum 설정으로 인해 정리가 지연될 수 있음.
      3. TimescaleDB Hypertable 특성: Chunk 테이블에 데이터가 분산되며, VACUUM이 자주 실행되지 않을 가능성.

    4. 해결 방법
  • Dead Tuple이 많은 테이블에 대해 VACUUM을 수동으로 실행하여 정리하자.
    1.  

Dead Tuple 비율 확인 (5% 미만이 정상, 5~20% 이면 관찰 필요, 20% 이상이면 비정상)

postgres=# SELECT relname,
       n_dead_tup,
       n_live_tup,
       ROUND(n_dead_tup::NUMERIC / (n_live_tup + n_dead_tup) * 100, 2) AS dead_tuple_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup > 0;
       relname        | n_dead_tup | n_live_tup | dead_tuple_ratio
----------------------+------------+------------+------------------
 _hyper_24_1447_chunk |         17 |       7198 |             0.24
 chunk_constraint     |          4 |        764 |             0.52
 dimension_slice      |         55 |        382 |            12.59
 bgw_job_stat         |          4 |          0 |           100.00
 chunk_index          |          4 |       1145 |             0.35
 chunk                |          2 |        382 |             0.52
 _hyper_24_1376_chunk |       1263 |       7196 |            14.93
(7 rows)

 

 

그렇게 심하게 잡아먹고 있진 않았다.. 

 

 

autovacuum 상태 확인

vi /home/postgres/pgdata/data/postgresql.conf

#------------------------------------------------------------------------------
# AUTOVACUUM
#------------------------------------------------------------------------------

#autovacuum = on                        # Enable autovacuum subprocess?  'on'
                                        # requires track_counts to also be on.
autovacuum_max_workers = 10             # max number of autovacuum subprocesses
                                        # (change requires restart)
autovacuum_naptime = 10         # time between autovacuum runs
**#autovacuum_vacuum_threshold = 50       # min number of row updates before # 🎯 현재 Dead Tuple 개수가 50개 이상이면 실행**
                                        # vacuum
#autovacuum_vacuum_insert_threshold = 1000      # min number of row inserts
                                        # before vacuum; -1 disables insert
                                        # vacuums
#autovacuum_analyze_threshold = 50      # min number of row updates before
**#autovacuum_vacuum_scale_factor = 0.2   # fraction of table size before vacuum  #🎯 테이블의 20% 이상의 Dead Tuple이 있으면 실행**

#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------

# - Memory -

shared_buffers = 7831MB                 # min 128kB  # **🎯** 전체 메모리의 25%~40% 권장
                                        # (change requires restart)
#huge_pages = try                       # on, off, or try
                                        # (change requires restart)
#huge_page_size = 0                     # zero for system default
                                        # (change requires restart)
#temp_buffers = 8MB                     # min 800kB
#max_prepared_transactions = 0          # zero disables the feature
                                        # (change requires restart)
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
# you actively intend to use prepared transactions.
work_mem = 2505kB                               # min 64kB  # **🎯** 대량 작업 시 증가 가능
#hash_mem_multiplier = 2.0              # 1-1000.0 multiplier on hash table work_mem
maintenance_work_mem = 2047MB           # min 1MB
#autovacuum_work_mem = -1               # min 1MB, or -1 to use maintenance_work_mem
#logical_decoding_work_mem = 64MB       # min 64kB

 

얘도 디폴트 값이었다.

 

 

압축 정책 확인

postgres=# SELECT * FROM timescaledb_information.compression_settings;
 hypertable_schema | hypertable_name | attname | segmentby_column_index | orderby_column_index | orderby_asc | orderby_nullsfirst
-------------------+-----------------+---------+------------------------+----------------------+-------------+--------------------
(0 rows)

postgres=#

 

현재 압축 정책이 없다. 압축 정책을 추가해줘야겠음 !!!!

 

  • 압축 정책 추가하면 메모리와 디스크 사용량 감소 가능!
    SELECT add_compression_policy('your_hypertable_name', INTERVAL '30 days');
    
    • 이 설정은 30일 이전의 데이터를 자동으로 압축해준다고 한다!

 

데이터베이스 백그라운드 라이터의 버퍼 관리 통계 확인

postgres=# SELECT
    buffers_checkpoint AS "Written by Checkpoints",
    buffers_clean AS "Clean Buffers",
    buffers_backend AS "Written by Backends"
FROM pg_stat_bgwriter;
 Written by Checkpoints | Clean Buffers | Written by Backends
------------------------+---------------+---------------------
                5415457 |          2693 |             3002002
(1 row)

  • 높은 Written by Checkpoints 값
  • 체크포인트 과정에서 메모리를 디스크로 내보낸 데이터가 많다는 것을 의미한다. 이는 트랜잭션 처리량이 높거나, shared_buffers가 충분히 활용되고 있음을 나타냄
  • 낮은 Clean Buffers 값
  • 클린 버퍼 작업이 상대적으로 적게 수행되었음을 나타냄. 이는 시스템이 클린 버퍼 정리에 대해 효율적으로 동작 중
  • Written by Backends 값
  • 클라이언트(백엔드)가 직접 디스크에 기록하는 데이터가 많음. 이는 버퍼가 꽉 차거나 충분히 캐시되지 않아 디스크 I/O가 빈번히 발생할 수 있음을 나타낸다고 한다. 

이것도 뭐 문제 없군 ,,,,,,

 

버퍼 사용 현황 확인

postgres=# SELECT
    sum(blks_read) AS "Blocks Read",
    sum(blks_hit) AS "Blocks Hit",
    sum(blks_hit) * 100.0 / (sum(blks_hit) + sum(blks_read)) AS "Cache Hit Ratio (%)"
FROM pg_stat_database;
 Blocks Read | Blocks Hit | Cache Hit Ratio (%)
-------------+------------+---------------------
      165898 |  413661536 | 99.9599113093116006
(1 row)

  1. 캐시 히트율이 매우 높음 (99.96%)
    • 대부분의 데이터 요청이 디스크가 아닌 메모리(shared_buffers)에서 처리되며 shared_buffers 설정이 적절하고, PostgreSQL이 메모리 캐시를 효율적으로 활용하고 있음을 나타냄
  2. Blocks Read 값이 낮음
    • 디스크 I/O 작업이 비교적 적게 발생
    • 메모리 캐시에 의해 대부분의 데이터 요청이 처리되고 있어 디스크 의존도가 낮은 이상적인 상태를 나타냄
  3. Blocks Hit 값이 높음
    • 메모리 캐시에서 읽은 데이터 블록이 많아, 성능이 최적화된 상태임을 보여줌
    • 애플리케이션 성능에 영향을 미치는 디스크 I/O 병목이 거의 없다. 

 

체크포인트 설정 확인 (디스크에 쓰는 작업)

postgres=# SHOW checkpoint_timeout;
 checkpoint_timeout
--------------------
 5min
(1 row)

postgres=# SHOW max_wal_size;
 max_wal_size
--------------
 1GB
(1 row)
  • checkpoint_timeout = 5min
    • PostgreSQL은 5분마다 체크포인트를 실행하도록 설정되어 있으며
    • 기본값(5분)인데, 데이터 변경 작업이 많은 워크로드에서는 체크포인트가 너무 자주 발생하여 디스크 I/O 부하를 증가시킬 수 있다. 적절히 조절하기.
  • max_wal_size = 1GB
    • 최대 WAL(Write Ahead Log) 크기가 1GB로 설정되어 있다. 
    • WAL이 1GB에 도달하면 강제로 체크포인트가 실행된다. 적절히 조절하자. 

 

 

 

 

큰 문제는 없어 보였으며 , 압축 정책만 조절하고 다시 반영해봐야겠다. 

스파크 OOM에러 , JVM OOM 에러 , 스택에러 등 메모리 문제가 자주 발생하니 메모리 문제에 예민해지는 듯 ㅠㅠ 

 

교훈 : 메모리 공부를 더 열심히 하자.