728x90
반응형
728x90
반응형
반응형

환경: springboot3.2

 

기존과 같이 new PageImpl()을 이용하여 Page<T> 객체를 내리는데 아래와 같은 에러(warn)가 발생한다.

Serializing PageImpl instances as-is is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure!
	For a stable JSON structure, please use Spring Data's PagedModel (globally via @EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO))

 

에러 발생 이유

  • Spring Boot 3.x
  • 내부적으로 사용하는:
    • Spring Data Commons 3.x (2023.0.x 이상)
    • Spring Framework 6.x

이 조합에서 PageImpl을 그대로 @RestController에서 반환하면 Jackson 직렬화 구조가 불안정하다는 경고가 발생한다.

 

Spring 팀은 PageImpl<T>의 직렬화가 다음 문제를 가진다고 판단했다:

  • Jackson 직렬화 시 필드 순서나 구조가 변경될 수 있음
  • 필드 이름이 내부 구현에 의존
  • API 스펙을 안정적으로 유지하기 어려움

그래서 Spring Data 팀이 공식적으로 PagedModel<T> 또는 DTO 변환을 권장하게 됨.

 

해결 방법

1. hate oas 를 사용하는 경우

PagedModel<T> 사용

return pagedResourcesAssembler.toModel(page);
 
: 결과가 PagedModel<EntityModel<T>> 형태가 되어 안정적인 JSON 구조를 보장
 

2. hate oas 사용 안하는 경우

 
1) 설정 추가로 해결
@Configuration
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
public class WebConfig {
}

2) 직접 커스텀 DTO를 만들어서 사용

public class PageResponse<T> {
    private List<T> content;
    private int page;
    private int size;
    private long totalElements;

    public PageResponse(List<T> content, int page, int size, long totalElements) {
        this.content = content;
        this.page = page;
        this.size = size;
        this.totalElements = totalElements;
    }

    // getters, setters
}
728x90
반응형
반응형

 

1. String Literal

  • 자바에서 "hello", "abc", "쿠폰"처럼 쌍따옴표로 직접 입력한 문자열
  • JVM의 String Constant Pool(문자열 상수 풀)에 저장됨
  • 같은 literal을 사용하면 동일한 인스턴스 재사용
  • 메모리 절약 + 빠름
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true

 

2. String 생성자 사용 (new)

 

  • 항상 heap 메모리새 인스턴스를 생성
  • 상수 풀에 있는 "hello"는 그대로 존재하지만, 생성자는 그것을 복사한 새로운 객체를 만듦
String s1 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s3); // false (주소 다름)

 


 

  • 특별한 이유가 없다면 항상 "hello" 같은 literal 방식 사용
  • new String("...")은 피하는 것이 좋음. 필요할 땐 .intern()을 써서 상수 풀로 옮길 수 있음

 

728x90
반응형
반응형

톰캣과 네티는 방식 자체가 다르다.

Tomcat: 기본적으로 동기 / 블로킹 I/O

  • 과거에는 BIO (Blocking I/O) 방식만 지원
  • 현재는 NIO 기반도 사용 가능하지만, Servlet API 자체가 동기 처리 구조
  • 요청마다 별도의 스레드가 생성되어 처리 (Thread-per-request model)
  • Spring MVC 같은 전통적인 웹 프레임워크와 잘 통합됨

Netty: 완전한 비동기 / 논블로킹 I/O 기반

  • NIO 기반으로 동작 (Java Non-blocking I/O)
  • 하나의 스레드가 여러 연결을 동시에 처리할 수 있음
  • 고성능 네트워크 처리에 유리함 (예: 게임 서버, 고속 API 게이트웨이)
  • 이벤트 기반 처리 (이벤트 루프 + 핸들러 체인)
  • 이벤트 루프(EventLoop)라는 단일 스레드가:
    • 여러 소켓 채널(연결)을 등록하고
    • 그 채널에 데이터가 도착하거나 쓰기 가능한 이벤트가 생길 때만
    • 콜백 방식으로 핸들러를 호출

단순히 보면 네티가 빠르긴 한데 이는 방식의 차이다.

  • Tomcat은 요청마다 스레드 생성 → 동시 요청 많으면 톰캣 스레드가 계속 늘어나면서 CPU/메모리 부담 커짐 → 느려짐
  • Netty는 이벤트 루프 방식 → 1개 스레드로 수천 개의 소켓 핸들링 가능 thread를 더 효율적으로 쓰게 만드는 방식
    • 게임 서버, 채팅 서버, 메시징 서버 등에 Netty가 선호되는 이유
  • 톰캣에서 네티로 바꾼다고 초당 request time이 줄어드는 것이 아님

 

유튜브 쉬운코드

+ JDK21부터 공식적으로 들어간 virtual thread가 잘 자리잡으면, 전체적인 throughput 관점에서 봤을 때도 Tomcat이 Netty한 성능을 낼 수 있지 않을까 기대

참고로 Spring WebFlux는?

  • Netty 기반으로 동작하는 비동기/리액티브 웹 프레임워크
  • Tomcat도 가능하지만, 리액티브하게 쓰려면 Netty가 더 자연스러움
728x90
반응형
반응형
UUID는 Universally Unique Identifier의 줄임말로, 전 세계적으로 고유한 ID를 의미

 

UUID란?

  • 128비트(16바이트) 크기의 고유 식별자
  • 일반적인 문자열 표현: "faca5a65-3500-4855-be9f-455c6544f856" (총 36자)
  • 보통 중복되지 않는 고유한 값을 자동으로 생성할 때 사용
  • 32자리 16진수
  • 하이픈 -으로 5개 구간으로 구분: 8-4-4-4-12
UUID uuid = UUID.randomUUID();

왜 UUID를 쓰나?

  • 고유 식별
    • 여러 시스템, 여러 서버에서 생성해도 충돌 위험이 거의 없음
  • 데이터 병합 안정성
    • 분산 시스템에서 병합해도 ID 충돌 없음
  • 보안성(노출 방지)
    • 숫자 증가 ID보다 추측 어렵고 노출에 강함

왜 이걸 키로 쓰지? 장단점은?

 

디비로 조회

## string to uuid
select unhex('faca5a6535004855be9f455c6544f856') from dual;

select * from poh where uuid = unhex('faca5a6535004855be9f455c6544f856') ;
728x90
반응형
반응형

1. IO Bound (Input/Output Bound)

  • 정의: 프로그램의 처리 속도가 입출력(IO) 작업에 의해 제한되는 경우.
  • 예시:
    • 파일 읽기/쓰기
    • 데이터베이스 접근
    • 네트워크 통신 (REST API, 소켓 통신 등)
  • 특징:
    • CPU는 대부분 대기 상태
    • 비동기 처리(Async), 논블로킹 IO, 캐시 활용 등을 통해 성능 개선 가능

2. CPU Bound

  • 정의: 프로그램의 처리 속도가 CPU 연산 능력에 의해 제한되는 경우.
  • 예시:
    • 복잡한 수학 계산
    • 압축, 암호화
    • 이미지/비디오 처리
  • 특징:
    • CPU 사용률이 매우 높음
    • 멀티스레딩, 병렬 처리(parallelism), SIMD 등으로 성능 개선 가능

IO Bound는 스레드가 많을수록 유리

  • 이유:
    • IO 작업은 대부분의 시간을 대기(Waiting) 상태로 보냄 (예: 파일 읽기, DB 쿼리 응답 기다림)
    • 대기 중인 스레드는 CPU를 거의 사용하지 않음
    • 따라서 여러 스레드가 IO를 동시에 처리해도 CPU 병목이 생기지 않음
  • 결과:
    • 스레드 수를 CPU 코어 수보다 훨씬 많이 늘려도 시스템에 부담이 덜함
    • 예: 웹 서버는 수천 개의 연결을 동시에 처리하기 위해 수천 개의 스레드를 유지할 수 있음 (혹은 비동기 처리 사용)

CPU Bound는 스레드가 너무 많으면 오히려 역효과

  • 이유:
    • CPU Bound 작업은 대부분의 시간을 계산(Compute)에 사용
    • 즉, CPU를 계속 점유하므로, 코어 수를 초과하는 스레드는 결국 경쟁, 문맥 교환(context switch) 발생
    • 이로 인해 오히려 성능 저하가 발생할 수 있음
  • 적절한 전략:
    • 스레드 수를 CPU 코어 수에 맞추거나 약간만 초과 (예: 4 코어 → 4~6개 스레드)
    • 병렬 처리 시 ForkJoinPool, parallelStream, Executors.newFixedThreadPool() 등 사용

코어수는 2개 스레드를 코어수+1 정도로 스레드 수를 줄여서 context switching에 드는 비용을 줄인다

 

압축 후 비동기로 업로드할 때 100개가 동시가 갈 경우 검수API 쪽에 부하가 걸리진 않을지 고려 필요

디비에서 전체 검색을 한다면, 하고나서 서버에 올릴 때 메모리 이슈 없을지 고려필요 -> 파일로? 그렇다면 압축은 어떻게?

불변객체
or record

 

728x90
반응형
반응형

클러스터 인덱스

  • 테이블의 레코드 자체가 인덱스 순서에 맞춰 저장됨
  • 한 테이블에 하나만 존재 가능 (왜냐하면 테이블 자체의 정렬 순서를 정하기 때문)
  • 기본 키(Primary Key)가 자동으로 클러스터 인덱스로 지정되는 경우가 많음 (DBMS마다 다름; Mysql: O, postgreSql: X)
  • 범위 검색이 매우 빠름 (BETWEEN, >=, <= 등)
  • 클러스터 인덱스 기준으로 정렬되므로 자주 업데이트되거나 정렬 순서가 자주 바뀌는 컬럼은 비추천
  • INSERT/UPDATE 성능이 약간 저하될 수 있음 (정렬 유지 때문에)

유투브 쉬운코드

논 클러스터 인덱스

실제 데이터와 분리된 별도의 인덱스 구조(별도 공간) 책의 목차처럼 인덱스만 따로 있고, 실제 데이터를 찾아가야 함

비교

 

외래키

  • 다른 테이블의 PRIMARY KEY(기본 키)를 참조해서 두 테이블 간의 관계를 연결해주는 키
  • 외래 키는 무결성(일관성), 데이터 신뢰성을 지키는 역할
    • 제약이 없으면 잘못된 값이 들어갈 수 있음
  • 외래 키(Foreign Key)가 있다고 해서 자동으로 정렬되지는 않음
  • 외래 키는 정렬 기능이 아니라 제약 조건(Constraint)

외래키와 인덱스

  • 외래키는 위와 같은 이유로 보통 JOIN하거나 WHERE 조건에서 자주 사용되기 때문에 인덱스가 없으면 느려질 수 있음
  • 특히 참조 대상이 삭제되거나 변경될 때도 외래 키 무결성 검사 위해 관련 데이터 검색이 필요한데, 인덱스 없으면 전체 테이블을 스캔

MySQL에서는 FOREIGN KEY를 설정할 때, 참조 대상이 인덱스가 아니면 오류 발생

CREATE TABLE orders (
    id INT PRIMARY KEY,
    member_id INT,
    FOREIGN KEY (member_id) REFERENCES members(id)
);
  • 이 경우 orders.member_id에 자동으로 인덱스 생성됨
  • 이유: MySQL은 성능 및 무결성 검증을 위해 인덱스를 자동 생성 그래서 EXPLAIN으로 보면 member_id에 인덱스가 잡혀 있음

 

 

 

 

728x90
반응형

'개발 > sql' 카테고리의 다른 글

[mysql] string to uuid  (0) 2025.05.09
[mysql] 복합 인덱스와 explain  (0) 2025.02.03
[mysql] order by null  (0) 2024.12.19
[파티셔닝] 하는법, 쓰는법  (0) 2024.11.25
비관락/낙관락 쓰기락/읽기락 베타락/공유락  (1) 2024.11.09
반응형

환경: jdk6, bouncy castle 설치됨, https 통신 필요

https통신 시도 시 아래와 같은 에러 발생

원인

  • JDK 6 환경에서 TLS 1.2 통신을 시도
    • 기본적으로 JDK 6은 TLS 1.2를 지원하지 않음
    • 하지만 Bouncy Castle을 설치하여 강제로 TLS 1.2를 활성화함
  • TLS Handshake 실패
    • 서버가 제공한 인증서를 검증할 때, JDK 6의 TrustStore (신뢰할 수 있는 루트 인증서 저장소)에 해당 루트 인증서가 없었음
    • 즉, 신뢰할 수 있는 CA(인증 기관)의 인증서가 누락되어 있어 통신 실패

해결

  • 루트 인증서를 설치하고 JDK 6 KeyStore에 추가
    • keytool 명령어를 사용하여 루트 인증서를 JDK 6의 TrustStore에 등록 
    • cacerts는 JDK의 기본 TrustStore ($JAVA_HOME/jre/lib/security/cacerts)
keytool -import -trustcacerts -keystore cacerts -storepass changeit -file rootCA.crt -alias myrootca
  • 다시 TLS 1.2 통신을 시도하여 성공
    • 루트 인증서가 추가되었으므로 서버 인증서 검증이 정상적으로 완료됨
    • 최종적으로 TLS 1.2 통신이 정상적으로 수행됨

 

728x90
반응형
반응형
728x90
반응형
반응형

환경: java17, spring boot 3.1.5, spring batch 5.0.3, mysql5.7

 

이슈:

아래 에러가 간헐적으로 발생하며 배치 실패. 재실행 시 정상 처리

Caused by: com.mysql.cj.jdbc.MysqlXAException: XAER_DUPID: The XID already exists
	at com.mysql.cj.jdbc.MysqlXAConnection.mapXAExceptionFromSQLException(MysqlXAConnection.java:344)
	at com.mysql.cj.jdbc.MysqlXAConnection.dispatchCommand(MysqlXAConnection.java:329)
	at com.mysql.cj.jdbc.MysqlXAConnection.start(MysqlXAConnection.java:290)
	at com.atomikos.datasource.xa.XAResourceTransaction.resume(XAResourceTransaction.java:217)
	... 81 common frames omitted
Caused by: java.sql.SQLException: XAER_DUPID: The XID already exists
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.StatementImpl.executeInternal(StatementImpl.java:763)
	at com.mysql.cj.jdbc.StatementImpl.execute(StatementImpl.java:648)
	at com.mysql.cj.jdbc.MysqlXAConnection.dispatchCommand(MysqlXAConnection.java:323)

디비에서 xa recover; 로 검색 시 남아있는 트랜젝션 없는 것 확인

 

해결:

XID 중복이라 우선 XID가 어떻게 생성되는지 확인

XID: <gtrid>:<bqual>
  • gtrid (Global Transaction ID): 분산 트랜잭션을 식별하는 고유한 값
  • bqual (Branch Qualifier): 트랜잭션 내에서 개별 브랜치를 구분하는 값

16진수 → ASCII 디코딩
제공된 XID는 16진수(Hex)로 인코딩 되어 있음. 이를 ASCII 문자로 변환해야 함

XID: 172.18.0.3.tm174046320874700011:172.18.0.3.tm11
XID: <IP>:tm<TIMESTAMP>:<BRANCH_ID>

gtrid = 172.18.0.3.tm174046320874700011
bqual = 172.18.0.3.tm11

  • 172.18.0.3 → 트랜잭션을 실행한 클라이언트 서버의 IP(여기서는 배치 서버)
  • tm174046320874700011 → 글로벌 트랜잭션 ID (gtrid)
    • 174046320874700011  타임스탬프 기반의 유니크 ID
  • tm11 → 브랜치 ID (bqual)

 

XID에 배치 서버 ip와 timestamp가 들어가므로 해당 서버에서 그 시간에 동시에 돌았던 'jta transaction'과 연관이 있었을 것으로 생각..

그걸 확인하기 위해 xid에 프로젝트 명이나 job 이름을 넣어보기로 했다.

상수 값도 줄 수 있고 아래처럼 spel을 사용하여 동적으로 줄 수도 있다.

spring.jta.atomikos.properties.transaction-manager-unique-name=${TRANSACTION_MANAGER_NAME:defaultTxManager}

 

  • 설정 대상: Atomikos 전체 트랜잭션 관리자
  • 역할: 트랜잭션 관리자(Transaction Manager)의 고유한 이름 지정
  • 사용 위치: 전역적으로 하나만 설정 (애플리케이션 전체에서 단 하나)
  • 중복되면 안 됨: 동일 네트워크 내 여러 인스턴스에서 서로 다른 값 필요

 

우선 해당 시간에 돈 배치 중 jta transaction 을 사용하는 배치는 이 배치 밖에 없을 것 같고

그래서 중복이 나도 한 프로젝트 안에서, 특히 이 job 안에서 발생했을 것 같긴 한데

우선 그걸 확증하기 위해 위와 같은 설정을 추가해 본다.

그래도 나면 그땐 진짜 우리끼리의 싸움

+ 그때는 XID에 UUID를 심을 수 있는 빈을 위 설정에 연결하는 게 좋을 것 같다.


추가로 

spring.jta.atomikos.properties.transaction-manager-unique-name=${TRANSACTION_MANAGER_NAME:defaultTxManager}

이 설정 값과 소스의 아래 부분이 동일한 설정이라고 생각했는데.. 그것은 아니었다..

AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
dataSource.setUniqueResourceName(resourceNameCreator.createResourceName(BATCH_DATASOURCE_NAME));

 

  • 설정 대상: 특정 DataSource(XA 리소스)
  • 역할: 각 XA 리소스를 식별하는 고유한 이름(Unique Name) 지정
  • 사용 위치: 각각의 AtomikosDataSourceBean 객체에 대해 개별적으로 설정
  • 중복되면 안 됨: 각 DataSource마다 고유해야 함 (여러 개의 XA 리소스를 사용할 경우 필수)

 

 

AtomikosDataSourceBean은 데이터 소스에 해당하는 고유 이름을 주는 것이고 

transaction-manager-unique-name는 atomikos transaction manager의 고유한 이름을 지정하는 것이었다..

 

우선 아래와 같이 세팅하고 추이를 지켜보기로 했다.

728x90
반응형
반응형

타임셰어링(Time Sharing)과 컨텍스트 스위칭(Context Switching)은 밀접한 관계가 있지만 다른 개념이라 하여 정리해본다.

 

타임셰어링(Time Sharing)

(OS가 관리하는) CPU 시간을 여러 프로세스 또는 스레드에 분배하는 방식

  • 목적: 여러 작업을 동시에 실행하는 것처럼 보이게 함 (멀티태스킹)
  • 방법: 일정한 시간 간격(타임 슬라이스, Time Slice)마다 CPU를 다른 프로세스/스레드에 할당
    • 모든 프로세스가 공평하게 CPU 시간을 나누어 사용(선점 없음)
  • 결과: 사용자는 여러 작업이 동시에 실행되는 것처럼 느끼지만, 실제로는 CPU가 빠르게 번갈아가며 실행하는 것

 

컨텍스트 스위칭(Context Switching)

CPU가 실행 중인 프로세스 또는 스레드를 변경할 때, 현재 상태(Context)를 저장하고 새로운 프로세스 또는 스레드의 상태를 복원하는 과정

  • 목적: 여러 프로세스를 실행하기 위해 이전 실행 상태를 저장하고 새로운 작업을 로드
  • 필요한 이유: OS가 프로세스나 스레드를 교체할 때 이전 작업을 나중에 다시 실행할 수 있도록 하기 위해
  • 오버헤드 발생: 컨텍스트를 저장하고 복원하는 작업은 추가적인 CPU 자원을 소모

컨텍스트 스위칭 과정

  1. 현재 실행 중인 프로세스의 레지스터, 메모리 상태(Context) 저장
  2. 새로운 프로세스의 레지스터, 메모리 상태 복원
  3. CPU가 새로운 프로세스를 실행

즉, 타임셰어링을 하려면 필연적으로 컨텍스트 스위칭이 발생함!


타임셰어링은 CPU 시간을 나누어 여러 프로세스가 실행되도록 하는 방식
타임셰어링을 수행하려면 컨텍스트 스위칭이 발생해야 함
하지만 컨텍스트 스위칭은 타임셰어링이 아닐 수도 있음

  • 예) 우선순위 기반 선점형(Preemptive) 스케줄링에서도 컨텍스트 스위칭 발생
    • 높은 우선순위의 프로세스가 CPU를 빼앗아 사용할 수 있음(선점 가능)

즉, 타임셰어링은 CPU 시간을 나누는 방식이고, 컨텍스트 스위칭은 그 과정에서 발생하는 기술적인 동작!

728x90
반응형

+ Recent posts