애플리케이션에서도 여러 프로세스나 쓰레드가 동일 데이터에 접근함으로써 발생할 수 있는 오류를 방지하기 위한 것

C++

세마포어, 크리티컬, 뮤텍스

자바

syncchronized, wait/notify, java.util.concurrent package class

락 범위 최소화

문제점

Logger.println 같이 개발자가 직접 사용하는 메서드에 synchronized 락이 있으면 로그 날짜와 시간 같은 부가 정보를 만드는 날짜까지 락에 포함된다. 그럼 락을 잡고 있는 시간이 길어져 성능에 영향을 미치게 된다.

해결책

StringBuilder 같은 클래스를 사용해 수십줄에 달하는 로그라도 하나의 로깅 문단으로 완성한 후 기록하는 시점에 Writer 객체에 대해서만 락을 설정한다면 경합을 감소시킬 수 있다.

락 제거

StringBuffer 여러 쓰레드가 동시에 데이터를 수정하는 경우가 없다면,

StringBuilder로 바꾸는 것이 성능 측면에서 효율적이다.

StringBuffer는 여러 쓰레드에서 동시 수정이 가능하도록 쓰레드 안전(Thread-Safe)하게 synchronized로 락 처리가 돼 있다. 따라서 한 스레드 내에서만 수정된다면 메서드를 호출할 때마다 락을 열고 닫을 필요 없이 CPU 클럭을 조금이나마 적게 사용한다.

StringBuilder sb = new StringBuilder(30);
sb.append("this").append(10000L).append("is").append("").append("test!");
StringBuffer sb = new StringBuffer(30);
sb.append("this").append(10000L).append("is").append("").append("test!");

문자열 처리 개선

String.format : 복잡한 과정이 있어 StringBuilder로 간단하게 처리하는 것보다 성능이 10배 차이

source.replaceAll : 내부적으로 정규 표현식을 사용하는 형태로 구현돼 있어서 복잡하다.

리플렉션 호출 제거

리플렉션 호출은 프로그램에 유연성과 확장성을 제공하지만 이를 구현하기 위해 객체를 생성하고 메서드를 호출하기 위한 준비 작업 하나하나가 직접 메서드를 호출하는 것과 맞먹는 비용이 든다.

스프링 AOP 를 이용한 공통 메서드 호출을 자재 해야 한다.

채번

DB 채번 - 성능 저하 원인

# 기존 최댓값 조회
select max + 1
# 채번 테이블
select num + 1
# DB 채번 기능 
sequence

기존 최댓값 조회:

동시에 채번이 이뤄지는 경우 동일한 번호가 채번되어 후속 작업에서 에러가 발생할 수 있으므로 주의해야 한다.

채번 테이블:

채번 테이블에 유형별 레코드로 채번 번호를 관리하는 방식으로 동시 채번에 의한 무제를 해결하기 위해 select for update 구문을 사용한다. 채번 테이블의 레코드에 대한 락 점유 시간을 최소화 할 수 있도록 별도 트랜잭션 처리가 이뤄져야 한다. 그럼에도 채번 요청이 많아지면 싱글 포인트 병목 지점으로 성능 저하의 원인이 될 가능성이 높다.

DB채번 기능:

DB에서 제공하는 채번 기능을 사용하는 것으로 세 가지 방식 중 가장 성능이 우수하다. 성능 저하가 발생한다면 미리 번호를 채번해서 캐시해 뒀다가 사용하는 sequence의 추가 기능 사용을 고려 할 수 있다.

문제

15개의 작업 스레드를 가진 10개의 자바 프로세스가 병렬적으로 실행되면서 채번 테이블 방식을 사용하는 배치가 있었다. 채번에 소요되는 시간이 59%나 됐다.

해결책

10000개의 단위로 DB채번을 한 번만 수행하도록 수정한 후 채번에 소요된 시간 비중이 0.5% 수준으로 감소했다. 전체 배치 소요시간은 1/2이하로 줄어들었다.

public class SequnceCache {
  static long maxSequence = 0L;
  static long currentSequence = 0L;
  static synchronized public long getSquence() {
    long sequence = 0L;
    if (currentSequence < maxSequence) {
      sequence = maxSequence;
    } else {
      sequence = currentSequence + getDBSquence();
      maxSequence = currentSequence + 1000;
    }
    currentSequence++;
    return sequence;
  }
  static private long getDBSquence() {
    return ("select num + 1 into:채번 from 채번테이블 where type =:유형 for update 채번테이블 set num + 10000 where type =:유형 commit; ")
  }
}

앞서 오라클DB의 SEQUENCE를 이용한 채번 캐시도 있다고 했다. 하지만 이는 DB엔진 내부에 미래 채번해 두는 것으로 애플리케이션에서 DB에서 채번된 숫자를 가져오는 횟수를 줄이는 것은 아니다. 성능 개선을 위해서는 DB와 애플리케이션 간 호출 횟수를 줄이는 것이 중요하다. 그래서 애플리케이션에서 채번된 숫자를 알아야 하는 경우가 아니라면 INSERT나 UPDATE SQL에 SEQUENCE.NEXTVAL을 함께 사용한다.

애플리케이션에서 채번된 숫자를 알아야 한다면 어떻게 할 수 있을까?

select db_sequence.nextval as seqval from dual connect by level <= :cacheSize

원하는 만큼의 레코드 열을 만들어 낼 수 있다. 이를 이응용해 sequence.nextval를 함께 사용하는 원하는 개수만큼 집합 채번이 가능해 진다.

채번된 숫자 목록을 애플리케이션 서버에서 캐시하고 있으면서 필요할 때 사용한다면 애플리케이션에서 채번된 숫자를 알아야 하는 경우더라도 DB 호출 횟수를 줄일 수 있게 된다.

날짜 연산

자바에서 제공하는 Calendar 클래스를 사용하지 말고, 2개의 배열에 저장해 날짜를 연산하는 방식 열배 이상 성능 향상

시간 문자열 처리

SimpleDateFormat

profile
시간은 돈과 바꿀 수 있다.

0개의 댓글