메모 목적의 글로써 생략되는 내용이 많이 있을 수 있는점을 감안해주세요

(우아콘 2020참고)

 

  1. querydsl exist 사용 금지
    1. 기본
      1. sql의 exist는 조건을 만족하는 1번째 row를 만나면 쿼리가 바로 종료
      2. count의 경우는 모든 row를 scan해야하기 때문에 성능이 exist보다 안 좋음
    2. qeurydsl의 exist는 count 쿼리를 이용해서 수행되기 때문에 성능이 안 좋음
    3. 직접 구현
      1. limit 1을 추가함(예전에 mysql 생쿼리로 개발할때도 이렇게 했었음..)
        1. 다만, 조회결과가 없으면 0이 아니라 null을 반환하니 null 체크처리 주의
  2. cross join 회피
    1. 나올수 있는 모든 경우의 수를 대상으로 하기때문에 성능이 안좋은 cross join. 피하는게 좋음
    2. querydsl은 묵시적 join사용할때 cross join발생할 수 있음
    3. 명시적 join으로 회피
      1. 예) innerJoin 메소드로 명시적 처리
  3. Entity 보다는 DTO를 우선 사용
    1. entity 조회시
      1. 실시간으로 Entity 변경이 필요한 경우에는 장점 -> 다만 회사 프로덕션 환경에서 이럴일은 없음
      2. hibernate 캐시 불필요
      3. 불필요한 컬럼 조회
      4. OneToOne N+1  쿼리 등
      5. 단순 조회 기능에서는 성능 이슈 요소가 많음
    2. DTO 조회시
      1. 고강도 성능 개선, 대량의 데이터 조회가 필요한 경우
      2. 조회 컬럼 최소화하기
        1. as 표현식으로 대체하면 DB에실행되는쿼리에서 as컬럼은 제외됨
          1. 예) Expressions.asNumber(bookNo).as("bookNo")
      3. Select 컬럼에 Entity 자제
        1. 불필요 신규 Entity의 모든 컬럼이 조회될 수 있음
        2. OntToOne 관계에 대해서 매건마다 조회됨(N+1무조건 발생하게 됨)
  4. Group by 최적화
    1. mysql에서는 order by null 을 이용하면 file sort가 발생하지 않는 기능이 존재(개인적으로도 많이 사용)
    2. querydsl에서는 order by null을 지원하지 않기 때문에 직접 구현해서 사용
      1. 정렬이 필요하더라도 조회결과가 100건 이하라면 어플리케이션에서 정렬하는걸 고려(페이징이 아닐때만)
        1. was는 scale out이 가능하지만 DB는 어려움
  5. 커버링 인덱스 사용
    1. 개인적으로도 대용량 게시판 서비스의 페이징 개념 만들때 사용함
    2. jpql은 from절의 서브쿼리를 지원하지 않기 때문에 우회처리가 필요
      1. 쿼리를 2개로 나눠서 실행
        1. Cluster Key(PK와 같은)를 커버링 인덱스로 빠르게 조회하고
        2. 조회된 Key로 select쿼리를 in 쿼리로 실행
  6. update 최적화
    1. 무분별한 DirtyChecking을 꼭 확인해야함
      1. 실시간 비지니스 처리, 실시간 단건 처리시
        1. 하이버네이트 캐시는 일괄 업데이트시 캐시 갱신이 안되기때문에
      2. Querydsl.update
        1. 대량의 데이터를 일괄로 Update 처리시
  7. bulk insert
    1. JPA에서는 auto_increment일때는 insert합치기가 적용되지 않는 문제가 있음
    2. jdbcTemplate롤 bulk insert는 처리 가능하나, 컴파일 체크, Type-safe 개발이 어려움
      1. 문자열로 쿼리를 작성해야해서
    3. 따로 개발해서 진행할수도 있지만... 개인적으로 고민좀 되는 부분이 있음
  1. 브랜치 생성

 

local에서 remote 브랜치로부터 신규 브랜치를 생성

 

 

 

remote에 test-1 브랜치 생성됨을 확인 가능

 

 

 

  1. test-1 브랜치를 master로 merge하기

intelliJ 에서 Git-> Merge 메뉴
master에 test-1 브랜치를 Merge 실행
이후 remote에 푸시하면 test-1에서 작업한 내용이 머지되어 remote까지 반영되었음을 확인 가능

 

 

 

참고 : 에버노트에 작성했던 글을 옮겨서 블로그에 작성한건데 양식 유지가 안되서 읽기가 조금 안 좋네요


 

  1. 목적
    1. 서비스를 운영중에 프로세스 재 시작없이 로그 레벨을 변경해야할 경우가 있을 수 있습니다.(Dynamically modify the log level)
      1. 예) 특정 케이스 디버깅을 위해서 프로덕션에서 디버그 레벨로 변경
    2. 여러가지 방법이 있을 수 있는데, alibaba의 arthas라는 프로젝트를 이용해서 변경해보고 arhas의 사용법을 익혀봄으로써 트러블슈팅 역량을 향상해보겠습니다.
  2. 참고 링크(arthas)
    1. github
    2. onlin tutorials
    3. documentation
    4. intellij plugin

3. dzone 참고 링크


  1. arthas 설치 및 실행
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

-- 사용법 확인하고 싶으면 -h옵션
java -jar arthas-boot.jar -h

 

  1. logger command를 이용해서 로그레벨 변경 방법
    1. 주의: 로그레벨을 낮추고 확인이 완료되면 잊지말고 원복 필요
    2. logger command
-- 현재 어플리케이션의 로그 레벨확인
logger

-- search class detail
예) sc -d yourClassName

-- ROOT라는 로거의 레벨을 debug로 변경
-- logger -c 클래스로더의해시--name ROOT --level debug
예) logger -c 51081592 --name ROOT --level debug

 

샘플

  1. arthas에서 로그레벨 변경 명령어
  2. 어플리케이션에서 로그가 변경되어 출력되는 모습
    1. logger 명령어로 ERROR레벨 이상으로 변경한 결과 -> ERROR레벨 이상의 로그만 출력됨을 확인 가능

 

 

아래 내용은 개인적인 생각임을 감안해주세요

 


2022년 4월 20일 내용 추가
 

원글: https://news.hada.io/topic?id=6121

일관성 유지 : URL/헤더/인증/상태코드..
ISO8601 UTC 날짜 포맷 사용
Public Endpoint만 인증 예외. 나머지는 모두 인증 필수
헬스 체크 Endpoint 제공
API 버저닝
API 키 인증 적용
합리적인 HTTP 상태코드 와 메소드 사용
각 Endpoint에 자체만으로 설명 가능한 간단한 이름을 사용
표준 에러 응답 사용
POST에서 생성된 자원을 리턴
PUT 대신 PATCH
최대한 구체적으로
Pagination 사용
각 자원을 확장 가능하게 (expand 등의 쿼리 파람을 줘서 추가 정보도 리턴 가능하게 설계)

 

원글: https://news.hada.io/topic?id=5823

- "AWS가 15년간 배운 좋은 API를 만드는 6가지 원칙" 에 대한 메모
1. API는 영원하다!
2. 하위 호환성을 지켜주세요.
3. 고객 사용 사례에서 거꾸로 만드세요.
4. 오류가 명시적인 API를 만드세요.
5. 바로 목적과 사용법을 이해할 수 있는 API를 만드세요.
6. 구현 세부 정보는 누출되지 않게 신경을 쓰세요.

- 초기 API 설계에서 실수하는 것
- Smithy를 통한 확장성 높은 API 만들기

 

몇년간 엄청나게 많은 API를 개발했는데, 그 중에서 RESTful API 설계에 대한 몇가지 개인적인 생각을 메모로 남겨봅니다.

 - 참고로, 인프라 셋팅/운영, 개발 실무, 개발팀 셋팅 및 SW개발 프로세스 수립, 많은 사용처에게 API 제공 등의 업무를 겪어서 단순 기술적인 부분이 아니라 조직원, 고객(?)에 대한 고민도 일부 반영된 내용입니다.



  1. RESTful API를 완벽히 적용하기는 실무에서 어렵습니다.
    1. API 사용쪽에서 방화벽 등의 인프라에 DELETE, PUT이 막혀서 사용이 불가능할 수 있습니다.
    2. 웹 기술에 익숙하지 않는 API 사용자(개발자)가 생각보다 많습니다.(그나마 최근 몇년 사이에 조금 좋아진듯합니다.)
      1. status 200이 정상임을 모르는 경우도 많음.. ;;)
      2. status 200일 경우에만 body를 파싱하도록 개발하는 개발자도 많음
        1. ex) 400응답일때 파라미터 xx가 오류라고 응답하면 에러코드나 설명을 처리할줄 모름

  2. POST와 GET 위주로 사용하는게 좋습니다.
    1. 데이터(리소스) 변경이 발생하는 경우는 POST를 사용
    2. 단순 질의(CQRS패턴의 질의? 같은 느낌인가..)는 GET을 사용
      1. 단, 보안이 중요하면 질의 형태도 POST를 사용
        1. 예) API URL을 메신저에 공유하면 각 메신저 개발사의 봇들이 호출 -> API 개발자가 실수로 인증 기능을 적용 안해둠 -> GET API가 작동->그런데 그 API가 유저 탈퇴 처리하는 운영기능 API였다면???
    3. DELETE와 같은 추가 메소드를 사용하지 못하는 문제는 URL을 잘 정의해서 회피/의도를 노출
      1. 예) URL path 마지막에 의도록 노출(ex. /블라블라/cancel)

  3. 이름을 잘 명명해야합니다.
    1. 이미 한번 배포되어 사용되면 바꾸기가 쉽지 않음
      1. 예)API URL에 오타가 있으면 서비스 종료까지 그대로 사용하게 됨(API 사용자가 생각보다 잘 수정해주지 않습니다.)

  4. API는 일관성을 가져야하며 사용자(API 사용 개발자)가 적용하기 쉬워야합니다.

  5. 지금은 필요하지 않아도 버전을 명시해야 하고 2depth에는 세부 기능의 대표 명사를 사용하는게 좋습니다.
    1. 서비스가 대박이 나서 트래픽이 늘어나면, URL path를 이용해서라도 트래픽을 분산, 또는 MSA로 적용 용이, 달리는 자동차 바뀌 교체 용이
      1. 예) 도메인/v1/member/

 

또 생각나면 추가로 작성하겠습니다.

 

 

메모

 

어플리케이션을 개발하다보면 분산락 처리를 신경써야하는 과제들이 간혹 있는데 저는 보통 2가지를 사용합니다.

 

  1. mysql의 named lock 사용
    1. 보통 어플리케이션이 RDB는 사용중인 경우가 많음
    2. 트래픽이 아주 많아서 성능이 중요하지 않음
    3. 개인적으로 티어가 복잡해지지 않아서 선호
    4. 참고
      1. mysql lockking 링크 
  2. redis이용
    1. redis 가 추가로 필요
      1. 개인적으로는 atomic을 보장하는 setnxsetex 를 활용하여 심플하게 개발하기도 함
        1. 스핀락 형태라 주의할점이 있음(ex. ttl 없고, redis 부하 증가 등). 간단히 예상되는 트래픽에 많이 씀
    2. 참고
      1. 참고 글  링크
      2. redis redlock best practices 링크
  3. 기타
    1. 아주 예전에는 zookeeper를 사용하기도 했는데 번거로움

시스템이 너무 복잡해져서 ROI 등을 따져서 적절하게 MSA로 전환하는데,

이벤트 방식으로 데이터 변경 사항을 전파시(loose 커플링, 장애 복구성 등의 장점 때문에) 순서에 대한 보장의 문제 등을 겪게 될겁니다.

 

이런 순서에 대한 문제를 해결하기 위한 방법이 있기는 하지만, 코드 및 아키텍쳐의 복잡성을 낮추기 위해서 저는 보통 zero-payload방식을 사용(추천)합니다.

 

간단히 핵심 내용만 정리하자면 아래와 같습니다.

 

zero-payload events

  1. 데이터 발생서비스는 메시지큐(kafka, aws sqs 등)에 변경이 발생한 데이터의 id(예. 주문ID와 같은 RDB에서의 PK)만 메시지큐에 발행(pub)
  2. 전파받아야하는 서비스(메시지 큐를 sub)하는 서비스는 해당 id로 메시지 발행 서비스에 API로 최신정보를 조회 

 

 

참고 링크: reflectoring.io/microservice-communication-patterns/

 

Microservice Communication Patterns

A discussion of several different communication patterns between distributed micro services.

reflectoring.io

 

 

 

 

내부 신입 개발자 등을 위해서 간단히 프로토타이핑한 java stream filter 중복제거 소스입니다.

package stream;

import lombok.Data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 내부 개발자 교육을 위한 간단한 java Stream 샘플 소스
 *  - 중복제거
 *
 * @author 엄승하
 */
public class StreamDistinct {

	public static void main(String[] args) {

		//테스트 데이터 생성
		int cnt = 10_000;
		List<TestVO> list = new ArrayList<>(cnt);
		for (int i = 1; i <= cnt; i++) {
			list.add(getTestVO(1, i)); //usreId는 동일하게 생성
		}

		//중복되지 않는 userId 추가 생성
		list.add(getTestVO(2, 10));
		list.add(getTestVO(3, 11));
		list.add(getTestVO(4, 12));

		System.out.println(String.format("\n테스트 list의 데이터 갯수:%d\n", list.size()));

		long start = System.currentTimeMillis();
		List<String> result = list.stream().filter(distinctByKey(m -> m.getUserId())).map(TestVO::getUserId).collect(Collectors.toList()); //java stream을 이용해서 유니크한 userId리스트만 모으기
		long end = System.currentTimeMillis();

		System.out.println("== Start: 중복 제거된 userId 리스트");

		for (String userId : result) {
			System.out.println(userId);
		}
		System.out.println("== End: 중복 제거된 userId 리스트");

		System.out.println("\n중복제거 stream filter 소요시간(millis): " + (end - start));

	}

	private static TestVO getTestVO(int suffixUserId, int suffixBookName) {

		TestVO vo = new TestVO();
		vo.setUserId("eom_" + suffixUserId);
		vo.setBookName("book_" + suffixBookName);

		return vo;
	}

	/**
	 * 특정 키로 중복제거
	 *
	 * @param keyExtractor
	 * @param <T>
	 * @return
	 */
	private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
		Map<Object, Boolean> map = new HashMap<>();
		return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
	}

	@Data
	private static class TestVO {

		private String userId;
		private String bookName;

	}
}

 

실행하면 아래와 같은 결과가 나옵니다.

테스트 list의 데이터 갯수:10003

== Start: 중복 제거된 userId 리스트
eom_1
eom_2
eom_3
eom_4
== End: 중복 제거된 userId 리스트

중복제거 stream filter 소요시간(millis): 2

사설 인증키를 blob으로 DB에 저장하고,

admin 시스템 만들어서 관리 자동화 하는 기능을 만드는데 가끔 잊어버립니다.(여러 랭귀지를 쓰다보니...)

 

잊어버릴때 찾기 위해서 필요한 부분을 메모 목적으로 blog에 작성해보겠습니다.

 

/**
    * private key 내용을 얻어옴
    *  -  -----BEGIN PRIVATE KEY----- 또는 -----END PRIVATE KEY----- 와 같은 가이드라인 줄은 제외하고 실제 사용하는 부분만 파일에서 가져옴
    *
    * @param privateKeyFile
    * @return
    */
private String getPrivateKeyBody(MultipartFile privateKeyFile) {

    try (BufferedReader br = new BufferedReader(new InputStreamReader(privateKeyFile.getInputStream()))) {

        String line;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            if (line.contains("PRIVATE KEY")) { //guard line은 pass
                continue;
            }
            sb.append(line);
        }

        return sb.toString();

    } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw new RuntimeException(e.getMessage(), e);
    }
}

+ Recent posts