728x90

 

JMeter와 함께하는 부하테스트,, 노트북이 버티지 못할까 조마조마한 마음으로 진행을 해보았습니다,,

 

다행이 docker 내부의 mysql이 CPU를 100% 넘기면서까지 노력해줘서 잘 마무리를 했습니다,,🌹

 

간단하게, JMeter 설치와 부하테스트 결과를 정리해 보겠습니다,,

 

기본 환경
- OS: Windows 11 Home
- Language: Java
- DB: MySQL, Redis
- IDE: IntelliJ

 

 

0. 사전 지식 🥸

기준
Spring MVC Spring WebFlux
처리 방식 동기/블로킹 비동기/논블로킹
서버 지원 Tomcat (Servlet 기반) Netty, Undertow, Tomcat 등 지원
성능 (대규모 트래픽) 스레드 수에 따라 성능 제한 높은 동시성 처리 및 확장성 우수
디버깅 용이 어려움
주요 사용 사례 전통적인 웹 앱, 관리 시스템 실시간 데이터, 채팅, IoT, 스트리밍 서비스

 

 

1. JMeter 설치

The Apache JMeter™ application is open source software, a 100% pure Java application designed to load test functional behavior and measure performance.
It was originally designed for testing Web Applications but has since expanded to other test functions.
Apache JMeter™ 애플리케이션은 기능 동작을 로드하고 성능을 측정하도록 설계된 100% 순수 Java 애플리케이션인 오픈 소스 소프트웨어입니다.
원래 웹 애플리케이션을 테스트하기 위해 설계되었지만 이후 다른 테스트 기능으로 확장되었습니다.

⭐ JMeter는 순수 자바 애플리케이션으로, Java 8 문법을 내부적으로 사용하기 때문에 자바 8 버전 이상이 설치되어 있어야합니다.

 

저도 정리할 수 있지만,, 이미 잘 정리된 글이 있다면,, 공유하는 게 더 효율적이라고 생각합니다^,,^

저도 따라했는데, 성공했습니다 ^,,^

https://developer-jinnie.tistory.com/105

 

[프로젝트] Windows 환경에서 JMeter 설치 및 부하 테스트 하기

부하 테스트를 위해 JMeter 설치가 필요했던 나 ,, 윈도우에서 JMeter 설치를 비롯해 테스트를 해야 하는 분들이 이 포스팅 하나만 읽어도 쉽게 가능했으면 하는 마음으로 포스팅한다. 설치1. JMeter

developer-jinnie.tistory.com

 

 

2. JMeter Plugin 설치

Transaction per Seconds와 Response Times Over Time을 함께 측정하기 위해 플러그인도 함께 설치합니다.

플러그인 설치 방법은 아래 블로그를 참조하시면 됩니다^,,^

https://blog.naver.com/ka28/222492575671

 

[JMETER] 부하 테스트로 TPS 측정하기(JMeter 플러그인 사용)

JMeter 플러그인 다운 및 적용하기 아래 하단의 사이트를 들어간다. https://jmeter-plugins.org/wiki/T...

blog.naver.com

 

 

3. 테스트할 API 준비

저는 MVC와 WebFlux에 대해 크게 3가지씩 테스트 할 예정입니다.

  * 단순 String을 반환하는 API

  * RDB를 조회하는 API

  * Cache를 활용한 API

 

Case MVC Webflux
단순 String 반환 1 1
RDB 데이터 조회 3 3
Caching 활용 2 2

 

대략 결과는 Webflux가 MVC보다 빠르되, String → Caching → RDB 순으로 빠르지 않을까,, 라고 예상해 봅니다.

 

 

4. Jmeter 설정

(좌) Thread Group 설정 / (우) Http Reqeust 설정

Thread Groups

Test Plan 우클릭 → Add → Threads (Users) → Thread Group

* Numbers of Threads (users): 동시에 테스트를 수행할 가상의 사용자 수

* Ramp-up period (seconds): 모든 스레드(사용자)가 가동될 때까지 걸리는 시간(초), 부하를 점진적으로 증가시키고 싶을 때 사용

  * Ramp-up Period ÷ Number of Threads = 각 스레드의 시작 간격

* Loop Count: 각 가상 사용자가 요청을 몇 번 반복할지 설정

 

 

Http Reqeust

Thread Group 우클릭 → Add → Sampler → Http Request

* Protocol, Server name or IP, Port Number, Http Request 입력

 

 

5. 테스트 결과 해석

Summary Report

Http Request 우클릭 → Add → Listener → Summary Report

* #Samples: 테스트 동안 발생한 HTTP 요청의 총 개수

* Average: 평균 응답 시간(ms)

* Min / Max: 최소/최대 응답 시간(ms)

* Std. Dev. (표준 편차): 응답 시간의 변동성

* Error %: 에러율

* Throughput: 초당 처리된 요청 수

* Received KB/sec / Sent KB/sec: 초당 수신/송신된 데이터 양, 서버와의 데이터 전송 효율성을 나타냄

* Avg. Bytes: 요청당 평균 전송된 데이터 크기

 

Transaction Per Second

초당 처리된 트랜잭션(요청)의 수

  * TPS가 높으면 서버가 빠르게 요청을 처리한다는 의미

  * 테스트 중 TPS가 급격히 하락하면 서버 과부하 가능성이 존재

 

Response Times Over Time

시간 흐름에 따른 응답 시간 변화

  * 초기에는 빠르다가 부하가 누적될수록 응답 시간이 증가하는지 확인

  * 스파이크(급격한 증가) 구간을 보면 병목 현상이나 과부하 시점을 파악할 수 있음

 

 

🍀 부하 테스트 전략

  1. Baseline 테스트: 적은 사용자 수로 정상 동작 확인
  2. 부하 증가 테스트: Ramp-up을 활용해 점진적 부하 증가
  3. 스트레스 테스트: Ramp-up 없이 대량의 사용자로 한계 테스트
  4. 안정성 테스트: 오랜 시간 동안 고정된 부하로 서비스 안정성 확인

 

 

6. 테스트 결과 정리

✅단순 String 반환

💡단순 String 반환 시, Summary Report 기준 Webflux가 Avg는 낮고, Throughput은 더 높음

 

RDB 조회

💡RDB 조회 시, Summary Report 기준 Webflux가 Avg는 낮고, Throughput은 더 높음

 

RDB 조회 및 Caching 활용

💡Caching 활용 시, Summary Report 기준 Avg, Throughput 모두 비슷

 

⭐캐시 처리 시 WebFlux의 이점이 미미한 이유:

Redis 자체가 매우 빠름 → 논블로킹 I/O의 효과가 뚜렷하게 나타나기 위해선 대기 시간이 길어야 함

 

 

+ 단순 RDB 조회 시, MySQL은 두뇌 풀 가동 모습,,🤖

 

 

 

📑

참고 자료

Chat GPT

https://developer-jinnie.tistory.com/105

 

[프로젝트] Windows 환경에서 JMeter 설치 및 부하 테스트 하기

부하 테스트를 위해 JMeter 설치가 필요했던 나 ,, 윈도우에서 JMeter 설치를 비롯해 테스트를 해야 하는 분들이 이 포스팅 하나만 읽어도 쉽게 가능했으면 하는 마음으로 포스팅한다. 설치1. JMeter

developer-jinnie.tistory.com

https://jmeter.apache.org/

 

Apache JMeter - Apache JMeter™

Apache JMeter™ The Apache JMeter™ application is open source software, a 100% pure Java application designed to load test functional behavior and measure performance. It was originally designed for testing Web Applications but has since expanded to oth

jmeter.apache.org

https://jaehoney.tistory.com/224

 

Apache JMeter란 무엇인가? (+ 사용 방법 with 성능 및 부하 테스트)

Apache JMeter Apache JMeter는 서버가 제공하는 성능 및 부하를 측정할 수 있는 테스트 도구이다. JMeter는 순수 Java 애플리케이션 오픈소스이며 서버나 네트워크 또는 개체에 대해 과부하를 시뮬레이션

jaehoney.tistory.com

https://blog.naver.com/ka28/222492575671

 

[JMETER] 부하 테스트로 TPS 측정하기(JMeter 플러그인 사용)

JMeter 플러그인 다운 및 적용하기 아래 하단의 사이트를 들어간다. https://jmeter-plugins.org/wiki/T...

blog.naver.com

 

728x90
728x90

 

글을 쓰기 전에 항상 고민합니다,, 오늘은 어떤 잔망루피를 데려올까,,🥸

 

오늘은 R2DBC에서 쿼리를 작성하는 2가지 방법을 작성할 예정이기에 잔망루피 2명이 있는 짤을 데려왔습니다,,

 

잔망 루피 2명,, (+ 뒷모습의 잔망루피는 덤,,)과 함께하는 Custom Query를 정리해보고자 합니다,,

 

1. gradle.build

외부 라이브러리를 쓰기 위해 종속성을 추가해줍니다.

dependencies{
  	// r2dbc 설정
	implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc' // JDBC 대신 비동기 방식으로 DB와 통신할 수 있도록 지원
	implementation 'io.asyncer:r2dbc-mysql:1.0.2' // R2DBC를 이용해 MySQL과 연결하기 위한 드라이버
	// 비동기/리액티브 방식으로 MySQL과 데이터를 주고받을 수 있도록 하는 설정
	// 리액티브(Reactive) 방식: 비동기(Asynchronous) + 논블로킹(Non-blocking) 프로그래밍 모델
}

 

 

2. application.yml

DB 설정을 추가해줍니다.

사용 DB: MySQL🐬

spring:
  r2dbc:
    url: r2dbc:mysql://localhost:3307/database_name
    username: username
    password: passworad

 

 

 

3. R2dbcConfig.java

R2DBC 설정 파일을 만들어줍니다.

@Component
@RequiredArgsConstructor
@Slf4j
@EnableR2dbcRepositories // 리액티브 리포지토리(ReactiveCrudRepository 등)를 자동으로 스캔하고, R2DBC를 통해 사용할 수 있도록 설정
@EnableR2dbcAuditing // R2DBC 엔터티의 자동 감사(Auditing) 기능을 활성화, createdAt, updatedAt 같은 값을 자동으로 입력
public class R2dbcConfig {
  private final DatabaseClient databaseClient;
  // MySQL 연결 시, 객체 생성은 하지만 Connection까지는 보장하지 않음
  // application.properties 파일에서 비밀번호 틀려도 Spring 연결 O
 }

 

 

4-1. Custom Repository 만들기 - @Query 버전

public interface UserR2dbcRepository extends ReactiveCrudRepository<User, Long> {

  @Modifying
  // @Query: 읽기 전용(SELECT 쿼리)으로 동작
  // CREATE, UPDATE, DELETE 같은 데이터를 변경하는 쿼리를 실행하려면 @Modifying을 추가
  @Query("DELETE FROM users WHERE name = :name")
  Mono<Integer> deleteByName(String name);
}

 

 

4-2. Custom Repository 만들기 - DatabaseClient

@Repository
@RequiredArgsConstructor
public class PostR2dbcCustomRepositoryImpl implements PostR2dbcCustomRepository {
  private final DatabaseClient databaseClient;

  @Override
  public Flux<Post> findAllByUserId(Long userId) {
    String sql = """
                SELECT p.id as pid, p.user_id as userId, p.title, p.content, p.created_at as pcreatedAt, p.updated_at as pupdatedAt,
                       u.id as uid, u.name, u.email, u.created_at as ucreatedAt, u.updated_at as uupdatedAt
                FROM posts p
                LEFT JOIN users u ON p.user_id = u.id
                WHERE p.user_id = :userId
        """;

    return databaseClient.sql(sql)
                         .bind("userId", userId)
                         // :userId에 매핑 -> 오류나는 부분 IntelliJ 문제
                         .fetch()
                         // fetch().all()을 쓰면 Flux, fetch().one()을 쓰면 Mono를 반환
                         .all()
                         .map(row -> Post.builder()
                                         .id((Long) row.get("pid"))
                                         .userId((Long) row.get("userId"))
                                         .title((String) row.get("title"))
                                         .content((String) row.get("content"))
                                         .user(User.builder()
                                                   .id((Long) row.get("uid"))
                                                   .name((String) row.get("name"))
                                                   .email((String) row.get("email"))
                                                   .createdAt(((ZonedDateTime) row.get("ucreatedAt")).toLocalDateTime())
                                                   .updatedAt(((ZonedDateTime) row.get("uupdatedAt")).toLocalDateTime())
                                                   .build())
                                         .createdAt(((ZonedDateTime) row.get("pcreatedAt")).toLocalDateTime())
                                         .updatedAt(((ZonedDateTime) row.get("pupdatedAt")).toLocalDateTime())
                                         .build());
  }
}

 

 

5. Custom Repository별 장단점 정리

비교 항목 ReactiveCrudRepository DatabaseClient
특징 ReactiveCrudRepository 기본 메서드
(save, findById, deleteById 등) 사용 가능
SQL 직접 작성
유연성 @Query로 커스텀 쿼리 가능
복잡한 JOIN 처리 어려움
복잡한 JOIN 및 다양한 SQL 작성 가능
활용 기본적인 CRUD 및 간단한 조건 검색에 적합 직접 최적화가 가능하므로
복잡한 비즈니스 로직이 필요한 경우 적합
동적 쿼리 불가능(동적 쿼리 작성 시, Querydsl 필요) 가능

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

 

728x90
728x90

 

살면서 도플갱어를 만난다면 죽는다는 무서운 말이 있지만,,

Redis는 도플갱어를 만나도 죽지 않습니다,,

 

나와 동일한 또 다른 나,,?

오히려 좋아,,🍀

 

replication의 개념과 docker-compose를 이용한 replica 생성 방법까지 간단히 정리해 보고자 합니다.

 

1. Replication의 정의

At the base of Redis replication (excluding the high availability features provided as an additional layer by Redis Cluster or Redis Sentinel) there is a leader follower (master-replica) replication that is simple to use and configure.
It allows replica Redis instances to be exact copies of master instances.
The replica will automatically reconnect to the master every time the link breaks, and will attempt to be an exact copy of it regardless of what happens to the master.
Redis 복제의 기본(Redis Cluster 또는 Redis Sentinel에서 추가 계층으로 제공하는 고가용성 기능 제외)에는 사용 및 구성이 간단한 리더 팔로워(마스터-복제본) 복제가 있습니다.
이를 통해 복제 Redis 인스턴스는 마스터 인스턴스의 정확한 복사본이 될 수 있습니다.
복제본은 링크가 끊어질 때마다 자동으로 마스터에 다시 연결되며, 마스터에 어떤 일이 발생하더라도 마스터의 정확한 복사본이 되려고 시도합니다.

 

 

2. 왜 Replication이 필요한가

안정성

Replication을 사용하면 마스터 서버에서 발생한 데이터 변경이 슬레이브 서버로 자동으로 복제됩니다. 이는 데이터 손실을 방지하고, 마스터 서버가 장애를 겪었을 때 슬레이브 서버를 통해 데이터가 유지될 수 있도록 합니다.

가용성

Redis의 Replication을 통해 여러 서버에 데이터를 복제함으로써, 마스터 서버가 다운되더라도 슬레이브 서버를 통해 읽기 작업을 처리할 수 있습니다.

읽기 부하 분산

마스터 서버에 대한 읽기 요청을 슬레이브 서버로 분산시킬 수 있습니다. 여러 개의 슬레이브 서버를 운영하여 읽기 부하를 분산시킴으로써 성능을 개선할 수 있습니다. 이를 통해 대규모 트래픽을 처리할 수도 있습니다.

 

 

3. Dokcer-Compose 파일 준비!

networks: #네트워크 설정으로, 서비스 간 통신을 가능하게 함
  replica: #네트워크 이름
    driver: bridge #네트워크 드라이버로 bridge를 사용, 같은 호스트 내에서 여러 컨테이너가 서로 통신할 수 있도록 설정

services: #services 아래에 실행할 컨테이너를 정의
  redis: #service 이름
    container_name: redis #container 이름
    image: redis:6.2 #docker hub 내 이미지(repository:verson_tag)
    ports: #port 매핑(호스트 6379 포트:컨테이너 6379 포트)
      - 6379:6379
    networks: 
      - replica 
      #연결할 네트워크
      #docker-compose.yml에서 networks를 정의하고, 각 컨테이너가 이를 사용하도록 설정하면 같은 네트워크를 공유하는 컨테이너끼리 이름(container_name)을 통해 접근할 수 있음
      #예: redis://redis:6379
    restart: always #컨테이너 중단 시, 동작 설정

  imreplica:
    container_name: imreplica
    image: redis:6.2
    ports:
      - 6378:6379
      #동일한 호스트에서 여러 개의 Redis 인스턴스를 실행하려면 각 인스턴스가 사용하는 포트를 다르게 설정해야 함
    networks:
      - replica
    volumes:
    #호스트(내 컴퓨터)와 Docker 컨테이너의 특정 폴더를 연결하는 설정
    #컨테이너 내부에서 생성된 파일이 호스트에 저장되거나, 호스트의 파일이 컨테이너에서 사용될 수 있게 됨
    #데이터를 영구적으로 저장하거나 공유하기 위해 사용
    #volumes를 통해 데이터를 외부(호스트)에 저장하면 컨테이너를 삭제해도 데이터가 보존
      - ./conf:/usr/local/etc/redis/
    command: redis-server /usr/local/etc/redis/redis.conf
    #컨테이너가 실행될 때 수행할 명령어
    #redis-server: Redis Server 실행
    #Redis 서버는 기본적으로 설정 파일인 redis.conf를 통해 설정을 읽고 실행
    restart: always

  imduplica:
    container_name: imduplica
    image: redis:6.2
    ports:
      - 6377:6379
    networks:
      - replica
    volumes:
      - ./conf:/usr/local/etc/redis/
    command: redis-server /usr/local/etc/redis/redis.conf
    restart: always

언제 그 기술이 익숙해졌다는 생각이 드시나요,,☺️

전 강의와 다르게 이름을 지을 때 입니다,,☺️

imreplica,,🤖 imduplica,,🤖

 

❓순간 왜 로컬 포트를 도커에 연결하고 있나,,라는 생각이 들 때, 읽어보면 좋은 글

Docker 컨테이너에 호스트 포트를 연결하는 이유는 외부 시스템(클라이언트나 다른 서버 등)이 컨테이너 내부의 서비스에 접근할 수 있게 만들기 위함입니다.
Docker는 컨테이너화된 애플리케이션을 실행하는 환경으로, 기본적으로 컨테이너는 호스트 시스템과 격리된 네트워크를 사용합니다.
따라서 호스트 포트를 연결해야만 호스트 시스템 외부에서 컨테이너의 서비스를 이용할 수 있습니다.

 

 

4. Replication을 위한 conf 파일 준비!

replicaof redis 6379
#Redis의 복제(replication) 기능을 설정
#replicaof: 현재 Redis 인스턴스를 복제본(replica)으로 설정하는데 사용
#지정된 마스터 Redis 서버에서 데이터를 복제하도록 설정
#redis: 복제할 마스터 Redis 서버의 서비스 이름(호스트명 또는 IP 주소)
#6379: 마스터 Redis 서버가 사용하는 포트 번호

 

🚨 [대기열 시스템] Docker Compose로 Grafana와 Prometheus로 모니터링 시스템 구축하기

이 때는 분명됐는데, 이번부터는 docker-compose.yml 파일에 version을 추가하면 오류는 아니고, 경고가 발생합니다,,🚨

the attribute `version` is obsolete, 
it will be ignored, 
please remove it to avoid potential confusion

top-level에서 version을 지정하는 환경이 obsolete되었기에 이제는 쓰지 않길 원한다는 것입니다.

version을 제거하면 경고 문구 없이 잘 동작합니다.

 

➕ Deprecated와 Obsolete

Deprecated
- 여전히 작동하며 당분간 지원 대상이나 향후 지원이 중단될 예정
Obsolete
- 더 이상 지원되지 않거나 사용되지 않으며, 대체 기술이나 방법으로 완전히 전환된 상태

 

 

5. Replication 테스트!

# 원본(Master) 인스턴스
docker-compose exec redis redis-cli

info replication
# Replication
# role:master, 현재 Redis 인스턴스의 역할
# connected_slaves:2, 현재 이 마스터 Redis 인스턴스와 연결된 슬레이브(복제본) 인스턴스의 수
# slave0:ip=172.18.0.3,port=6379,state=online,offset=941,lag=0
# offset: 마스터와 슬레이브 간 동기화 상태를 나타내는 데이터의 위치
# 마스터와 슬레이브의 오프셋이 같으면 동기화가 완료된 상태
# lag: 슬레이브가 마스터와 동기화할 때의 지연 시간
# slave1:ip=172.18.0.2,port=6379,state=online,offset=941,lag=0
# master_failover_state:no-failover
# 마스터가 장애가 발생했을 때 슬레이브 중 하나를 새로운 마스터로 승격시키는 과정

set key1 1
# 복제본에서도 실시간으로 데이터 확인 가능
# 복제본(Slave, imreplica&imduplica) 인스턴스
docker-compose exec imreplica redis-cli
set key1 1
# READONLY You can't write against a read only replica.

 

💡 Master Redis 인스턴스 Stop 후, Start할 경우,

 

  • 기존 마스터가 중단된 후, 슬레이브 중 하나가 failover로 인해 마스터로 승격
  • 기존 마스터를 다시 시작해도, 기존 마스터는 슬레이브 역할로 동작하며 데이터 일관성을 유지
    • Master를 대리했던 인스턴스가 새로운 정보를 Write 했을 가능성이 존재

 

# Stop 전
# Replication
role:master
connected_slaves:2
slave0:ip=172.18.0.3,port=6379,state=online,offset=1000,lag=0
slave1:ip=172.18.0.2,port=6379,state=online,offset=1000,lag=0


# Stop 후, Start 시
# Replication
role:slave
master_host:172.18.0.3
master_port:6379
master_link_status:up
slave_repl_offset:0

 

 

📑

참고 자료

Chat GPT

https://redis.io/docs/latest/operate/oss_and_stack/management/replication/

 

Redis replication

How Redis supports high availability and failover with replication

redis.io

https://teveloper.tistory.com/82

 

docker-compose.yaml: `version` is obsolete 이슈

이슈 발생사내 프로젝트에 docker-compose를 사용해 docker container환경을 구축하던 중 다음과 같은 경고 메세지가 발생했다.    docker-compose의 버전관련 문제인듯 하여 찾아보니 깃허브 이슈에서 다

teveloper.tistory.com

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

728x90
728x90

 

오늘 강의를 들으면서 모르는 단어의 연속이었지만.. 암 오케..

 

옆에 맛있는 빵을 두고 먹으면서 이겨내 보았습니다.

 

이겨낸 김에 적어보는 Grafana와 Prometheus로 Redis-exporter 모니터링 해보는 방법..

 

 

1. docker 대신 docker-compose 사용 준비

Docker
단일 컨테이너를 관리하는 도구로, 애플리케이션과 그 애플리케이션이 실행되는 데 필요한 모든 것을 컨테이너라는 단위로 패키징하고 실행

 

Docker Compose
여러 컨테이너를 한 번에 관리할 수 있는 도구로, 애플리케이션이 여러 컨테이너로 구성된 경우(예: 웹 서버, 데이터베이스, 캐시), 이를 하나의 설정 파일(docker-compose.yml)로 정의하고 실행

 

Redis, Redis Exporter, Prometheus, Grafana로 구성된 총 4개의 컨테이너를 관리할 예정이므로 Docker Compose를 사용할 예정입니다.

 

Docker Desktop을 설치했을 경우, Docker 및 Docker Compose 모두 설치되므로 따로 설치 작업은 필요 없습니다.

 

 

2. docker-compose 설정 파일 준비

docker-compose.yaml
여러 개의 Docker 컨테이너를 정의하고 관리하기 위해 사용되는 설정 파일

⭐💡학습 목표: Redis 데이터를 기반으로 Prometheus가 모니터링 데이터를 수집하고, Grafana가 이를 시각화 💡⭐

전체 데이터 흐름

  1. Redis
    • 데이터베이스로 동작하며, 현재 상태와 메트릭(예: 메모리 사용량, 키 개수 등)을 제공
  2. Redis Exporter
    • Redis의 상태 정보를 수집하여 Prometheus가 이해할 수 있는 형식으로 변환
    • Redis와 연결(여기선 REDIS_ADDR=redis://redis:6379)하고 메트릭 데이터를 Prometheus로 전달
  3. Prometheus
    • Redis Exporter가 제공한 메트릭 데이터를 주기적으로 수집하고 저장
    • 데이터베이스 역할을 하며, 요청 시 데이터를 반환
  4. Grafana
    • Prometheus에 저장된 데이터를 가져와 사용자 정의 대시보드를 통해 시각화
    • 사용자는 웹 인터페이스로 그래프, 차트 등을 확인할 수 있음
version: '3.8' #Docker Compose 파일 버전
networks: #네트워크 설정으로, 서비스 간 통신을 가능하게 함
  monitor:
    driver: bridge #네트워크 드라이버로 bridge를 사용

services: #services 아래에 실행할 컨테이너를 정의
  redis:
    container_name: redis #container 이름 지정
    image: redis:6.2 #Image 지정
    ports: #port 매핑
      - 6379:6379
    networks:
      - monitor
      #연결할 네트워크
      #Docker 네트워크는 컨테이너 간의 통신을 가능하게 하는 가상의 네트워크
      #docker-compose.yml에서 networks를 정의하고, 각 컨테이너가 이를 사용하도록 설정하면 같은 네트워크를 공유하는 컨테이너끼리 이름(container_name)을 통해 접근할 수 있음
      #예: redis://redis:6379
    restart: #컨테이너 중단 시, 동작 설정
      always

  prometheus: #모니터링 및 경보 시스템
    image: prom/prometheus:latest
    user: root
    volumes: 
    #호스트(내 컴퓨터)와 Docker 컨테이너의 특정 폴더를 연결하는 설정
    #컨테이너 내부에서 생성된 파일이 호스트에 저장되거나, 호스트의 파일이 컨테이너에서 사용될 수 있게 됨
    #데이터를 영구적으로 저장하거나 공유하기 위해 사용
    #volumes를 통해 데이터를 외부(호스트)에 저장하면 컨테이너를 삭제해도 데이터가 보존
      - ./prometheus/config:/etc/prometheus 
      #호스트의 ./prometheus/config에 있는 설정 파일이 컨테이너 내부에서 사용
      #컨테이너 내부에서 설정을 수정하면 그 내용이 호스트의 ./prometheus/config에 그대로 저장
      - ./prometheus/data:/prometheus
      #호스트의 폴더 ./prometheus/data → 컨테이너 내부의 폴더 /prometheus와 연결
      #Prometheus가 수집한 데이터가 컨테이너 내부의 /prometheus에 저장되는데, 이 데이터가 호스트의 ./prometheus/data에 동기화
      #컨테이너를 삭제해도 데이터는 호스트의 ./prometheus/data 폴더에 남아 있음
    #요약: Prometheus의 설정 파일과 데이터를 호스트의 특정 폴더에 저장하고 컨테이너가 이를 읽고 쓰도록 연결하는 작업
    ports:
      - 9090:9090
    networks:
      - monitor
    restart: always


  grafana: #Prometheus의 데이터를 시각화해주는 툴
    container_name: grafana
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_USER=admin #관리자 ID를 admin으로 설정
      - GF_SECURITY_ADMIN_PASSWORD=password #비밀번호를 password로 지정
      - GF_USERS_ALLOW_SIGN_UP=false #사용자가 직접 가입하지 못하도록 설정
    volumes:
      - ./grafana/data:/var/lib/grafana #Grafana 데이터 저장소
      - ./grafana/provisioning:/etc/grafana/provisioning #Grafana의 초기 설정 파일들이 들어 있는 디렉터리
    ports:
      - 3000:3000
    depends_on: #Prometheus가 먼저 실행되어야 함
      - prometheus
    networks:
      - monitor
    restart: always

  redis-exporter: #Redis 데이터를 Prometheus에 전송해주는 툴
    container_name: redis-exporter
    image: oliver006/redis_exporter:latest
    environment:
      - REDIS_ADDR=redis://redis:6379 #Redis 컨테이너와 연결
      #redis:// Redis 프로토콜(통신 방식)
      #redis Redis 컨테이너의 이름 (DNS처럼 사용)
    ports:
      - 9121:9121
    depends_on:
      - prometheus #Prometheus가 먼저 실행되어야 함
    networks:
      - monitor
    restart: always

 

 

3. 의존성의 기반에 되는 prometheus 설정 파일 만들기

prometheus.yml
Prometheus가 어디에서 데이터를 수집할지(스크래핑) 설정하는 파일
global:
  scrape_interval: 1m 
  #모든 스크래핑 기본 간격 설정
  #기본적으로 1분마다(targets에서 지정한 서버에서) 데이터를 수집

scrape_configs: #스크래핑 대상 정의
  - job_name: 'prometheus'
    scrape_interval: 1m
    # scrape_configs.scrape_interval이 global.scrape_interval보다 우선 적용
    static_configs: #고정된 타겟 리스트
      - targets: ['localhost:9090'] #Prometheus 자체를 스크래핑

  - job_name: 'redis-exporter'
    scrape_interval: 5s
    static_configs:
      - targets: ['redis-exporter:9121'] #Redis Exporter에서 메트릭 수집

 

 

4. 가자, Docker-compose!

docker-compose -f -d docker-compose.yml up
# Docker Compose는 기본적으로 커맨드가 실행하는 디렉토리에 있는 docker-compose.yml 또는 docker-compose.yaml을 설정 파일로 사용
# -f: 다른 이름이나 경로의 파일을 Docker Compose 설정 파일로 사용
#   : 여러 개의 설정 파일 사용
# up: Docker Compose에 정의되어 있는 모든 서비스 컨테이너를 한 번에 생성하고 실행하기 위해서 사용
# -d: 백그라운드에서 컨테이너 실행

docker-compose start redis
# 내려가 있는 있는 특정 서비스 컨테이너를 올리기 위해서 사용
# 서비스에 대한 기존 컨테이너를 시작
# docker-compose.yml 파일에서 서비스 이름을 기준으로 사용

docker-compose ps
# Docker Compose에 정의되어 있는 모든 서비스 컨테이너 목록을 조회할 때 사용

 

 

5. 실행 화면

Redis-exporter(127.0.0.1:9121)

 

Prometheus(127.0.0.1:9090)

 

Grafana(127.0.0.1:3000)
(3000 포트... 이제 React만의 것이 아닙니다..🍂)

+ Grafana에 Prometheus 연결

⚙️설정 → Data Sources  → Add new data source: Prometheus 선택

  🚨url 입력 시, localhost가 아닌 localhost가 아니라 서비스 이름(prometheus)을 사용해야함

      * Prometheus 컨테이너는 기본적으로 localhost:9090에서 동작하지만, 이는 Prometheus 컨테이너 내부에서만 접근 가능한 주소

      * Grafana 컨테이너는 네트워크 상에서 Prometheus 컨테이너에 접근해야 하며, 이 경우 localhost가 아니라 서비스 이름(prometheus)을 사용해야 함(Docker Compose는 서비스 이름을 DNS 이름으로 자동 해석)

 

+ Grafana Redis Dashboard 설정

  * 해당 Dashboard Template의 Copy ID to clipboard 또는 Download JSON 클릭

  * Dashboards → Import → Import via grafana.com 또는 Upload dashboard JSON file → Import 클릭

 

  • Grafana Dashboard Template

https://grafana.com/grafana/dashboards/

 

Grafana dashboards | Grafana Labs

No results found. Please clear one or more filters.

grafana.com

  • Redis Exporter Dashboard Template

https://grafana.com/oss/prometheus/exporters/redis-exporter/?tab=dashboards

 

Prometheus OSS | Redis exporter

Overview Installation Recording rules Dashboards Alerting rules Grafana Cloud Integration

grafana.com

 

 

6. 안녕, Dokcer-Compose!

docker-compose down
# Docker Compose에 정의되어 있는 모든 서비스 컨테이너를 한 번에 정지시키고 삭제

docker-compose stop redis
# 돌아기고 있는 특정 서비스 컨테이너를 정지시키기 위해서 사용
# 컨테이너를 제거하지 않고 실행중인 컨테이너를 중지
# docker-compose.yaml 파일에서 서비스 이름을 기준으로 사용

 

 


📑

참고 자료

Chat GPT

https://solo5star.tistory.com/19

 

grafana와 prometheus로 모니터링 시스템 구축하기

리뉴얼 된 블로그로 보기: https://solo5star.dev/posts/19/ 작업관리자는 윈도우 PC를 사용하는 사람들에겐 너무나도 친숙한 프로그램입니다. 렉이 걸린다면 CPU를 너무 많이 잡아먹고 있는건지, 메모리

solo5star.tistory.com

https://www.daleseo.com/docker-compose/

 

Docker Compose 커맨드 사용법

Engineering Blog by Dale Seo

www.daleseo.com

https://velog.io/@coastby/Grafana-%EC%84%A4%EC%B9%98-prometheus%EC%99%80-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0

 

[Grafana] 설치, prometheus와 연동하기

Grfana는 Grafana Labs가 개발한 오픈소스 데이터 시각화 플랫폼이다. 차트와 그래프로 대시보드를 구성하여 간편하게 데이터를 확인할 수 있다. 또한 기존의 서버 환경, 쿠버네티스 클러스터 또는

velog.io

 

728x90
728x90

cache의, cache를 위한, cache에 의한

cache의 3단 변신..

 

Vegeta로 하염없는 Request를 보내보니 알겠더군요..

* [대기열 시스템] HTTP load testing tool, Vegeta 설치(Window)

Cache가 중요하다는 것을요..

 

메모리에 저장해두고 누구보다 빠르게 남들과는 다르게 데이터를 전달해주는 Redis..

 

 

다양한 방법, 3.5단 변신을 정리해보고자 합니다..🤖

 

1단 변신 준비. RedisConfig - userRedisTemplate

Redis에 데이터를 저장하고 가져올 때, User 객체를 쉽게 직렬화/역직렬화하여 처리할 수 있도록 RedisTemplate을 설정

RedisConfig.java
@Configuration
public class RedisConfig {

  @Bean
  RedisTemplate<String, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    // RedisConnectionFactory: Redis 서버와의 연결을 관리하는 클래스
    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
    // ObjectMapper: JSON 데이터를 Java 객체로 변환하거나, Java 객체를 JSON 데이터로 변환
    // DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false: JSON에 Java 클래스에 정의되지 않은 필드가 있어도 에러를 발생시키지 않도록 설정
    // JavaTimeModule: java.time 패키지의 날짜/시간 클래스(LocalDate, LocalDateTime 등)를 처리할 수 있는 모듈을 추가
    // SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS
    // : 날짜를 타임스탬프가 아닌 ISO-8601 포맷(예: 2023-01-18T10:15:30)으로 직렬화하도록 설정

    // Object Mapper 설정 이유
    // Java 객체의 구조에 맞지 않는 JSON 필드가 있을 경우 에러가 발생할 수 있음
    // Java의 날짜/시간 클래스(LocalDate, LocalDateTime 등)는 기본적으로 직렬화/역직렬화가 제대로 지원되지 않음
    // 기본적으로 날짜와 시간을 타임스탬프 형식(예: 1674042600000, 밀리초 단위)으로 직렬화
    // 날짜를 사람이 읽을 수 있는 ISO 형식(예: 2025-01-18T15:30:00)으로 변환

    RedisTemplate<String, User> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    template.setKeySerializer(new StringRedisSerializer()); // 키를 문자열(String)로 직렬화
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(objectMapper, User.class)); // JSON 포맷으로 데이터를 저장

      return template;
  	}
  }

 

 

1단 변신. RedisConfig - objectRedisTemplate

RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장

RedisConfig.java
@Configuration
public class RedisConfig {

  @Bean
  RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Object.class)
        .build();
    // PolymorphicTypeValidator: allowIfSubType을 통해 역직렬화 허용 클래스 타입을 정의
    
    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
    // activateDefaultTyping: Jackson에서 직렬화/역직렬화 시 객체 타입 정보를 포함하도록 설정하는 메서드
    // DefaultTyping: 비최종 클래스(Non-final class)에 대해서만 타입 정보를 포함

    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
    // RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장
    // GenericJackson2JsonRedisSerializer
    // : JSON 데이터를 역직렬화할 때, 대상 객체의 타입 정보를 명시적으로 제공하지 않으면 기본적으로 Map 타입(즉, LinkedHashMap)으로 역직렬화

    return template;
  }
}

 

 

2단 변신. RedisHashUser

@Redishash를 사용해 객체를 Redis의 Hash 데이터 구조로 저장

RedisHashUser.java
@RedisHash(value = "redishash-user", timeToLive = 30L)
// @RedisHash를 사용해 Redis에 저장할 객체를 정의
public class RedisHashUser {

  @Id
  private Long id;
  // HSET redishash-user:3 id 3
  private String name;
  // HSET redishash-user:3 name "hi3"
  @Indexed
  private String email;
  // HSET redishash-user:3 email "hi3@email.com"
  // "SADD" "redishash-user:email:hi3@email.com" 3
  private LocalDateTime createdAt;
  private LocalDateTime updatedAt;
}

 

RedisHashUserRepository.java
public interface RedisHashUserRepository extends CrudRepository<RedisHashUser, Long> {
	// ...
}

 

JpaRepository는 Redis가 아닌 RDBMS(예: MySQL, PostgreSQL 등)와 상호작용하기 위한 Repository 

CrudRepository / RedisRepository를 상속하는 Repository 이용 필수

기능 JpaRepository CrudRepository
저장소 대상 관계형 데이터베이스 (RDBMS) Redis
데이터 모델 테이블과 레코드 Key-Value 또는 Hash 등
내부 구현 JPA(EntityManage, Hibernate 등) 기반 RedisTemplate 기반
동작 방식 SQL 쿼리 생성 및 실행 Redis 명령어(SET, GET 등) 실행
@RedisHash 지원 여부 지원하지 않음 지원함

 

 

3단 변신(최종 변신). @Cacheable

캐시에 데이터가 없을 경우, 캐시에 데이터를 추가하고, 있을 경우, 캐시의 데이터 반환

CacheConfig.java
@EnableCaching
// Spring의 캐싱 기능을 활성화
// 캐싱 관련 애너테이션(@Cacheable, @CacheEvict, @CachePut)이 동작하도록 설정
// 애플리케이션 컨텍스트에 한 번만 추가되면 전체 프로젝트에서 동작
@Configuration
public class CacheConfig {

  public static final String CACHE1 = "cache1";
  public static final String CACHE2 = "cache2";

  @AllArgsConstructor
  @Getter
  public static class CacheProperty{
    private String name;
    private Integer ttl;
  }

  @Bean
  public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(){
    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Object.class)
        .build();

    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);

    List<CacheProperty> properties = List.of(
        new CacheProperty(CACHE1, 300),
        new CacheProperty(CACHE2, 30)
    );

    return (builder -> {
      properties.forEach(i ->
          builder.withCacheConfiguration(i.getName(),
              RedisCacheConfiguration.defaultCacheConfig()
              .disableCachingNullValues()
              .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
              .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)))
              .entryTtl(Duration.ofSeconds(i.getTtl()))));
    });
    // RedisCacheConfiguration.defaultCacheConfig(): 기본 Redis 캐시 설정을 가져옴
    // disableCachingNullValues(): null 값을 캐싱하지 않도록 설정
    // serializeKeysWith: Redis의 키를 직렬화하는 방법을 설정, 키를 문자열로 직렬화
    // serializeValuesWith: Redis의 값을 직렬화하는 방법을 설정, GenericJackson2JsonRedisSerializer를 사용하여 값을 JSON으로 직렬화
    // entryTtl: 캐시의 만료 시간을 설정
  }
}

 

UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
  @Cacheable(cacheNames = CACHE1, key = "'users:' + #id")
  // cacheNames: 캐시를 구분하고 각각의 캐시에 대해 커스터마이징된 설정을 적용하기 위해 사용
  public User getUser3(final Long id) {
    return userRepository.findById(id).orElseThrow();
  }
}

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

 

728x90