[241129 TIL]

프로젝트 관리 심화

인메모리 저장소 및 캐싱

  • 캐싱

캐싱

  • 자주 사용되는 데이터를 더 빠른 캐시(Cache)에 저장하는 기법을 부르는 용어

캐시(Cache)

  • Cache는 본래 CPU 내부의 작은 영역으로, 정말 빈번히 접근하게 되는 데이터를 저장해두는 임시 기억 장치
  • 기본적으로 영속성을 위해 파일시스템(디스크)에 저장하고, 빠른 활용을 위해 메모리(RAM)에 저장한다면, 정말 많이 사용되는 휘발성 데이터가 캐시에 저장

캐싱의 필요성

  • 캐시의 목적과 방식을 웹 개발에 적용해, 빈번하게 접근하게 되는 데이터베이스의 데이터를 Redis 등의 인메모리 데이터베이스에 저장을 함으로서 데이터를 조회하는데 걸리는 시간과 자원을 감소시키는 기술
  • 웹 브라우저에서는 자주 바뀌지 않는 이미지 등을 브라우저 캐시에 저장해 페이지 로드를 줄이는 것도 캐싱의 일종이며, 이는 RESTful 설계 원칙 중에서 응답이 캐싱이 가능한지 명시해야 한다는 제약사항으로도 나타남

캐싱 전략

  • 고려 사항
    • 캐시 적중(Cache Hit): 캐시에 접근했을 때 찾고 있는 데이터가 있는 경우를 나타냄
    • 캐시 누락(Cache Miss): 캐시에 접근했을 때 찾고 있는 데이터가 없는 경우를 나타냄
    • 삭제 정책(Eviction Policy): 캐시에 공간이 부족할때 어떻게 공간을 확보하는지에 대한 정책
  • Cache-Aside
    • Lazy Loading이라고도 하며, 데이터를 조회할 때 항상 캐시를 먼저 확인하는 전략
    • 캐시에 데이터가 있으면 캐시에서 데이터를, 없으면 원본에서 데이터를 가져온 뒤 캐시에 저장
    • 필요한 데이터만 캐시에 보관
    • 최초로 조회할 때 캐시를 확인하기 때문에 최초의 요청은 상대적으로 오래 걸림
    • 반드시 원본을 확인하지 않기 때문에, 데이터가 최신이라는 보장이 없음
  • Write-Through
    • 데이터를 작성할때 항상 캐시에 작성하고, 원본에도 작성하는 전략
    • 캐시의 데이터 상태는 항상 최신 데이터임이 보장
    • 자주 사용하지 않는 데이터도 캐시에 중복해서 작성하기 때문에, 시간이 오래 걸림
  • Write-Behind
    • 캐시에만 데이터를 작성하고, 일정 주기로 원본을 갱신하는 방식
    • 쓰기가 잦은 상황에 데이터베이스의 부하를 줄일 수 있음
    • 캐시의 데이터가 원본에 적용되기 전 문제가 발생하면 데이터 소실의 위험성이 존재

캐싱 전략(Cache-Aside, Write-Through, Write-Behind)


'[내일배움캠프] AI 를 활용한 백엔드 아카데미 심화 과정 > TIL' 카테고리의 다른 글

MSA 개인 프로젝트 (2)  (0) 2024.12.06
MSA 개인 프로젝트 (1)  (1) 2024.12.06
Redis 명령어  (2) 2024.11.28
Docker  (0) 2024.11.27
QueryDSL  (0) 2024.11.26

[241128 TIL]

프로젝트 관리 심화

인메모리 저장소 및 캐싱

  • Redis

Redis 명령어

 

String

  • 가장 기본적인 자료형, Redis가 Java의 Map<String, String>처럼 동작한다고 생각하면서 접근하면 편함
  • 저장할 수 있는 최대 크기는 512MB
  • GET, SET
    • SET <key> <value>: key에 value 문자열 데이터를 저장, "으로 공백 구분
    • GET <key>: key에 저장된 문자열 반환
  • INCR, DECR
    • INCR key: key에 저장된 데이터를 1 증가
    • DECR key: key에 저장된 데이터를 1 감소
  • MSET, MGET
    • MSET key value [key value …]: key value의 형태로 주어진 인자들을 각 key에 value를 저장
    • MGET key [key]: 주어진 모든 key에 해당하는 데이터를 반환.

List

  • 여러 문자열 데이터를 Linked List의 형태로 보관하는 자료형
  • 스택 또는 큐 처럼 사용
  • Java의 Map<String, List<String>> 형태
  • LPUSH, RPUSH, LPOP, RPOP
    • LPUSH key value: key에 저장된 리스트의 앞쪽에 value를 저장
    • RPUSH key value: key에 저장된 리스트의 뒤쪽에 value를 저장
    • LPOP key: key에 저장된 리스트의 앞쪽에서 값을 반환 및 제거
    • RPOP key: key에 저장된 리스트의 뒤쪽에서 값을 반환 및 제거
  • LLEN, LRANGE
    • LLEN key: key에 저장된 리스트의 길이를 반환
      • 없는 Key를 대상으로 하면 0
      • 다른 자료형을 저장한 Key를 대상으로 하면 오류 발생
    • LRANGE key start end: key의 start부터 end까지 원소들을 반환
      • end가 실제 길이를 벗어나도 오류가 발생 X
      • start > end일 경우 빈 결과 반환
      • 음수의 경우 리스트의 뒤에서부터 데이터를 가져옴

Set

  • 문자열의 집합
  • 중복값 X, 순서 X
  • SADD, SREM, SMEMBERS, SISMEMBER, SCARD
    • SADD key value: key에 저장된 집합에 value를 추가
    • SREM key value: key에 저장된 집합의 value를 제거
    • SMEMBERS key: key에 저장된 집합의 모든 원소를 반환
    • SISMEMBER key value: key에 저장된 집합에 value가 존재하는지 반환
    • SCARD key: key에 저장된 집합의 크기를 반환
  • SINTER, SUNION, SINTERCARD
    • SINTER key1 key2: key1과 key2에 저장된 집합들의 교집합의 원소들을 반환
    • SUNION key1 key2: key1과 key2에 저장된 집합들의 합집합의 원소들을 반환
    • SINTERCARD number key1 [key2 ...]: number개의 key에 저장된 집합들의 교집합의 크기를 반환

Hash

  • Field - Value 쌍으로 이뤄진 자료형
  • Hash 데이터를 가져오기 위해 Key를 사용, 이후 다시 Key에 저장된 Hash 데이터에 Field - Value 쌍을 넣어주는 형식으로 동작
  • Redis 전체가 Map 이라면 Hash는 Map<String, Map<String, String>>의 형식
  • HSET, HGET, HMGET, HGETALL, HKEYS, HLEN
    • HSET key field value [field value]: key의 Hash에 field에 value를 넣음. 한번에 여러 field - value 쌍을 넣어줄 수 있음
    • HGET key field: key에 저장된 Hash의 field에 저장된 value를 반환. 없는 field의 경우 null.
    • HMGET key field [field]: key에 저장된 Hash에서 복수의 field에 저장된 value를 반환.
    • HGETALL key: key에 저장된 Hash에 저장된 field - value를 전부 반환.
    • HKEYS key: key에 저장된 Hash에 저장된 field를 전부 반환
    • HLEN key: key에 저장된 Hash에 저장된 field의 갯수를 반환

Sorted Set

  • 이름처럼 정렬된 집합
  • 기본적으로 Set과 동일하게, 유일한 값들만 유지하지만 여기에 더해 각 값들에 score라고하는 실수를 함께 보관
  • 데이터를 가져올 때, score를 바탕으로 정렬하여 값들을 가져올 수 있음
  • ZADD, ZINCRBY, ZRANK, ZRANGE, ZREVRANK, ZREVRANGE
    • ZADD key score member [score member ...]: key의 Sorted Set에 score를 점수로 가진 member를 추가, 이미 있는 member의 경우 새로운 score를 설정
    • ZRANK key member: key의 Sorted Set의 member의 순위를 오름차순 기준으로 0에서 부터 세서 반환
    • ZRANGE key start stop: key의 Sorted Set의 member들을 start 부터 stop 순위까지 오름차순 기준으로 반환
    • ZREVRANK key member: key의 Sorted Set의 member의 순위를 내림차순 기준으로 0에서 부터 세서 반환
    • ZREVRANGE key start stop: key의 Sorted Set의 member들을 start 부터 stop 순위까지 내림차순 기준으로 반환
    • ZINCRBY key increment member: key의 Sorted Set의 member의 score를 increment 만큼 증가 (음수를 전달하면 감소)

그 외 공용 명령

  • DEL, EXPIRE, EXPIRETIME
    • DEL key: key(와 저장된 데이터)를 제거
    • EXPIRE key seconds: key의 TTL(유효시각)을 seconds로 설정, seconds초가 지나면 key 제거
    • EXPIRETIME key: key가 만료되는 시각을 Unix Timestamp로 반환
  • KEYS *: 저장된 모든 Key 확인
  • FLUSHDB: 모든 Key 제거

'[내일배움캠프] AI 를 활용한 백엔드 아카데미 심화 과정 > TIL' 카테고리의 다른 글

MSA 개인 프로젝트 (1)  (1) 2024.12.06
캐싱(Caching), 캐싱의 필요성, 캐싱 전략  (1) 2024.11.29
Docker  (0) 2024.11.27
QueryDSL  (0) 2024.11.26
MSA 프로젝트  (0) 2024.11.25

[241127 TIL]

프로젝트 관리 심화

  • Docker
  • Docker-compose
  • CI/CD 파이프라인 구축
  • AWS Elastic Container Service 사용

Docker

  • 애플리케이션을 쉽게 만들고, 테스트하고, 배포할 수 있게 도와주는 소프트웨어 플랫폼
  • 애플리케이션을 컨테이너라는 가볍고 이식성 있는 패키지로 실행 가능
  • Docker 이미지는 애플리케이션을 실행하는 데 필요한 모든 것(코드, 런타임, 시스템 도구, 시스템 라이브러리 등) 포함
  • 이미지 (Image)
    • 애플리케이션과 그 실행에 필요한 모든 것을 포함하는 읽기 전용 템플릿
    • 코드, 런타임, 라이브러리, 환경 변수, 구성 파일 등 포함
    • 이미지는 컨테이너를 생성하기 위한 청사진 역할을 합니다.
  • 컨테이너 (Container)
    • 컨테이너는 Docker 이미지를 실행한 상태입니다. 이미지가 정적인 템플릿이라면, 컨테이너는 실제로 애플리케이션이 실행되는 동적인 환경입니다.
    • 컨테이너는 격리된 공간에서 애플리케이션을 실행하며, 필요한 모든 의존성을 포함합니다.
    • 하나의 시스템에서 여러 개의 컨테이너를 독립적으로 실행할 수 있습니다.
  • Dockerfile
    • Dockerfile은 Docker 이미지를 생성하기 위한 스크립트입니다. 이 파일에는 이미지를 빌드하는 데 필요한 명령어들이 포함되어 있습니다.
    • Dockerfile을 사용하면 이미지 생성 과정을 자동화하고 일관되게 만들 수 있습니다.
  • Docker Hub
    • Docker Hub는 Docker 이미지를 공유하고 저장하는 중앙 저장소입니다. 사용자는 Docker Hub에서 다양한 공개 이미지를 다운로드하거나 자신만의 이미지를 업로드할 수 있습니다.
  • 볼륨 (Volume)
    • 볼륨은 컨테이너의 데이터를 지속적으로 저장할 수 있는 메커니즘입니다. 컨테이너가 삭제되더라도 볼륨에 저장된 데이터는 유지됩니다.
    • 볼륨을 사용하면 데이터를 컨테이너와 독립적으로 관리할 수 있습니다.
  • 네트워크 (Network)
    • Docker 네트워크는 컨테이너 간의 통신을 관리하는 방식입니다. Docker는 여러 가지 네트워크 드라이버를 제공하여 다양한 네트워크 설정을 지원합니다.
    • 기본적으로 모든 컨테이너는 브리지 네트워크를 통해 통신할 수 있습니다.
    • 네트워크 종류
      • Bridge Network (브리지 네트워크)
        • 기본적으로 Docker가 컨테이너를 실행할 때 사용하는 네트워크입니다.
        • 동일한 브리지 네트워크에 연결된 컨테이너들은 서로 통신할 수 있습니다.
        • 외부 네트워크와는 NAT (내부 네트워크의 여러 장치가 하나의 공용 IP 주소를 통해 외부 네트워크와 통신할 수 있도록 IP 주소를 변환하는 기술) 를 통해 통신합니다.
        • 일반적으로 단일 호스트에서 여러 컨테이너를 연결할 때 사용됩니다.
        • 명시하지 않으면 모두 브리지 네트워크에서 실행됩니다.
        docker network create my-bridge-network
        docker run -d --name container1 --network my-bridge-network nginx
        docker run -d --name container2 --network my-bridge-network nginx
        

[241126 TIL]

MSA 학습

MSA 프로젝트 구현


ProductRepositoryImpl 코드 분석

 

  • ProductRepositoryCustom 인터페이스를 구현하여, QueryDSL을 기반으로 복잡한 검색 로직 수행
  • 주요 기능-  동적 검색 조건정렬페이징 

코드 전체 구조

@RequiredArgsConstructor
public class ProductRepositoryImpl implements ProductRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    @Override
    public Page<ProductResponseDto> searchProducts(ProductSearchDto searchDto, Pageable pageable) {
        // Pageable 객체에서 정렬 조건 추출
        List<OrderSpecifier<?>> orders = getAllOrderSpecifiers(pageable);

        // QueryDSL로 동적 검색 수행
        QueryResults<Product> results = queryFactory
                .selectFrom(product)
                .where(
                        nameContains(searchDto.getName()),              // 이름 검색 조건
                        descriptionContains(searchDto.getDescription()), // 설명 검색 조건
                        priceBetween(searchDto.getMinPrice(), searchDto.getMaxPrice()), // 가격 범위 조건
                        quantityBetween(searchDto.getMinQuantity(), searchDto.getMaxQuantity()) // 수량 범위 조건
                )
                .orderBy(orders.toArray(new OrderSpecifier[0])) // 정렬 조건 추가
                .offset(pageable.getOffset())                 // 시작 위치 설정
                .limit(pageable.getPageSize())                // 페이징 크기 설정
                .fetchResults();                              // 쿼리 실행

        // 검색 결과를 DTO로 변환
        List<ProductResponseDto> content = results.getResults().stream()
                .map(Product::toResponseDto)
                .collect(Collectors.toList());

        // 총 검색된 데이터 수
        long total = results.getTotal();

        // Page 객체로 결과 반환
        return new PageImpl<>(content, pageable, total);
    }

    // 이름 포함 조건
    private BooleanExpression nameContains(String name) {
        return name != null ? product.name.containsIgnoreCase(name) : null;
    }

    // 설명 포함 조건
    private BooleanExpression descriptionContains(String description) {
        return description != null ? product.description.containsIgnoreCase(description) : null;
    }

    // 가격 범위 조건
    private BooleanExpression priceBetween(Double minPrice, Double maxPrice) {
        if (minPrice != null && maxPrice != null) {
            return product.price.between(minPrice, maxPrice);
        } else if (minPrice != null) {
            return product.price.goe(minPrice);
        } else if (maxPrice != null) {
            return product.price.loe(maxPrice);
        } else {
            return null;
        }
    }

    // 수량 범위 조건
    private BooleanExpression quantityBetween(Integer minQuantity, Integer maxQuantity) {
        if (minQuantity != null && maxQuantity != null) {
            return product.quantity.between(minQuantity, maxQuantity);
        } else if (minQuantity != null) {
            return product.quantity.goe(minQuantity);
        } else if (maxQuantity != null) {
            return product.quantity.loe(maxQuantity);
        } else {
            return null;
        }
    }

    // 정렬 조건 생성
    private List<OrderSpecifier<?>> getAllOrderSpecifiers(Pageable pageable) {
        List<OrderSpecifier<?>> orders = new ArrayList<>();

        // Pageable의 정렬 조건을 QueryDSL의 OrderSpecifier로 변환
        if (pageable.getSort() != null) {
            for (Sort.Order sortOrder : pageable.getSort()) {
                // 정렬 방향 결정 (ASC/DESC)
                com.querydsl.core.types.Order direction = sortOrder.isAscending() 
                    ? com.querydsl.core.types.Order.ASC 
                    : com.querydsl.core.types.Order.DESC;

                // 정렬 필드에 따른 OrderSpecifier 추가
                switch (sortOrder.getProperty()) {
                    case "createdAt":
                        orders.add(new OrderSpecifier<>(direction, product.createdAt));
                        break;
                    case "price":
                        orders.add(new OrderSpecifier<>(direction, product.price));
                        break;
                    case "quantity":
                        orders.add(new OrderSpecifier<>(direction, product.quantity));
                        break;
                    default:
                        break;
                }
            }
        }

        return orders;
    }
}

searchProducts 메서드

 

1. 페이징 및 정렬 처리

 

List<OrderSpecifier<?>> orders = getAllOrderSpecifiers(pageable);

 

 

  • getAllOrderSpecifiers(pageable):
    • QueryDSL의 정렬 객체(OrderSpecifier)를 생성하는 메서드
    • Pageable의 정렬 조건에 따라 QueryDSL에서 사용할 정렬 정보를 동적으로 생성
private List<OrderSpecifier<?>> getAllOrderSpecifiers(Pageable pageable) {
    List<OrderSpecifier<?>> orders = new ArrayList<>();

    if (pageable.getSort() != null) {
        for (Sort.Order sortOrder : pageable.getSort()) {
            com.querydsl.core.types.Order direction = sortOrder.isAscending() 
                ? com.querydsl.core.types.Order.ASC 
                : com.querydsl.core.types.Order.DESC;

            switch (sortOrder.getProperty()) {
                case "createdAt":
                    orders.add(new OrderSpecifier<>(direction, product.createdAt));
                    break;
                case "price":
                    orders.add(new OrderSpecifier<>(direction, product.price));
                    break;
                case "quantity":
                    orders.add(new OrderSpecifier<>(direction, product.quantity));
                    break;
                default:
                    break;
            }
        }
    }

    return orders;
}
  • Pageable 객체의 Sort 정보를 기반으로 QueryDSL의 OrderSpecifier 생성
  • 정렬 속성(property)에 따라 createdAt, price, quantity 필드의 정렬 조건 추가

 

2. QueryDSL 검색 쿼리

QueryResults<Product> results = queryFactory
        .selectFrom(product)
        .where(
                nameContains(searchDto.getName()),
                descriptionContains(searchDto.getDescription()),
                priceBetween(searchDto.getMinPrice(), searchDto.getMaxPrice()),
                quantityBetween(searchDto.getMinQuantity(), searchDto.getMaxQuantity())
        )
        .orderBy(orders.toArray(new OrderSpecifier[0]))
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetchResults();

 

  • queryFactory.selectFrom(product)
    • QueryDSL로 Product 엔터티를 검색
  • where(...)
    • 검색 조건을 동적으로 추가
    • 각 조건은 BooleanExpression 객체로 반환되며, null인 경우 무시
  • orderBy(orders.toArray(...))
    • 정렬 조건 적용
  • offset(pageable.getOffset()) & limit(pageable.getPageSize())
    • 페이징 처리: 요청한 페이지의 시작 위치(offset)와 크기(limit)를 설정

3. 결과 처리

List<ProductResponseDto> content = results.getResults().stream()
        .map(Product::toResponseDto)
        .collect(Collectors.toList());
long total = results.getTotal();

return new PageImpl<>(content, pageable, total);
  • getResults(): 조회된 Product 객체 리스트.
  • toResponseDto(): 각 Product 엔터티를 ProductResponseDto로 변환.
  • getTotal(): 전체 검색 결과 수.
  • PageImpl:
    • Spring Data의 Page 구현체로, 결과 데이터(content), 페이징 정보(pageable), 총 데이터 수(total)를 포함.

[241125 TIL]

MSA 학습

MSA 프로젝트 만들어보기

  • 강의 마무리
  • 프로젝트

MSA 프로젝트

 

 

 

 


[241122 TIL]

MSA 학습

  • 보안구성
  • Config
  • 분산추적
  • 이벤트 드리븐

보안구성

  • 마이크로서비스 아키텍처에서는 각 서비스가 독립적으로 배포되고 통신하기 때문에 보안이 매우 중요
  • 데이터 보호, 인증 및 권한 부여, 통신 암호화 등을 통해 시스템의 보안성을 확보해야 함

✏️

jwt - 변조, 수정의 보안을 말하는거 즉, 데이터 무결성을 확인 페이로드 안에 데이터(정보) 확인을 못하게 암호화한다를 뜻하는 게 아님

gateway 만 알고 나머지는 사용자가 접근못하게 방화벽 처리 19091로만 접속가능하게


Spring Cloud Config

  • 분산 시스템 환경에서 중앙 집중식 구성 관리를 제공하는 프레임워크
  • 애플리케이션의 설정을 중앙에서 관리하고, 변경 사항을 실시간으로 반영
  • Git, 파일 시스템, JDBC 등 다양한 저장소를 지원
  • 중앙 집중식 구성 관리: 모든 마이크로서비스의 설정을 중앙에서 관리
  • 환경별 구성: 개발, 테스트, 운영 등 환경별로 구성을 분리하여 관리 가능
  • 실시간 구성 변경: 설정 변경 시 애플리케이션을 재시작하지 않고도 실시간으로 반영할 수 있음

✏️

Config 서버가 native 프로필로 로컬 파일 시스템을 사용할 경우, 파일 변경을 자동으로 감지하지 않음

그래서 config 가 변경되면 config 서버는 재실행해줘야함!


분산 추적

  • 분산 시스템에서 서비스 간의 요청 흐름을 추적하고 모니터링하는 방법
  • 각 서비스의 호출 관계와 성능을 시각화하여 문제를 진단하고 해결할 수 있도록 도움
  • 트레이스(Trace) : 하나의 요청이 시작부터 끝까지 각 서비스를 거치는 전체 흐름을 나타냄
  • 스팬(Span) : 분산 추적에서 가장 작은 단위로, 특정 서비스 내에서의 개별 작업 또는 요청을 나타냄
  • 컨텍스트(Context): 요청이 서비스 간에 전달될 때 함께 전파되어, 각 서비스가 요청의 전체 흐름에 대한 정보를 가질 수 있게함
  • 필요성
    • 마이크로서비스 아키텍처에서는 여러 서비스가 협력하여 하나의 요청을 처리
    • 서비스 간의 복잡한 호출 관계로 인해 문제 발생 시 원인을 파악하기 어려울 수 있음
    • 분산 추적을 통해 각 서비스의 호출 흐름을 명확히 파악하고, 성능 병목이나 오류를 빠르게 진단할 수 있음

이벤트 드리븐 아키텍처

  • 시스템에서 발생하는 이벤트(상태 변화나 행동)를 기반으로 동작하는 소프트웨어 설계 스타일
  • 이벤트는 비동기적으로 처리되며, 서비스 간의 느슨한 결합을 통해 독립적으로 동작할 수 있게함
  • 이벤트: 시스템 내에서 발생하는 상태 변화나 행동을 나타내는 메시지
  • 이벤트 소스: 이벤트를 생성하여 이벤트 버스에 전달하는 역할
  • 이벤트 핸들러: 이벤트를 수신하여 처리하는 역할
  • 이벤트 버스: 이벤트 소스와 이벤트 핸들러 간의 메시지 전달을 중개
  • 장점
    • 느슨한 결합
      • 서비스 간의 강한 종속성을 제거하여 독립적인 개발과 배포가 가능
      • 이벤트 기반 통신을 통해 서비스 간의 결합도 낮춤
    • 확장성
      • 수평 확장이 용이하여 대규모 시스템에서 유용
      • 이벤트 프로듀서와 컨슈머를 독립적으로 확장 가능
    • 비동기 처리
      • 이벤트를 비동기적으로 처리하여 시스템의 응답성 향상
      • 요청과 응답을 비동기적으로 처리하여 성능 최적화
  • 단점
    • 복잡성 증가
      • 이벤트 기반 통신으로 인해 시스템의 복잡성 증가 가능
      • 이벤트 흐름과 상태 관리를 체계적으로 설계 필요
    • 장애 전파
      • 이벤트 실패 시 다른 서비스로 장애가 전파될 수 있음
      • 이벤트 재처리 및 장애 복구 메커니즘 구현 필요

+ Recent posts