실서비스를 준비하는 데 있어서 서버의 성능 테스트는 매우 중요한 부분이다.

(여기서 성능 테스트란 단순히 Performance test 만을 말하지는 않는다. 자세한건 다음을 참조 - 

https://jins-dev.tistory.com/entry/load-testing-%EA%B3%BC-stress-testing-performance-testing-%EC%97%90-%EB%8C%80%ED%95%9C-%EB%B9%84%EA%B5%90)

 

그렇다면 성능 테스트를 어떻게 할 것이고 어떻게 병목현상을 해결하여 개선을 이루어낼 것인가가 관건이며, 이를 위해서는 단순히 코드를 효율적으로 짜는 것 이상의 학습이 필요하다.

"성능" 에 대한 이해와 "테스트" 에 대한 이해, Performance Engineering 에 대한 이해가 모두 필요한 것이다.

 

그렇다면 먼저 서버의 성능 측정에 앞서 알아두어야할 용어들을 정리해보자.

 

(1) Response Time : 클라이언트의 요청을 서버에서 처리하여 응답해주는데까지 걸리는 전체 시간을 말한다.

실제 요청이 들어간 시점부터 네트워크를 거치고 비즈니스 로직의 처리가 끝난 후 다시 클라이언트가 응답을 받는 시점까지 걸리는 시간을 말한다.

 

(2) Think Time : 사용자가 다음 Request 를 보내기 전까지 활동하는 시간을 말한다. 실제 별도의 서버 요청을 하지않고 웹문서를 보거나 컨텐츠를 즐기는 시간을 Think time 으로 분류한다.

첫번째 요청에서 다음 요청을 보낼때까지의 시간이므로 Request Interval 로 부르기도 한다.

 

(3) Concurrent Users : 말그대로 동시에 서비스를 사용하는 유저를 말한다. 웹서버 / 세션서버 등 서버의 종류에 따라 유저의 행동양식에 대한 분류는 매우 다양해지게 된다.

일반적으로 Concurrent User 는 Active User 와 Inactive User 로 분류한다.

 

 - Active User : 실제 Request 를 보내고 Response 를 대기 혹은 Connection 을 잡고 있는 유저를 말한다.

일반적으로는 Request 에 대해 실제 Transaction 을 발생시키는 유저에 대해 언급한다.

여기서 포함되는 유저들의 목록에는 실제로 서버에서 클라이언트의 요청을 받을 시, Connection Pool 을 모두 가져가는 유저들과, Connection Pool 의 할당을 대기하고 있는 Pending User 들 역시 Active User 에 포함된다는 점이다.

 

 - Inactive User : 실제 Request 를 수행하지 않고 대기중인 유저를 말한다. 서버의 종류에 따라 연결을 유지하고 있는 유저의 경우에도 Inactive User 로 보기도 한다.

웹서버의 경우 대부분의 Inactive User 는 Think time 을 갖는 유저로 간주된다.

 

(4) Throughput : 서버가 클라이언트에 송신한 데이터량

 

(5) Vuser : Virtual User - 주로 퍼포먼스 테스트를 위해 만드는 가상 유저

 

(6) MRT : Mean Response Time 의 약자로 유저들에 대한 평균 응답시간을 말한다.

 

(7) TPS : Transaction Per Second 의 약자로 주로 서버 성능의 척도가 된다. 초당 트랜잭션 처리 수를 의미한다.

일반적으로 TPS = AU / MRT 라는 공식에 따라 Active User 의 수에 비례하고 유저들에 대한 평균 응답시간(MRTT)에 반비례한다.

 

(8) Scenario : 테스트를 위한 시나리오를 말한다. 정확한 TPS 측정을 위해서 가장 중요한 부분 중 하나로, 실제 서비스에서 유저가 어느정도 비율로 어떤 API 를 호출할지 예측하는 과정이 필수적이다.

Service Architecture 를 잘 이해하고 있어야됨은 물론이고, 이에 따라 API 호출의 비율과 그에 맞는 튜닝으로 서비스의 질을 향상시켜야 한다.

 

 

위의 용어를 이해했으면 다음으로 퍼포먼스 테스트를 위한 프로세스를 정리해보자.

 

1. 먼저 서비스의 목표와 그를 위한 규모를 측정해야 한다.

 

2. 서비스 내에서 사용자의 요청 빈도 수를 예측하고, 그에 알맞게 테스트 시나리오를 만든다.

 

3. 테스트 시나리오에 맞게 다양한 방향으로 테스트를 진행한다.

여기서 테스트는 Stress Test / Performance Test / Load Test 가 복합적으로 진행되어야 한다. 각 테스트별 한계값 임계치를 측정하는 것이 중요하며, 각각의 테스트 결과는 서로에 영향을 미치게 될 것이다.

 

4. Stress Test / Load Test / Performance Test 를 통해 현재 시스템의 부하 한계치, 퍼포먼스 등이 측정이 되었다면, 모듈별로 분석해서 튜닝하는 작업이 필요하다.

비즈니스 로직 뿐 아니라 Database / Network / Middleware / Infrastructure 등을 모두 튜닝하는 것이 중요하다.

 

5. 튜닝을 통해 테스트를 다시 수행하며 서비스의 한계치를 다시 측정한다. 목표에 이를 때까지 4~5 과정을 반복한다.

 

 

위의 테스트 Process 를 거치면 실제 생각했던것과 다른 상황을 많이 마주치게 된다.

 

가령, 우리는 응답시간이 큰 변동폭이 없을 것이며, 가용 자원량 만큼 TPS 가 올라갈 것이라 예측하고 한계점을 측정하겠지만, 실제로는 가용자원의 양은 처음에 급격이 증가하되, 어느정도 TPS 이상이 확보되면 일정해지며 응답시간은 TPS 증가에 따라 기하급수적으로 올라가게 된다.

 

이는 주로, Service architecture 가 Caching 을 하기 때문에 가용자원에 대한 부분을 상당 부분 부담해주기 때문이며, 응답시간의 경우 부하가 쌓이는 만큼 Connection Pool 에서 유저의 요청에 대한 Pending 이 생기기 때문이다.

 

 

아직 Performance Engineering 에 대해서는 지식이 짧아 정리하지 못했는데, 계속해서 공부를 해보면서 추가 포스팅 하도록 해야겠다.

 

(포스팅의 글이 너무 좋아 참조하였습니다. 

https://bcho.tistory.com/787, http://www.jidum.com/jidums/view.do?jidumId=562dumId=562)

 

iLiFO 지덤 :: 테지덤 성능테스트 – Little’s law 포함

I. 성능테스트의 개요와 원리 Little’s law 가. 시스템 테스트의 정의 사용자가 시스템을 사용하기에 성능상 문제가 있는 지를 검증하여 개선하기 위한 테스트 나. 소프트웨어 성능 테스트의 기본 원리 Little’s law ‘공간 내에 머무는 객체 수(L)’는 ‘객체의 공간유입량(λ)’과 ‘객체가 머무는 시간(W)’에 비례한다. 즉 L = λW이다. 시스템의 성능(TPS)는 트랜잭션을 발생시키는 사용자(Active User)를 평균응답시간(Mean

www.jidum.com

 

성능 엔지니어링 대한 접근 방법 (Performance tuning)

성능 엔지니어링에 대한 접근 방법 조대협 성능 개선, Performance Tuning, 용량 선정 과 같은 튜닝 관련 용어들은 모든 개발자나 엔지니어에게 모두 흥미가 가는 주제일 것이다. 그 만큼 소프트웨어에서 고성능을..

bcho.tistory.com

 

 

실제 라이브 서비스를 위해 서버 산정을 할 때, 서비스 규모에 걸맞는 트래픽을 감당하기 위한 테스팅은 필수적이다.

 

근래에는 클라우드의 발전으로 Scalability 나 Elasticity 를 확보할 수 있는 많은 방법과 기술들이 생겼다고는 해도, 결국 해당 솔루션을 사용하는 것은 "비용" 이며 서버개발자라면 이 비용을 최소화하고 효율적인 서버를 구축해내는 것이 "실력" 을 증명해내는 일이다.

 

여기서 피할 수 없는 백엔드 테스트의 종류들이 나타나는데, 일반적으로 스트레스 테스트 또는 부하 테스트라고 알려진 테스트들이다.

이 테스트들은 중요하지만, 개발자들이라도 차이를 잘 모르는 사람들이 꽤나 있고 서비스의 규모가 작다면 혼용되어 쓰는 경우가 다수이기 때문에 정리해서 알아둘 필요가 있다.

 

(1) Load Testing (부하 테스트)

말그대로 시스템이 얼마만큼의 부하를 견뎌낼 수 있는 가에 대한 테스트를 말한다.

 

보통 Ramp up 이라하여 낮은 수준의 부하부터 높은 수준의 부하까지 예상 트래픽을 꾸준히 증가시키며 진행하는 테스트로, 한계점의 측정이 관건이며, 그 임계치를 높이는 것이 목적이라 할 수 있다.

 

일반적으로 "동시접속자수" 와 그정도의 부하에 대한 Response Time 으로 테스트를 측정한다.

예를들어 1분동안 10만명의 동시접속을 처리할 수 있는 시스템을 만들수 있는지 여부가 테스트의 주요 관건이 된다.

 

(2) Stress Testing (스트레스 테스트)

스트레스 테스트는 "스트레스받는 상황에서 시스템의 안정성" 을 체크한다.

 

가령 최대 부하치에 해당하는 만큼의 많은 동시접속자수가 포함된 상황에서 시스템이 얼마나 안정적으로 돌아가느냐가 주요 관건이 된다. 

스트레스 상황에서도 시스템의 모니터링 및 로깅, 보안상의 이슈나 데이터의 결함 등으로 서비스에 영향이 가서는 안되므로 결함에 대한 테스트가 일부 포함된다.

가령 메모리 및 자원의 누수나 발생하는 경우에 대한 Soak Test 나 일부러 대역폭 이상의 부하를 발생시키는 Fatigue Test 가 포함된다.

 

이처럼 부하가 심한 상황 또는 시스템의 복구가 어려운 상황에서 얼마나 크래시를 견디며 서비스가 운영될 수 있으며 빠르게 복구되는지, 극한 상황에서도 Response Time 등이 안정적으로 나올 수 있는지 등을 검사하게 된다.

예를들어 10만명의 동시접속 상황에서 크래시율을 얼마나 낮출 수 있는가, 혹은 데이터 누락을 얼마나 방지할 수 있는가 에 대한 테스팅이 있을 수 있다.

 

(3) Performance Testing (퍼포먼스 테스트)

Load Test 와 Stress Test 의 모집합 격인 테스트의 종류이다. 리소스 사용량, 가용량, 부하 한도(Load Limit) 를 포함해서 응답시간, 에러율 등 모든 부분이 관건이 된다.

 

특정 상황에서 어떤 퍼포먼스를 보이는지에 대한 측정이 주가 되며, 서비스의 안정성보다는 퍼포먼스 자체에 집중한다.

 

주로 서비스적 관점에서는 Performance Test 보다는 Load Test 나 Stress Test 가 더 중점이 되며, 시스템 전체적인 성능의 양적 측정과 같은 관점에서 Performance Test 로 분류한다.

보통 그럴 때에 수행하는 테스트로 Benchmark 라고 하기도 한다.

 

 

위와 같은 종류의 테스트 들을 수행하는 데 있어 제일 중요한 부분 중 하나는 모니터링과 리포팅이라고 할 수 있겠다.

관련해서 경험과 같이 포스팅으로 정리할 예정이다.

 

 




Spring 은 기본적으로 Framework 의 사용자, 즉 개발자가 비즈니스 로직의 구현에만 집중할 수 있게 서블릿 처리와 같은 기타 작업을 대신해주는 잘 구성된 프레임워크이다.


개발을 하다보면 비즈니스 로직 이외에도 Request 와 Response 에 대해 직접 처리하거나 비즈니스 로직을 처리하기 이전, 혹은 이후에 작업을 처리해야할 때가 있다.


예를 들어 Request 와 Response 에 대한 로깅이나 API 전반에 걸친 인증 등 Framework Layer 에서 처리할 수 있는 작업들이 있으며, 이 때 Filter 와 Interceptor 로 작업을 처리한다.





<출처 : https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/>



1. 필터 (Filter)



public interface Filter {

public void init(FilterConfig filterConfig) throws ServletException;

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;

public void destroy();
}




Filter 는 Servlet Container 에 의해 동작이 제어되는 Java Class 로 HTTP Request 가 Service 에 도착하기 전에, HTTP Response 가 Client 에 도착하기 전에 제어할 수 있다.


Filter 는 Request 를 처리할 때 Dispatcher Servlet 이 작업을 처리하기 전에 동작하고, Response 를 처리할 때에는 Dispatcher Servlet 에 의해 작업이 끝난 이후에 동작한다.


정확히 분류하자면 Filter 는 J2EE 의 표준이며 Servlet 2.3 부터 지원되는 기능으로 Spring Framework 에서 지원을 하고는 있지만 Spring 프레임워크만의 기능은 아님을 알아두자.


Filter 는 Filter Chain 을 갖고 있으며 Application Context 에 등록된 필터들이 WAS 구동 시에 Context Layer 에 설정된 순서대로 필터 체인을 구성한다.


구성된 체인은 맨 처음 인스턴스 초기화(init())를 거친 후 각 필터에서 doFilter() 를 통해 필터링 작업을 처리하고 Destroy 된다.


이 때의 환경 설정은 주로 톰캣을 사용할 경우 web.xml 또는 Java Configuration 을 이용해서 구현하게 된다.




2. 인터셉터 (Interceptor)



public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception;

    void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;

    void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

}


Interceptor 는 필터와는 다르게 Spring 레벨에서 지원하는 Servlet Filter 이다. Spring Context 내에서 HTTPRequest 와 HTTPResponse 처리에 대해 강력한 기능을 제공한다.


Java Servlet 레벨에서 동작하는 Filter 와 다르게 Spring Context 레벨에서 동작하므로 Dispatcher Servlet 이 Request 및 Response 를 처리하는 시점에 Interceptor Handler 가 동작한다.


Dispatcher Servlet 에서 요청이 처리되고 나면 요청받은 URL 에 대해 등록된 Interceptor 가 호출되며 Prehandle - Controller 실행 - PostHandle - AfterCompletion 의 순서로 인터셉터가 작업을 처리한다.


이 때의 환경 설정은 주로 servletContext.xml 또는 Java Configuration 을 이용해서 구현하게 된다.



필터와 인터셉터는 현업에서도 자주 사용되는 유용한 도구이니 확실하게 알아두고 꼭 필요할 때 응용해서 사용할 필요가 있다.



참조 : 

http://www.mkjava.com/tutorial/filter-vs-interceptor/

https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle

https://supawer0728.github.io/2018/04/04/spring-filter-interceptor/




메시지 지향 미들웨어(Message Oriented Middleware)란 독립될 수 있는 서비스간에 데이터를 주고받을 수 있는 형태의 미들웨어를 말한다.


구성요소간 통신을 하는 방법에 있어서 네트워크(Network) 를 이용하거나 Process 간 통신 등의 중계를 해주는 미들웨어의 경우에도 Message Oriented Middleware 라고 부를 수 있지만, 일반적으로 Server to Server 로 전달되는 Message 서비스를 말한다.


메시지 지향 미들웨어의 사용은 통신을 통해 Service 들 간의 분리와 독립, 그럼에도 불구하고 안정적인 기능을 제공해줌으로써 Software Architecture 를 설계하는 데 큰 유연성을 제공해준다.


메시지 지향 미들웨어는 대부분 메시지큐(Message Queue) 의 형태로 구현되어 있으며 줄여서 MQ라고 부른다.


MQ의 종류에는 흔히 알려진 Rabbit MQ 가 있으며 이런 종류의 메시지큐들은 AMQP(Advanced Message Queueing Protocol) 를 이용하여 구현되어 있다.


MQ 시스템을 인프라에 구축했을 때에 아키텍처상으로 이를 브로커(Broker)라고 부르기도 한다.


Message Queue 를 이용할 때 얻을 수 있는 장점으로는 다음과 같은 것들이 있다.


(1) Redundancy : 메시지의 실패는 보관 및 관리가 되며 메시지가 실패한다고 하더라도 시스템에 영향을 주지 않는다.


(2) Traffic : 메시지 큐에 메시지를 쌓아두면 무분별하게 증가할 수 있는 트래픽에 대해 느리더라도 안정된 처리가 가능하다.


(3) Batch : 메시지 큐는 메시지에 대한 일괄처리를 수행하며 특히 부하가 심한 Database 에 대한 처리에 대한 효율성을 기대할 수 있다..


(4) Decouple : 소프트웨어와 MQ는 분리되며, 이는 소프트웨어 설계에 큰 이점을 준다.


(5) Asynchronous : 큐에 넣기 때문에 영향을 받지않고 비즈니스 로직을 처리할 수 있다.


(6) Ordering : 트랜잭션의 순서가 보장되며 동시성 문제에서도 안전하다.


(7) Scalable : 다수의 서비스들이 접근하여 원하는 서비스를 이용하는데 용이하다. 이는 다수의 서비스를 분리하는 데에도 큰 도움을 준다.


(8) Resiliency : 연결된 서비스에 이상이 생기더라도 다른 서비스에 영향을 미치지 않는다.


(9) Guarantee : MQ 는 트랜잭션방식으로 설계되어 있으며 메시지 처리의 트랜잭션을 보장한다.


이러한 장점들을 갖고 있기 때문에 주로 API 서버를 구축할 때 중요하거나 그 자체로 무거울 수 있는 기능들을 별도의 서버로 분리하고, MQ 를 통해 통신하는 방식이 선호된다.


이는 프로젝트의 규모가 커짐에도 유연성을 확보할 수 있는 좋은 수단이며 서비스 자체에서 처리하기 부담스러운 요구사항들을 만족시킬 수 있는 옵션을 제공한다.



다음 링크들을 참조했습니다.


https://docs.oracle.com/cd/E19435-01/819-0069/intro.html

https://stackify.com/message-queues-12-reasons/

https://heowc.tistory.com/35





Cache 가 저장된 데이터를 Flush 시키는 전략은 캐시 서버의 유지에 있어서 중요한 부분이다.


가령 캐시에 저장된 데이터가 실제 데이터와 동기화가 안되어 잘못된 값이 참조된다거나, 이전 데이터가 만료되지 않는 경우,

혹은 저장된 정보인데 Key 값이 Mismatch 나거나 불필요하게 Flush 되어 Cache hit-ratio 가 낮아진다면 이는 전체 서버 구조에 영향을 미치게 된다.


다음은 Cache 서버를 사용할 때 유의해야할 몇가지 Cache 의 Flushing 전략 들을 정리해보았다.


주로 사용되는 캐시인 Redis 위주로 정리가 되어있으며, Cache 자체에 대한 범용적인 정리이지만 예시는 Redis 위주이며 캐시의 종류에 따라 차이가 있을 수 있다.



1. Cache 의 Flushing 은 일반적으로 Active 한 방법과 Passive 한 방법이 있음을 이해하자.


 : Active 한 방법은 별도의 요청에 따라 Cache 를 Flush 시키는 방법이고, Passive 한 방법은 자체에 Expire Time 을 이용해서 외부에서 별도 요청없이 캐시를 비우게끔 만드는 전략이다.

 

 만약 Active Flushing 을 하지 않는다면, 해당 캐시는 수동적으로만 갱신되므로 실시간적인 데이터 동기화 및 캐싱이 불가능하다.

 Passive Flushing 을 하지 않는다면, Active 한 방식으로 Flushing 되지않는 데이터는 영구히 캐시 서버에 남아서 공간을 차지하게 되며 이는 큰 비용이 될 수 있다.


 반대로 지나치게 Active 한 Flushing 이 많아진다면, Cache 는 Read 요청 대비 Write 요청의 비율이 높아져 쓰는 효율이 없어지게 되고, Passive Flushing 도 지나치게 짧은 단위로 Flushing 이 일어난다면 Cache hit ratio 가 줄어들 것이므로 설계에 유의가 필요하다.

 

 일반적으로 API 의 설계에 맞춰 Active Flushing 타이밍을 정확히 맞추고, Passive Flushing 을 위한 Expire Time 의 설정은 선험적 지식 또는 벤치마킹 등에 의해 측정되어 설정되게 된다.

 


2. Cache Refresh / Expire reset 기준을 확인하는 것이 중요하다.


 : 사용하는 캐시 서버 및 설정에 따라 다를 수 있지만, Cache Refresh 의 기준을 확인하는 것은 중요하다. 

 가령 Redis 의 경우 Cache hit Read 기준으로 Expire reset 이 발생하기 때문에, 계속적으로 Cache hit 이 발생하면 자동으로 Expire 는 되지않는다.

 단순 기능이라면 큰 문제가 안되겠지만 캐시를 바라보는 경우가 많을 경우 문제가 생기기 쉬운 상황이므로 잘 체크해주어야 한다.


 즉, 지속적으로 Cache-hit 이 발생하는 한, Active 한 방식의 Flushing 이 아닌 Passive Flushing 을 기대하는 건 잘못된 방식이다.

 개인적으로 QA 프로세스에서 Caching 문제가 발생하고 있었는데, 이 부분을 간과해서 문제가 해결되지 않은 버전이 Live 에 포함될 뻔 한 일이 있었다.



3. 사용하는 캐시 서버의 Expire 로직을 이해하자.


 : Cache 를 사용할 때 캐시 서버의 로직을 파악하고 있는 것이 중요하다. 중요한 것은 사용하는 Cache 서버가 어떻게 내부적으로 동작하냐는 것이다.

 가령 Redis 의 경우 일정한 주기를 갖고 랜덤하게 key 들을 뽑아서 expire 을 검사하고 expiration rate 가 높을 경우 다시 일정 주기로 검사하는 루틴을 지닌다.

 이러한 구조를 이해하는 것은 서버 설정하는 데 있어서 기본이 되며 보다 나은 환경을 구축하는 데 도움을 준다.




캐시에 대한 현업에서 이슈들로 겪으면서 가장 중요했던 기본 내용들만 정리해보았다.


실제 이슈와 그에 대한 트러블 슈팅은 따로 포스팅에 정리하도록 하겠다.





얼마전 Spring 의 Circular Dependency 이슈가 있어서 좀 더 자세히 알아보다가 궁금해져서 정리한 내용이다.


본 포스팅은 이슈를 정리한 내용이므로 다음 내용을 선행적으로 참조해볼 필요가 있다.

(https://jins-dev.tistory.com/entry/Spring-DIDependency-Injection-%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-%EC%82%AC%EC%9A%A9?category=760012)


먼저 이슈가 된 내용은 서버가 올라갈 때 Circular Dependency 관계에 있는 Bean 들 간의 설정에 있어서 @Autowired 어노테이션을 실수로 빼먹은 부분이었는데, 그로인해 참조된 Bean 이 null 상태로 초기화되는 문제 때문이었다.



Spring Reference manual 에 따르면 스프링은 먼저 각 Bean 들을 초기화하고 다른 Bean 들에 Inject 하는 방식으로 기본적으로 Circular Dependency 문제를 해결한다.


즉, 껍데기만 만들어놓고 Bean 을 먼저 Injection 한다는 것이 옳다.


여기서 주의할 부분은 상호간에 Bean 이 Inject 될 시에 Inject 되는 Bean 은 완전히 Initialized 된 상태가 아니라는 것이다.


하지만 이런 Spring Framework 레벨에서의 처리가 있음에도 Spring team 은 결국 Circular Dependency 문제는 발생하지 않는 경우가 최선이며, 그런 안전한 구조를 설계하기 위한 Constructor Injection 을 권장하고 있다.


Spring framework 를 이용한 생산성과 편의성을 보다 추구하는 측에서도 Setter Injection 의 사용이나 @Lazy 어노테이션을 이용한 Lazy Init 을 권장하고 있다.




참조


https://stackoverflow.com/questions/3485347/circular-dependency-in-spring


https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/beans.html#d0e2299


https://www.logicbig.com/tutorials/spring-framework/spring-core/circular-dependencies.html







스프링 프레임워크의 주요 철학 중 하나로 가장 큰 테두리 중 하나는 DI(Dependency Injection) 이다.


Dependency Injection 에 대해 먼저 살펴보자면, 말그대로 "의존성 주입" 을 말하며, 스프링 프레임워크는 Framework 레벨에서 DI 를 제공해준다.


Spring 의 Container 들은 Bean 객체들을 관리하는 데 있어서 DI 를 이용하며 이를 통해 Life Cycle 을 용이하게 관리할 수 있으며 이 것이 스프링 프레임워크의 핵심적인 동작이라고 할 수 있다.


즉, 프레임워크 레벨의 관리를 통해 개발자는 객체들간의 의존성에 신경을 덜 쓰고 Coupling 을 줄일 수 있으며 높은 재사용성과 가독성있는 코드를 만들어낼 수 있다.


이를 제어의 역전(Inversion Of Control) 이라 하며, 이것이 스프링 프레임워크의 특징적인 개념인 IOC 이다.

(클래스 관리의 주체가 개발자가 아닌 프레임워크라는 뜻이다.)


그리고 결과적으로 이러한 개발 편리성은 높은 생산성을 이끌어낼 수 있는 스프링의 큰 장점이다.


- Dependency Injection 을 통해 얻을 수 있는 장점 -


 (1) Dependency Reduction : 객체 상호 간 의존성 관계를 줄여준다.


 (2) Reusable Structure : 코드의 재사용과 조합이 용이하다.


 (3) Readability : 코드들이 분리되다보니 가독성이 뛰어나진다.


 (4) Loose Coupling & Easy to change : 구조는 변화에 민감하지 않을 수 있다.


그 외에 테스트가 용이하고 다양한 패턴을 적용하는 데 유연하다는 점도 큰 장점이 될 수 있다.



Spring Framework 에서 개발자가 Dependency Injection 을 하는 데 몇가지 방법들이 있다.


 (1) Field Injection


 가장 흔히 볼 수 있는 Injection 방법으로 사용하기도 간편하고 코드도 읽기 쉽다.



public class Sample {
    
    @Autowired
    private Example example;
    
}


 많이 사용됨에도 불구하고 Field Injection 을 통한 의존성 주입은 권장되지 않는다.

 이유는 너무 추상적인 Injection 기법 때문이다. 의존성 주입이 쉽기 때문에 Dependency 관계가 복잡해질 우려가 있으며 이는 Framework 의 사용에 있어 다음과 같은 안티패턴적 측면을 갖는다.


 - Single Responsibility Principle Violation 

 : 너무나 쉬운 의존성의 주입은 하나의 클래스가 지나치게 많은 기능을 하게됨으로써 초기 설계의 목적성이자 "객체는 그에 맞는 동작만을 한다." 는 원칙에 위배되기 쉽다.

 위배된 경우 리팩토링의 비용은 크다.


 - Dependency Hiding

 : 추상화된 의존관계는 의존성을 검증하기 힘들게 만든다. 


 - DI Container Coupling

 : Field Injection 을 사용하면 해당 클래스를 곧바로 Instant 화시킬 수 없다. 이 부분 때문에 Constructor Injection 이 권장되는 이유이기도 하다.

 가령 Container 밖의 환경에서 해당 클래스의 객체를 참조할 때, Dependency 를 정의해두는 Reflection 을 사용하는 방법 외에는 참조할 수 있는 방법이 없다.

 DI Framework 는 Field Injection 된 클래스의 Instance 화에 대해서 Null Pointer Exception 을 만들어낼 것이다.


 - Immutability

 : Field Injection 된 객체는 final 을 선언할 수 없으므로 가변적(Mutable)이다. 객체는 변경될 수 있으며 이에 대한 대응에는 큰 비용이 든다.



 (2) Setter Injection


 선택적인 의존성을 주입할 경우 유용하며, Spring 3.x 대까지 가장 권장되던 방식이다.



public class Sample {
private Example example;

@Autowired
public void setExample(Example example) {
this.example = example;
}
}


 Field Injection 으로 인한 패턴적 위험성을 상당 부분 해소한다. Optional Injection 의 경우 권장되는 방식이다.

 @Required 어노테이션을 이용하면 의존성이 필요한 Setter 를 만들 수 있다.


 (3) Constructor Injection


 Spring 4.x 이상부터 권장되는 방식이다.



public class Sample {
    private final Example example;

    @Autowired
    public Sample(Example example) {
        this.example = example;
    }
}


 코드를 통해 알 수 있듯, final 선언이 가능하며 Immutability 에 대한 해소가 가능하며 의존성의 순환 참조(Circular Dependency) 에 대한 예방이 가능하다.

 순환 참조 시 위의 방법을 이용한 코드는 BeanCurrentlyCreationExeption 을 발생시킨다.

 역시 위에서 언급된 Container Coupling 문제도 해결이 되는데, 생성자를 통한 Injection 이므로 즉각적인 Instance 화 등에 대한 문제도 해결된다.



 많은 예제 코드들이 Field Injection 방식을 사용하고 있으나 Constructor Injection 의 사용이 권장된다.

추가로 참조한 바에 의하면 Spring Team 에서는 Setter 방식을 좀 더 권장하며 이유는 생성자가 지나치게 복잡해질 수 있기 때문이라고 한다.

패턴적 관점에서 이견이 있는 듯 하다.



Spring 4.3 이상부터는 생성자가 하나인 경우 @Autowired 를 사용하지 않아도 무방하다.



정리에 있어 다음 링크들을 참조하였다.


https://www.vojtechruzicka.com/field-dependency-injection-considered-harmful/


https://dzone.com/articles/dependency-injection-pitfalls


https://blog.outsider.ne.kr/753



Spring 3.1 버전부터 추가된 FlashMap 은 스프링에서 파라미터를 간편하게 전달하기 위한 자료구조이다.


주로 같은 도메인에서 Controller 간, 혹은 웹페이지에서 발생하는 Redirect 시 데이터 처리를 간단하게 하기 위한 목적으로 사용된다.


플래시 맵을 사용하는 용도는, URL 에 데이터를 노출시키지 않으면서 데이터를 전달하고 싶으면서도 Session 데이터에 넣기에 적합하지 않을 경우이다.

(Session 은 사용 후 값을 지워줘야 하므로 실수로 누락되는 경우를 방지해야하며 실제로 유저 정보들이 많이 관리되므로 가벼운 데이터의 경우에도 세션을 이용하기 부담스러운 경우가 많다.)


FlashMap 의 특징은 일반 Map 자료구조처럼 쉽고 간단하게 사용될 수 있으면서 사용되고 난 이후에는 Spring 에서 값을 자동으로 지워준다는 점에 있다.


즉, FlashMap 은 휘발성이며 사용하는 개발자 입장에서는 관리에 부담없이 가볍게 Attribute를 다룰 수 있다.


단, 휘발성이므로 서버가 지속적으로 관리하면서 사용해야 하는 공유 Attribute 에는 적합하지 않다고 한다.



참조 : 


https://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/web/servlet/FlashMap.html


https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-flash-attributes




Hibernate 를 이용한 토이프로젝트를 수행하면서... 큰 삽질을 해서 정리해보는 포스팅 ㅜㅜ


hbm2ddl.auto 설정은 hibernate 포함된 매핑 설정으로 Database 스키마 생성 스크립트를 만드는데 사용된다


시작시마다 Mapping 설정대로 Schema 세팅할 있는데 설정 값에 따라 다음과 같이 동작한다.


-      Create : Session Factory 실행될 스키마를 지우고 다시 생성. 생성 후에는 classpath import.sql 파일이 있는지 찾아 등록된 쿼리문을 실행한다.


-      Create-drop : 기본적으로 create 동일하지만 session 팩토리가 내려갈 db 스키마를 삭제한다. (이걸로 설정했다가 디비 날려먹었었다.)


-      Update ; 도메인 객체구성과 스키마를 비교해서 도메인 객체에 맞춰서 db 스키마를 변경한다. 데이터나 스키마를 지우지 않는다.


-      Validate : 도메인 객체구성과 db스키마가 같은지만 확인할 변경하지 않는다. 다를 session Factory 시작시 확인하고 예외를 발생시킨다.


다음은 Hibernate 사용시 알아두어야 하는 JPA 프로퍼티들이다.





COMET 이란 2006년 알렉스 러셋(Alex Russel)이 정의한 용어로, 브라우저가 HTTP 요청에 대해 데이터를 푸시하는 방법을 고안한 웹 모델이다.


일반적인 웹모델은 널리 알려진 바 대로 서버와 클라이언트 브라우저간 상태를 유지한 통신이 불가능하다. 


COMET 은 이러한 HTTP 의 본질적 한계를 어느정도 극복하고자 만든 Web Application Model 이라고 생각하면 된다.


가령 브라우저가 지속적으로 서버에 데이터를 받아야할 필요가 있을 때 서버의 데이터가 업데이트 되지않았다면 이는 무의미하다.

즉, 클라이언트의 입장에서 갱신이 필요한 절대적인 시점은 무조건 서버의 데이터가 변경되었을 때가 된다.


COMET 은 간단하게는 Client 로 유의미한 메시지를 전달할 때까지 HTTP 응답을 지연시키는 기술이다.


좀 더 정확히는 서버가 클라이언트의 요청에 응답할 때 응답을 "늘어뜨리는 방법" 을 이용해서 긴 시간동안 브라우저가 접속을 끊지않고 서버의 응답을 대기하게 만든다. 


즉, Long polling 과 원리가 같다.

(Long Poll – 서버측에서 단순히 클라이언트 측에 대한 연결을 길게 유지함으로써 지정한 기간 내에 정보가 있으면 응답을 전달한다. 메시지 양이 많으면 Polling 과 차이가 없으며 지정한 시간이 초과되어 끊기면 다시 요청하는 말그대로 긴 – Polling 이다.)


클라이언트는 Long polling 을 해주고, 그 시간 동안 서버는 서버에서 발생한 이벤트 정보 등도 같이 끼워넣어줌으로써 이른바 "HTTP 푸시" 를 흉내내게끔 한다.


COMET 모델에서 서버는 비동기 방식을 취하고, 클라이언트가 다양한 테크닉으로 푸시를 받을 수 있는 구조를 취하는 경우가 일반적이다.


이에 Javascript 레벨에서 사용되는 테크닉으로, Hidden Iframe 영역(Forever frame)을 할당하고 서버측에서 발생하는 이벤트별로 <script> 태그 내에 데이터를 채워넣는 방식 또는 Ajax 의 long polling 방식, 그리고 Active X 를 이용하는 방식을 사용하기도 한다.


COMET 모델에서 서버와 클라이언트가 통신하기 위해 JSONP 라는 방식을 사용하는데, 이는 JSON 규격의 데이터로 통신을 하되 Client 로부터 전달받은 Callback 함수를 호출하게끔 하는 방식이다.


이렇게 되면 브라우저는 Callback 함수로 감싸여진 Json 데이터를 이용해서 <script> 또는 XHR 을 구성하며 해당 리소스를 읽고 수행하게 된다.


이처럼 COMET 모델을 이용하면 서버에서 발생한 이벤트를 Client 의 추가 요청없이 송신할 수 있다. 



현재는 Websocket 및 HTML5에서 표준화된 Server-sent Event 와 연관되어 발전해가고 있다.





+ Recent posts