영속성 컨텍스트란?

  • Server side와 Database 사이에 엔티티를 저장하는 논리적인 영역이라고 할 수 있다. 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 보관하고 관리한다.
  • 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어진다. 그리고 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있고 영속성 컨텍스트를 관리할 수 있다.

영속성 컨텍스트가 엔티티를 관리하면 얻게되는 장점

1차 캐시

  • Map 객체로 저장
    • 엔티티를 식별자 값(@Id 매핑)으로 구분한다. Key-value로 관리하는데 이때 key 값이 @Id 값이 된다.
  • 식별자 값 필요
    • 영속상태의 엔티티는 반드시 식별자 값이 있어야 한다.
  • 1차 캐시를 사용하면 한 가지 기능을 두개의 트랜잭션으로 처리할 경우?
    • OSIV를 사용하여 영속성 컨텍스트의 데이터를 공유한다.
  • 2차 캐시는 언제 사용하나?
    • 하이버 네이트가 지원하는 캐시는 크게 3가지가 있음
      • 엔티티 캐시
        • 엔티티 단위로 캐시한다. 식별자로 엔티티를 조회하거나 컬렉션이 아닌 연관된 엔티티를 로딩할 때 사용한다.
      • 쿼리 캐시
        • 쿼리와 파라미터 정보를 키로 사용해서 캐시한다.
      • 컬렉션 캐시
        • 엔티티와 연관된 컬렉션을 캐시한다. 컬렉션이 엔티티를 담고 있으면 식별자 값만 캐시한다.
        • 문제는 쿼리 캐시나 컬렉션 캐시만 사용하고 대상 엔티티에 엔티티 캐시를 적용하지 않으면 성능상 심각한 문제가 발생할 수 있다.

동일성 보장

  • 동일한 객체 반환
    • Collection에서 객체를 빼오듯이 같은 객체를 반환하게 되면 새로운 객체가 나오는 것이 아니라 동일한 객체가 반환된다.
  • JPQL을 사용하게 되면 기존 영속성 컨텍스트의 데이터는 갱신되나?
    • 변경감지
      • 자동 Update
        • 영속성 상태의 객체는 객체의 데이터가 변경이 되면, 자동 update 된다.
        • EntityManager에서 flush가 되고, commit이 된다.
          • flush가 되는 시점
            • 강제 flush 호출
            • 트랜잭션 종료시
            • JPQL 쿼리 실행
              • JPQL은 실제 DB side에서 데이터를 가져오기 때문에 동기화를 위하 JPQL 쿼리가 실행 전에 flush가 된다.

트랜잭션으로 인한 쓰기 지연

  • 쓰기 지연
    • 영속성 컨텍스트는 트랜잭션 범위 안에서 동작한다. 그래서 트랜잭션이 끝나야 commit이 이뤄지고 반영된다.

Lazy 로딩

  • 엔티티와 관계가 맺어진 엔티티의 데이터를 실제로 접근하여 자식 객체가 필요한 시점에 로딩합니다. 이를 통해 불필요한 데이터베이스 쿼리를 줄이고, 성능을 최적화할 수 있습니다.

Checked Exception과 트랜잭션은 아무 상관 없다

  • @Transactional은 기본적으로 unchecked exception에 대해서만 rollback을 수행한다고 알고있었다. 하지만 엄밀히 말하자면 그것은 오해라고 할 수 있다.
  • 트랜잭션이, 메세지 트랜잭션을 말하는 건지, 데이터베이스 트랜잭션을 말하는 건지 데이터베이스의 트랜잭션을 말하는 것이라도 checked exceptoin이든 unchecked exceptoin이든 에 따라 롤백을 할지 안할지는 전적으로 개발자에게 달려 있다.
    • 즉, 자바 프로그래밍 언어에서는 위와 같은 규칙은 없다.
  • Exception 종류에 따라 롤백 유무가 다르다고 정리된 이유는, 자바의 유명한 프레임워크 Spring Framework 의 트랜잭션 처리에서 비롯 됐기 때문
  • 스프링에서는 기본적으로는 런타임 예외 같은경우 바로 롤백을 한다.
  • 그러나 이 상황도 개발자가 옵션을 어떻게 설정하느냐에 따라 충분히 바꿀 수 있는 요소이다. 예를들어 런타임 자식 예외클래스 중에 몇몇을 골라서 롤백을 하지 않도록 설정이 가능하다.
  • 따라서 정리하자면, 자바를 스프링과 동일시 한다면 위의 정리는 맞지만, 자바 프로그래밍에서만 생각한다면 checked exceptoin과 unchecked exceptoin과 트랜잭션 롤백 유무는 연관이 없는 것이다. 그리고 예외발생이 트랜잭션 처리에 대해서는 checked 든 unchecked 든 전적으로 개발자 마음에 달려있다고 볼 수 있다.

'Computer science > Spring' 카테고리의 다른 글

@Controlleradvice,@ExceptionHandler  (0) 2023.06.21
@Transactional  (0) 2023.06.15
Filter 와 Interceptor  (0) 2023.06.15
OSIV  (0) 2023.06.08
N + 1 문제  (0) 2023.06.08

Filter

  • DispatcherServlet 이전에 실행된다.
  • 모든 요청을 처리하는 DispatcherServlet 앞단에 실행되기 때문에 모든 일괄적인 요청에 대해 변경하거나 유효성 검사를 한곳에서 처리할 수 있다.
  • 스프링 빈이 아니므로 web.xml에 등록한다.
  • Filter는 빈으로 등록되지 않아서 주입을 받을 수는 없지만 애플리케이션 컨텍스트의 생성된 빈들로 주입 받을 수는 있다.(SpringSecurity Filter)

Interceptor

  • Interceptor는 DispatcherServlet 다음에 실행되는 스프링 내부 영역으로 Application Context 내에서 관리되므로 Bean으로 등록할 수 있다.
  • Interceptor는 특정 HandlerMapping에 종속되어 다양한 전략의 Interceptor를 생성할 수 있다.
  • Interceptor는 @ControllerAdvice @ExceptionHandler를 이용하여 예외 처리가 가능하다.

'Computer science > Spring' 카테고리의 다른 글

@Transactional  (0) 2023.06.15
영속성 컨텍스트  (0) 2023.06.15
OSIV  (0) 2023.06.08
N + 1 문제  (0) 2023.06.08
스프링 프레임워크 특징  (0) 2023.06.08

CompletableFuture

  • 2014년에 발표된 java 8에서 도입
  • 비동기 프로그래밍 지원
  • Lambda, Method reference 등 java 8의 새로운 기능 지원

Method reference

  • ::연산자를 이용하여 함수에 대한 참조를 간결하게 표현
  • method reference
  • static method reference
  • instance method reference
  • constructor method reference
@RequiredArgsConstructor
public static class Person{
    @Getter
    private final String name;

    public Boolean compareTo(Person o){
        return o.name.compareTo(name) > 0;
    }
}

public static void print(String name){
    System.out.println(name);
}

public static void main(String[] args){
    var target = new Person("f");
    Consumer<String> staticPrint = MethodReferenceExample::print;

    Stream.of("a","b","c","d")
            .map(Person::new)// constructor reference
            .filter(target::compareTo)// method reference
            .map(Person::getName)// instance method reference
            .forEach(staticPrint);// static method reference
}

CompletableFuture 클래스

public class CompletableFuture<T> implements
	Future<T>, CompletionStage<T>

Future

  • 비동기적인 작업을 수행
  • 해당 작업이 완료되면 결과를 반환하는 인터페이스

CompletionStage

  • 비동기적인 작업을 수행
  • 해당 작업이 완료되면 결과를 처리하거나 다른 CompletionStage를 연결하는 인터페이스

ExecutorService

  • 쓰레드 풀을 이용하여 비동기적으로 작업을 실행하고 관리
  • 별도의 쓰레드를 생성하고 관리하지 않아도 되므로, 코드를 간결하게 유지 가능
  • 쓰레드 풀을 이용하여 자원을 효율적으로 관리

ExcutorService 메서드

  • execute
    • Runnable 인터페이스를 구현한 작업을 쓰레드 풀에서 비동기적으로 실행
  • submit
    • Callable 인터페이스를 구현한 작업을 쓰레드 풀에서 비동기적으로 실행하고, 해당 작업을 결과를 Future<T> 객체로 반환
  • shutdown
    • ExcutorService를 종료. 더 이상 task를 받지 않는다.

Excutors - ExecutorService 생성

  • newSingleThreadExecutor
    • 단일 쓰레드로 구성된 쓰레드 풀을 생성. 한 번에 하나의 작업만 실행
  • newFixedThreadPool
    • 고정된 크기의 쓰레드 풀을 생성. 크기는 인자로 주어진 n과 동일
  • newCachedThreadPool
    • 사용 가능한 쓰레드가 없다면 새로 생성해서 작업을 처리하고, 있다면 재사용. 스레드가 일정 시간 사용되지 않으면 회수
  • newScheduledThreadPool
    • 스케줄링 기능을 갖춘 고정 크기의 쓰레드 풀을 생성. 주기적이거나 지연이 발생하는 작업을 실행
  • newWorkStealingPool
    • work steal 알고리즘을 사용하는 ForkJoinPool을 생성

Future의 isDone, isCancelled, get, cancel

  • future의 상태를 반환
  • isDone
    • task가 완료되었다면, 원인과 상관없이 true 반환
  • isCancelled
    • task가 명시적으로 취소된 경우, true 반환
  • get
    • 결과를 구할 때까지 thread가 계속 block
    • future에서 무한 루프나 오랜 시간이 걸린다면 thread가 blocking 상태 유지
  • get(long timeout, TimeUnit unit)
    • 결과를 구할 때까지 timeout동안 thread가 block
    • timeout이 넘어가도 응답이 반환되지 않으면 TimeoutException 발생
  • cancel(boolean mayInterruptIfRunnig)
    • future의 작업 실행을 취소
    • 취소할 수 없는 상황이라면 false 반환
    • mayInterruptIfRunning이 false라면 시작하지 않은 작업에 대해서만 취소

Future 인터페이스의 한계

  • cancel을 제외하고 외부에서 future를 컨트롤 할 수 없다.
  • 반환된 결과를 get() 해서 접근하기 때문에 비동기 처리가 어렵다
  • 완료되거나 에러가 발생했는지 구분하기 어렵다

Blocking의 종류

  • blocking은 thread가 오랜 시간 일을 하거나 대기하는 경우 발생
  • CPU-bound blocking
    • thread가 대부분의 시간 cpu 점유
    • 연산이 많은 경우
    • 성능을 올리기 위해 추가적인 코어를 투입
  • IO-bound blocking
    • thread가 대부분의 시간을 대기
    • 파일 읽기,쓰기 network 요청 처리, 요청 전달 등
    • 성능을 올리기 위해 IO-bound non-blocking 가능하다

Blocking의 전파

  • 하나의 함수에서 여러 함수를 호출하기도 하고, 함수 호출은 중첩적으로 발생
  • callee는 caller가 되고 다른 callee를 호출
  • blocking한 함수를 하나라도 호출한다면 caller는 blocking된다.
  • 함수가 non-blocking 하다라고 이야기 하려면 모든 함수가 non-blocking이어야 한다.
  • 따라서 I/O bound blocking또한 발생하면 안된다.\

동기 Blocking I/O

  • recvfrom을 호출(시스템콜)
  • blocking socket을 이용해서 read/write를 수행
  • 쓰레드가 block된다 (wait queue 에서 기다림)

동기 Non-blocking I/O

  • recvfrom을 주기적으로 호출(시스템콜)
  • non-blocking socket을 이용해서 read/write 수행
  • 작업이 완료되지 않았다면 EAGAIN/EWOULDBLOCK 에러 반환

비동기 Non-blocking I/O

  • aio_read를 호출
  • 작업이 완료되면 커널이 완료 시그널을 보내거나 callback을 호출
    • 요청을 했던 스레드와 다른 스레드에게 전달

'웹프로그래밍 > WebFlux' 카테고리의 다른 글

Reactive Programming  (0) 2023.07.21
Reactive manifesto  (0) 2023.07.05
CompletionStage 인터페이스  (0) 2023.07.03
Future 인터페이스  (0) 2023.06.13
동기 / 비동기 / Blocking / Non-blocking (함수 호출 관점)  (0) 2023.06.01

OSIV

  • open session in view
  • view 영역까지 session 영역이 열어둔다는 의미

Open Session? View 영역?

  • Open Session은 세션 영역을 연다는 의미
  • 정확히는 영속성 컨텍스트의 영역을 의미한다.
  • View 영역은 일반적으로 생각하는 Controller 영역이나 뷰 영역(jsp, thymeleaf) 영역을 의미한다.
  • Controller나 뷰 영역에도 영속성 컨텍스트를 유지한다는 것

영속성 컨텍스트의 범위는?

  • 영속성 컨텍스트는 엔티티 매니저와 연관이 깊다.
  • 엔티티 매니저가 생성되는 시점에 생성되고 엔티티 매니저가 종료되는 시점에 소멸한다. 트랜잭션 매니저를 별도로 구현하지 않고 @Transactional 애너테이션을 사용한다면 애너테이션이 붙어있는 메서드 영역 내에서 영속성 컨텍스트가 생성되고 소멸되는 것으로 이해할 수 있다.

영속성 컨텍스트의 범위를 확장해야 하는 이유?

  • @Transactional 애너테이션은 보통 Service 레이어에서 많이 사용하는데 Service 레이어가 아닌 컨트롤러나 뷰 영역에서도 영속성 컨텍스트가 필요한 경우가 있을 수 있지만 많이 경험하지 못하였을 것이다.
    • 대부분 DTO를 사용하기 때문
  • 컨트롤러 영역에서 필요한 DTO 모델을 생성하여 Dao에서 내려받은 결과값을 DTO에 바인딩 해주기 때문에 OSIV를 고려해야하는 경우가 많지 않았을 것이다.
  • 하지만 몇년 전까지만 하더라도 JPA를 사용하면 엔티티를 그대로 모델로 사용하는 경우가 많았고 컨트롤러, 뷰 영역에서도 엔티티를 조작하는 경우가 발생

뷰 영역에서 엔티티를 수정할 경우 DB반영이 될 까?

  • 트랜잭션을 사용하는 서비스 계층이 끝날 때 트랜잭션이 커밋되면서 이미 플러시를 했기 때문에 스프링이 제공하는 OSIV 서블릿 필터나 OSIV 스프링 인터셉터는 요청이 끝나면 플러시를 호출하지 않고 em.close()로 영속성 컨텍스트만 종료해 버리므로 플러시가 일어나지 않는다. 만약 뷰 영역에서 em.plush()를 강제로 호출해도 트랜잭션 범위 밖이라는 예외가 발생하게 될 것이다.

OSIV On

  • 최초 데이터베이스 커넥션 시작 시점부터 api 응답이 끝날 때까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지한다.
    • api컨트롤러에서 지연 로딩이 가능
  • 지연로딩은 영속성 컨텍스트가 살아있어야 가능하고, 영속성 컨텍스트는 기본적으로 데이터베이스 커넥션을 유지한다. 이것이 큰 장점
  • 너무 오랜시간 데이터베이스 커넥션 리소스를 사용하기 때문에 실시간 트랙픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있다.

OSIV Off

  • 트랜잭션을 종료하는 시점에서 영속성 컨텍스트를 닫고, 데이터 베이스 커넥션도 반환한다. 따라서 커넥션 리소스를 낭비하지 않는다.
  • 지연로딩을 트랜잭션 내부에서 처리해야 한다.
  • 트랜잭션이 끝나기 전에 모든 지연로딩을 강제로 호출해 두어야 한다.

'Computer science > Spring' 카테고리의 다른 글

@Transactional  (0) 2023.06.15
영속성 컨텍스트  (0) 2023.06.15
Filter 와 Interceptor  (0) 2023.06.15
N + 1 문제  (0) 2023.06.08
스프링 프레임워크 특징  (0) 2023.06.08

N + 1 문제란?

  • 연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 되는 문제.

왜 발생하는 것인가?

  • jpaRepository에 정의한 인터페이스 메서드를 실행하면 JPA는 메서드 이름을 분석하여 JPQL을 생성하여 실행하게된다.
  • JPQL은 SQL을 추상화한 객체지향 쿼리 언어로서 특정 SQL에 종속되지 않고 엔티티 객체와 필드 이름을 가지고 쿼리를 한다.
  • 그렇기 때문에 JPQL은 findAll()이란 메서드를 수행하였을 때 해당 엔티티를 조회하는 select * from table 쿼리만 실행하게 된다.
  • JPQL 입장에서는 연관관계 데이터를 무시하고 해당 엔티티 기준으로 쿼리를 조회
  • 그렇기 때문에 연관된 엔티티 데이터가 필요한 경우, FetchType으로 지정한 시점에 별도로 호출하게 된다.

해결방안

Fetch join

  • 사용자가 원하는것은 join 코드 일 것
  • 최적화된 쿼리를 우리가 직접 사용할 수 있음 → Fetch join
  • JpaRepository에서 제공해주는 것은 아니고 JPQL로 작성해야 한다.
  • ex)
@Query("select o from Owner o join fetch o.cats")
List<Owner> findAllJoinFetch();
  • 연관관계의 연관관계가 있을 경우에도 하나의 쿼리 문으로 표현할 수 있으므로 유리하다.
  • 데이터 호출 시점에 모든 연관 관계의 데이터를 가져오기 때문에 FetchType을 Lazy로 해놓는 것이 무의미해짐

EntityGraph

  • @EntityGraph의 attributePaths에 쿼리 수행시 바로 가져올 필드명을 지정하면 Lazy가 아닌 Eager 조회로 가져오게 된다.
  • Fetch join과 동일하게 JPQL을 사용하여 query문을 작성하고 필요한 연관관계를 EntityGraph에 설정하면 된다.
  • Fetch join과는 다르게 join문이 outer join으로 실행된다. ( Fetch join == inner join)
  • ex
@EntityGraph(attributePaths = "cats")
@Query("select o from Owner o")
List<Owner> findAllEntityGraph();

컬렉션 Fetch join

  • 컬렉션 fetch join은 일대다 관계에서 사용할 수 있으며 데이터가 많아질 수 있다.
    • DISTINCT로 중복 제거가 가능
    • SQL의 DISTINCT
      • ROW가 완벽히 일치해야 중복 제거
    • JPQL의 DISTINCT
      • SQL의 DISTINCT 기능 뿐만 아니라 동일한 엔티티면 중복 제거
        • application단에서 엔티티 중복을 제거한다.

Fetch join의 한계와 극복

  • 컬렉션을 fetch join 하면 페이징 API를 사용할 수 없다.
    • 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인을 해도 페이징이 가능하다.
    • HIbernate는 경고로그를 남기며 메모리에서 페이징을 한다.
    • 전체 데이터를 가져와 메모리에 올려놓고, 메모리에서 N개씩 데이터를 결과로 보여준다. 자칫 OutOfMemory가 발생할 수 있고, 성능에 영향을 줌

xxxToOne 관계를 모두 페치조인 한다.

  • toOne관계는 row수를 증가시키지 않기 때문
  • 컬렉션은 지연로딩으로 조회

@컬렉션은 지연로딩으로 설정 → Batch_fecth_size를 사용

  • hibernate.default.batch_fetch_size(글로벌 설정)
  • @BatchSize(디테일 설정)
  • 프록시 객체를 설정한 size만큼 in 쿼리로 조

'Computer science > Spring' 카테고리의 다른 글

@Transactional  (0) 2023.06.15
영속성 컨텍스트  (0) 2023.06.15
Filter 와 Interceptor  (0) 2023.06.15
OSIV  (0) 2023.06.08
스프링 프레임워크 특징  (0) 2023.06.08

스프링 프레임워크란?

객체지향적으로 설계한 POJO 유지하며 애플리케이션을 쉽고 효과적으로 개발할 수 있도록 지원하는 프레임워크

  • 스프링을 사용하면 개발자는 비즈니스 로직에만 집중할 수 있다

POJO

  • Plain Old Java Object
  • 특정 기술과 환경에 종속적이지 않은 순수한 자바 객체

PSA

  • Portable Service Abstraction
  • 추상화 계층을 사용하여 어떤 기술을 내부에 숨기고 사용자에게 편의성을 제공하며, 이 기술을 다른 기술 스택으로 간편하게 바꿀 수 있는 확장성을 가진 서비스를 의미한다.
  • ex) Jpa 인터페이스를 이용하여 orm기술을 사용

IoC

  • Inversion of Control - 제어의 역전
  • 스프링에서는 일반적인 자바 객체를 new로 생성하여 개발자가 관리하는 것인 아닌 Spring Container에서 객체를 관리하고 제어한다.
  • 개발자 → 프레임워크로 제어권한이 넘어감

DI

  • Dependency injection - 의존성 주입
  • 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜 준다.
  • 장점
    • 유지보수가 쉬우며 테스트가 용이함
    • 객체간 결합도가 낮아진다.
    • 코드의 재사용성과 유연성이 높아진다.

AOP

  • 관점 지향 프로그래밍
  • 관점 지향
    • 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화
  • AOP를 사용하여, logging, transaction관리, security에서의 적용 등 AspectJ와 같이 완벽하게 구현된 AOP와 통합하여 사용이 가능

'Computer science > Spring' 카테고리의 다른 글

@Transactional  (0) 2023.06.15
영속성 컨텍스트  (0) 2023.06.15
Filter 와 Interceptor  (0) 2023.06.15
OSIV  (0) 2023.06.08
N + 1 문제  (0) 2023.06.08

Caller와 Callee

  • 함수가 다른 함수를 호출하는 상황
  • Caller : 호출하는 함수
  • Callee : 호출 당하는 함수

함수형 인터페이스

  • 함수형 프로그래밍을 지원하기 위해 java 8부터 도입
  • 1개의 추상 메서드를 갖고 있는 인터페이스
  • 함수를 일급 객체로 사용할 수 있다.
    • 함수를 변수에 할당하거나 인자로 전달하고 반환값으로 사용 가능
  • Function, Consumer, Supplier, Runnable 등
  • 함수형 인터페이스를 구현한 익명 클래스를 람다식으로 변경 가능
  • 함수형 인터페이스는 호출한 쓰레드에서 실행된다

Type A

@Slf4j
public class A {
    public static void main(String[] args) {
        log.info("start main");
        var result = getResult();
        var nextValue = result + 1;
        assert nextValue == 1;
        LOGGER.info("Finish main");
    }
    public static int getResult(){
        LOGGER.info("Start getResult");
        try{
            Thread.sleep(1000);
        } catch (InterruptedException e){
            throw new RuntimeException(e);
        }
        var result = 0;
        try{
            return result;
        }finally {
            LOGGER.info("Finish getResult");
        }
    }
  • getResult를 호출하고 값을 ++ 시키고 1과 같은지 체크한다.
  • 이때 1초를 sleep 후 반환하기 때문에 main함수는 최소 1초 이상의 시간이 걸리게 된다.

Type B

@Slf4j
public class B {
    public static void main(String[] args) {
        log.info("start main");
        getResult(new Consumer<Integer>(){
            @Override
            public void accept(Integer integer){
                var nextValue = integer + 1;
                assert nextValue == 1;
            }
        });
        log.info("Finish main");
    }
    public static void getResult(Consumer<Integer> cb){
        log.info("Start getResult");
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            throw new RuntimeException(e);
        }
        var result = 0;
        cb.accept(result);
        log.info("Finish getResult");
    }
  • Consumer라는 함수형 인터페이스를 인자로 넘긴다.
  • result를 반환하지 않고 accept함수를 실행시킨다.
  • A 에서는 main이 직접 getResult()를 호출하지만 B에서는 콜백을 통해 행위를 위임한다.

Type A

  • main이 getResult의 결과에 관심이 있다.
  • main은 결과를 이용해서 다음 코드를 실행한다.

Type B

  • main은 getResult의 결과에 관심이 없다.
  • getResult는 결과를 이용해서 함수형 인터페이스를 실행한다.

동기

  • caller는 callee의 결과에 관심이 있다.
  • caller는 결과를 이용해서 action을 수행한다.

비동기

  • caller는 callee의 결과에 관심이 없다.
  • callee는 결과를 이용해서 callback을 수행한다.

Blocking / Non-blocking

Type A

  • main은 getResult()가 결과를 리턴하기 전까지 아무것도 할 수 없다.
  • main은 getResult()가 완료될 때까지 대기한다.

Type B

  • main은 getResult가 결과를 구하고 callback을 실행하기 전까지 아무것도 할 수 없다.
  • main은 getResult가 완료될 때까지 대기한다.

Blocking

  • callee를 호출한 후, callee가 완료되기 전까지 caller가 아무것도 할 수 없다.
  • 제어권을 callee가 가지고 있다.
  • caller와 다른 별도의 thread가 필요하지 않다.

Non-Blocking

  • callee를 호출한 후, callee가 완료되지 않더라도 caller는 본인의 일을 할 수있다.
  • 제어권을 caller가 가지고 있다.
  • caller와 다른 별도의 thread가 필요하다.
  동기 비동기
Blocking caller는 아무것도 할 수 없는 상태가 된다. 결과를 얻은 후 직접 처리한다. caller는 아무것도 할 수 없는 상태가 된다. 결과는 callee가 처리한다.
Non-blocking caller는 자기 할 일을 할 수 있다. 결과를 얻은 후 직접 처리한다. caller는 자기 할 일을 할 수 있다. 결과는 callee가 처리한다.

 

'웹프로그래밍 > WebFlux' 카테고리의 다른 글

Reactive Programming  (0) 2023.07.21
Reactive manifesto  (0) 2023.07.05
CompletionStage 인터페이스  (0) 2023.07.03
Future 인터페이스  (0) 2023.06.13
blocking과 non-blocking의 차이(I/O 관점)  (0) 2023.06.12

Apache kafka

  • Apache Software Foundation의 Scalar 언어로 된 오픈 소스 메시지 브로커 프로젝트
    • 메시지 브로커
      • Publisher로 부터 전달받은 메시지를 Subscriber로 전달해주는 중간 역할
      • 응용 소프트웨어 간에 메시지를 교환할 수 있게 한다.
      • 이 때 메시지가 적재되는 공간을 Message Queue라고 하며 메시지의 그룹을 Topic이라고 한다.
  • 링크드인에서 개발, 2011년 오픈 소스화
    • 2014년 11월 링크드인에서 kafka를 개발하던 엔지니어들이 kafka개발에 집중하기 위해 Confluent라는 회사 창립
  • 실시간 데이터 피드를 관리하기 위해 통일된 높은 처리량, 낮은 지연 시간을 지닌 플랫폼 제공
  • Producer(송신자)/Consumer(수신자) 분리
  • 메시지를 여러 Consumer에게 허용
  • 높은 처리량을 위한 메시지 최적화
  • Scale-out 가능(클러스터)
  • Eco-system

Kafka Broker

  • 실행 된 kafka 애플리케이션 서버
  • 3대 이상의 Broker Cluster 구성
  • Zookeeper 연동
    • 역할 : 메타데이터 저장
    • Controller 정보 저장
  • n개 Broker 중 1대는 Controller 기능 수행
    • Controller 역할
      • 각 Broker에게 담당 파티션 할당 수행
      • Broker 정상 동작 모니터링

Ecosystem

  • kafka와 데이터를 주고받기 위해 사용하는 Java Library
    • kafka-clients
  • Producer, Consumer, Admin, Stream 등 kafka 관련 API 제공
  • 다양한 3rd party library 존재 : C/C++, Node.js, Python, .NET

Kafka 서버 기동

- Zookeeper 및 kafka 서버 구동
    - $KAFKA_HOME\bin\windows\zookeeper-server-start.bat $KAFKA_HOME\config\zookeeper.properties
    - $KAFKA_HOME\bin\windows\kafka-server-start.bat $KAFKA_HOME\config\server.properties
- Topic 생성
    - $KAFKA_HOME\bin\window\kafka-topics.bat —-create -—topic quickstart-events —-bootstrap-server loacalhost:9092 \ -—partitions 1
- Topic 목록 확인
    - $KAFKA_HOME\bin\window\kafka-topics.bat -—bootstrap-server localhost:9092 —list
- Topic 정보 확인
    - $KAFKA_HOME\bin\window\kafka-topics.bat -—describe -—topic quickstart-events -—bootstrap-server localhost:9092
- 메시지 생산
    - $KAFKA_HOME\bin\window\kafka-console-producer.bat --broker-list localhost:9092 —-topic quickstart-events
- 메시지 소비
    - $KAFKA_HOME\bin\window\kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic quickstart-events \ --from-beginning
  • topic생성 후 목록과 정보를 확인해봄

  • 메시지 확인!

Kafka Connect

  • kafka Connect를 통해 Data를 Import/Export 가능
  • 코드없이 Configuration으로 데이터를 이동
  • Standalone mode, Distribution mode 지원
    • RESTful API 통해 지원
    • Stream 또는 Batch 형태로 데이터 전송 가능
    • 커스텀 Connector를 통한 다양한 Plugin 제공
  • 테스트를 위한 MariaDB 설치완료

'DevOps > MSA' 카테고리의 다른 글

Spring Cloud Gateway - Load Balancer  (0) 2023.05.15
Spring Cloud Gateway - Filter 적용  (0) 2023.05.14
Spring Cloud Gateway  (0) 2023.05.14
Spring Cloud 와 Eureka  (0) 2023.05.14

SSE 로 서버에서 클라이언트로의 단방향 통신을 구현하며 생긴 궁금증

  • 기본적으로 http는 비연결성, 무상태 프로토콜로 한번의 요청과 응답으로 역할이 끝나게 된다.
  • 하지만 SSE는 구독 요청 → 특정 이벤트 발생 시 요청이 오지 않음에도 서버에서 클라이언트로 데이터를 전송할 수 있다.
  • 이러한 기술을 구현할 수 있도록 하는 것 ! → http persistence connection이라는 것 까지는 도달할 수 있었다.
    • 의문점
      • spring에서 사용하는 서블릿 컨테이너에 스레드 풀 기본 값은 200이다.
      • SSE를 구현할 시 동시에 200명이 SSE알림을 받기 위해 구독을 한 상황
        • 이때 서버에서 클라이언트로 데이터를 계속 보내기 위해서는 연결을 지속해야한다. 그 말은 어떠한 서버자원을 계속 점유하고 있어야한다.(I/O발생 끊기지 않은 상황)
        • 그렇다면 그 이후로 다른 request가 왔을 때 큐 에서 계속 대기해야하는 상황이 생기지 않을까..???? 모든 스레드 자원이 연결을 지속하기 위한 목적으로 반납되지 않는 상황이라고 생각되었다.
    • 해소
      • 나와 같은 생각을 가진 질문이 10년전에 올라왔었고 답변들을 보면
      • 서버의 스레딩 모델에 따라 다르고 apache는 기본적으로 연결당 하나의 스레드를 사용하므로 대기 한다고 나와있다.
        • 동접자수가 특정값을 넘어간다면 폴링이 더 적합한 선택일 지도 모른다.
        • 비동기적인 처리를 한다면 이를 해결 할 수 있을지도..?(NIO, webflux등 공부해봐야겠다.)
    • https://stackoverflow.com/questions/14225501/server-sent-events-costs-at-server-side

    2023-06-25

    해당 문제에 대한 테스트를 진행해보았다.
package com.example.ssetest.controller;

import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@RestController
public class SseController {
    private static final Map<String, SseEmitter> sseEmitters = new ConcurrentHashMap<>();

    @GetMapping(value = "sub",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public ResponseEntity<SseEmitter> connect(HttpServletResponse response){
        String userId = UUID.randomUUID().toString();
        log.info("시작 {}",userId);

        SseEmitter sseEmitter = new SseEmitter(100000L * 45L);

        sseEmitters.put(userId, sseEmitter);
        log.info("id 값은 {}",sseEmitters.keySet());

        sseEmitter.onCompletion(()->{
            log.info("onCompletion sseEmitters {}",userId);
            sseEmitters.remove(userId);
        });
        sseEmitter.onTimeout(() -> {
            log.info("onTimeout sseEmitter {}",userId);
            sseEmitters.remove(userId);
        });
        sseEmitter.onError((e) -> {
            log.info("Error seeEmitter {}", userId);
            sseEmitters.remove(userId);
        });

        try {
            sseEmitter.send(SseEmitter.event().name("connect").data("Connection"));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return ResponseEntity.ok(sseEmitter);
    }
    @GetMapping("/sleep")
    public ResponseEntity<String> sleepM() throws Exception{
        Thread.sleep(10000000);
        return ResponseEntity.ok("Sleep");
    }
    @GetMapping("/answer")
    public ResponseEntity<String> answer(){
        return ResponseEntity.ok("answer");
    }
}

테스트 시나리오

server.tomcat.max-connections=5
server.tomcat.threads.max=5
  • 톰캣의 쓰레드 수를 5개로, 커넥션을 5개로 제한해보았다.
    1. sleep을 5번 호출 → answer 호출
      • answer에 대한 응답이 오지 않음
    2. sub를 5번 호출 → answer 호출
      • answer에 대한 응답이 오지 않음

쓰레드를 timeout 동안은 점유하고 있다는 생각이 들었다. but 테스트의 전제가 잘못되었다는 것을 깨닫고 다시 테스트를 해보았음

# 톰캣서버의 가용가능한 최대 쓰레드는 5개로 설정
server.tomcat.threads.max=5
# 기동시점부터 최소로 유지되어야 하는 쓰레드는 5개
server.tomcat.threads.min-spare=5
  1. sleep을 5번 호출 → answer 호출
    • answer에 대한 응답이 오지 않음
  2. sub를 5번 호출 → answer 호출
    • answer에 대한 응답이 왔음

어떻게 된 일일까? 쓰레드를 점유하고 있는 것이 아닌가? max-connection을 기반으로 테스트를 해보았다.

server.tomcat.max-connections=3
  1. sleep을 5번 호출 → answer 호출
    • answer에 대한 응답이 오지 않음
  2. sub를 5번 호출 → answer 호출
    • answer에 대한 응답이 오지 않음

테스트 결과로 볼 때 기존에 생각했던 것 과는 다른 결론이 나왔다.

  • 기존의 내 생각 Thread가 반환되지 못하고 이벤트 발생을 기다릴 것이다.
  • 테스트 결과
    • Thread에 제한을 두었지만, 다른 요청을 받을 수 있다.
    • Max-Connection에 제한을 두니, 다른 요청을 받을 수 없었다.

이를 이해하기 위해 tomcat 여러가지 자료들을 찾아보았다.

  • 먼저 현재 내가 사용하고 있는 톰캣의 버전은 10버전이다.
  • tomcat은 9.0버전 부터 BIO connector를 지원하지 않는다고 한다.

BIO Connector란?

  • Socket Connection을 처리할 때 Java의 기본적인 IO 기술을 사용
  • Thread Pool에 관리되는 thread는 소켓 연결을 받고 요청을 처리하고 요청에 대해 응답한 후 소켓 연결이 종료되면 pool에 다시 돌아오게 된다.
  • 즉, connection이 닫힐 때까지 하나의 thread는 특정 connection에 계속 할당되어 있다.
    • 내가 가진 궁금증은 이러한 배경을 바탕으로 발생하였던 것!
  • 이러한 방식으로 Thread를 할당하여 사용할 경우, 동시에 사용되는 thread 수가 동시 접속할 수 있는 사용자의 수가 된다.
  • 이러한 방식을 채택하여 사용할 경우 thread들이 충분히 사용되지 않고 idle 상태로 낭비되는 시간이 많이 발생한다.
  • 이러한 문제점을 해결하고자 NIO Connector가 등장!
  • 간단하게 BIO Connector는 Acceptor가 소켓을 획득하고 worker thread pool에서 socket을 처리하기 위한 idle 상태인 worker thread를 찾는다.
  • 만약 Worker thread pool에 idle thread가 없다면, 요청을 처리할 thread가 없기 때문에 Acceptor는 block 된다.
  • 중요한 점은 Connector 내부에서 각 요청에 상응하는 Thread를 Workde Thread Pool에서 꺼내서 1:1로 매핑해준다.

그렇다면 NIO Connector란?

  • BIO와 달리 새로운 연결이 발생할 때, 바로 새로운 Thread를 할당하지 않고(Connection이 Thread와 1:1 매핑 관계가 아니다.) Poller라는 개념의 Thread에게 Connection(Channel)을 넘겨준다.
  • Poller는 Socket들을 캐시로 들고 있다가 해당 Socket에서 data에 대한 처리가 가능한 순간에만 thread를 할당하는 방식을 사용해서 thread가 idle 상태로 낭비되는 시간을 줄여준다.

tomcat이 NIO 방식으로 동작하여 Thread와 1:1 매핑관계가 아니기 때문에 이러한 테스트 결과가 나왔다고 생각한다

  • 앞으로 I/O MultiPlexing과 웹플럭스에 대해서도 공부할 생각이다.

+ Recent posts