로드밸런싱(Load Balancing) 이란 Software 용어로 부하 분산의 의미를 갖고 있다. 말그대로 동작에 있어서 부하가 심해져 병목현상이 생기는 것을 방지하기 위해 사용한다.

이는 컴퓨팅 리소스, 네트워크 리소스 등 모든 부분에서의 성능향상을 기대할 수 있게 한다.


일반적으로 L4 Load Balancer 와 L7 Load Balancer 가 존재한다.




(1) L4 Load Balancing


 L4는 네트워크 Layer 4번째 계층을 의미한다. 4번째 계층에서 수행되어야할 작업의 중요한 역할 중 하나는 IP, 포트, 세션을 기반으로한 LoadBalancing 작업이 있다.

이런 이유로 L4 Load Balancer 를 Connection Load Balancer 혹은 Session Load Balancer 라 부르기도 한다.


4계층에 포트정보가 들어감으로써 디테일한 로드밸런싱이 가능하기 때문에 일반적으로 Load Balancer 를 말할 때 L4 스위치 자체를 말하기도 한다.

이렇게 L4 스위치만 이용하더라도 부하분산 및 포트에 대한 필터링이 가능하다. 

로드밸런서가 있으면 서버 몇대에 이상이 생기더라도 다른 서버가 대신 작업을 수행하는 Failover가 가능하며,

또한 이 부분에서 TCP/UDP 패킷 분석이 가능하기 때문에 Firewall 처리도 수행한다.



보통 로드밸런서의 설정 시 서버들에 대한 주기적인 health Check를 통해 장애 여부를 판단할 수 있으며 분산알고리즘(metric)에는 흔히 알려진 라운드로빈, Least Connection, Response Time, Min miss, bandwidth based, 해싱 알고리즘이 있다.


- 라운드로빈 : 세션을 순차적으로 맺어주는 방식이다. 일반적으로 5:5의 분산이 가능하나 경로별로 같은 처리량이 보장이 안된다.


- Least Connection : 가장 적은 Open Session을 가진 서버로 연결을 붙여준다. 가장 많이 사용되는 방식이다.


- Response Time : 각 Real Server 들이 다루는 Resource의 양과 Connection 시간, 데이터 양이 다른 경우 사용하기 적합하다. 로드밸런서가 서버와 직접 통신을 하면서 응답시간이 빠른쪽으로 많은 세션을 할당해준다.


- Hash : 특정 클라이언트는 특정 서버로만 할당시키는 방식. 경로가 보장되며 접속자수가 많을수록 분산 및 효율이 뛰어나다. 다만 접속자수가 적을수록 공평하게 분산이 안될수도 있다.


- Minimum Missis : 해시 기법과 유사하지만 특정 서버 다운시 해시값의 재할당이 이루어진다. Source IP 기반으로 해싱을 하기 때문에 프록시를 사용하는 경우 Hashing이나 Min miss를 사용하면 안된다.


- Bandwidth based Loadbalancing : 서버들과의 대역폭을 고려하여 분산한다.



(2) L7 Load Balancing


 L4 Load Balancer 가 일반적으로 TCP / UDP 에 대해서만 동작하는 것과 다르게, L7 Load Balancer 는 OSI 7 층의 HTTP 에 대해서도 작동한다.

Layer 7 의 로드밸런서는 주로 HTTP 라우팅에 대한 처리를 담당한다. 


Layer 7 Load Balancing 의 주요 특징들은 다음과 같다.


- Persistence with Cookies : 주로 쿠키를 활용한 Connection Persistence 를 유지하며, 쿠키 정보를 분석한다. WAS 로 이루어지는 연결에 대해 해당 연결을 동일한 서버로 연결되게끔 해준다.


- Context Switching : 클라이언트가 요청한 리소스에 대해 Context 를 전환할 수 있다. 가령 Static 이미지 리소스 등에 대해 Load Balancer 가 확장명에 따른 분류로 Image Server 로 연결해줄 수 있다.


- Content Rewriting : 전달받은 Request 를 변환해서 재전송이 가능하다.


그 외에도 프록시 처리(X-forwarded-for) 및 보안 로직 처리 등도 이루어진다.

로드밸런싱 알고리즘은 L4 와 유사하며 역시 가장 간단하게 라운드로빈 알고리즘이 주로 사용된다.


일반적으로 L7 로드밸런서는 L4 로드밸런서보다 비싸지만 상위 프로토콜에서 그 이상의 유연성을 보여준다.




 

 

HTTP/1.0 은 가장 기초적인 형태의 웹 프로토콜을 제시하였고, 이는 일전 포스팅에도 정리되어 있듯이 TCP 프로토콜 위에 HTTP Spec 을 이용한 HTTP 프로토콜을 충실히 따른다.


이 방식은 웹이라는 새로운 환경에서 TCP에 비해 나은 성질의 프로토콜이었으나, 웹의 규모가 거대해지면서 몇가지 이슈들이 발생하였다.

 

특히 Network Latency 이슈가 주된 문제였는데, HTTP/1.1 은 이 문제를 해결하기 위해 특징적인 2가지 개념을 도입한다.

 

(1) HTTP Pipelining (HTTP 파이프라이닝)

 

 

 

HTTP Pipelining 이란 HTTP1.1 로 스펙이 업그레이드 되면서 클라이언트와 서버간 요청과 응답의 효율성을 개선하기 위해 만들어진 개념이다.

 

HTTP Request 들은 연속적으로 발생하며, 순차적으로 동작한다.


HTTP/1.0 에서 HTTP Request 는 소켓에 write 한뒤, 서버의 Response 를 받아 다음 Request 를 보내는 방식으로 웹이 동작한다. 

여러 요청에 대해 여러 응답을 받고, 각 처리가 대기되는 것은 Network Latency 에 있어서 큰 비용을 요구한다.

게다게 HTTP/1.0 에서 HTTP 요청들은 연결의 맺고 끊음을 반복하기 때문에 서버 리소스 적으로도 비용을 요구한다.


HTTP/1.1 에서는 다수의 HTTP Request 들이 각각의 서버 소켓에 Write 된 후, Browser 는 각 Request 들에 대한 Response 들을 순차적으로 기다리는 문제를 해결하기 위해 여러 요청들에 대한 응답 처리를 뒤로 미루는 방법을 사용한다.

 

즉, HTTP/1.1 에서 클라이언트는 각 요청에 대한 응답을 기다리지 않고, 여러개의 HTTP Request 를 하나의 TCP/IP Packet 으로 연속적으로 Packing 해서 요청을 보낸다.


파이프라이닝이 적용되면, 하나의 Connection 으로 다수의 Request 와 Response 를 처리할 수 있게끔 Network Latency 를 줄일 수 있다.


하지만 위의 기법 설명에서 언급하듯이, 결국 완전한 멀티플렉싱이 아닌 응답처리를 미루는 방식이므로 각 응답의 처리는 순차적으로 처리되며, 결국 후순위의 응답은 지연될 수 밖에 없다.


<이미지 출처 : https://www.popit.kr/%EB%82%98%EB%A7%8C-%EB%AA%A8%EB%A5%B4%EA%B3%A0-%EC%9E%88%EB%8D%98-http2/>



이는 HTTP의 Head Of Line Blocking 이라 부르며 Pipelining 의 큰 문제점이다.


따라서 HTTP/1.1 의 웹서버는 서버측에 Multiplexing 을 통한 요청의 처리를 요구하며, 각 Connection 당 요청과 응답의 순서가 보장되는 통신을 한다.

 

HTTP 파이프라이닝은 HTTP/2 가 등장하면서 멀티플렉싱 알고리즘으로 대체되었고, 모던 브라우저들에서도 기본적으로는 활성화하지 않고 있다.

 

 

(2) Persistent Connection

 

 

HTTP 1.0 초기의 HTTP 연결은 요청시 TCP 의 3-way handshake 방식으로 연결이 이루어졌었다.

즉, 웹클라이언트와 서버간의 연결 성립 이후 SYN, SYN-ACK, ACK 핸드셰이킹이 발생하고 이를 바탕으로 통신을 구성한 뒤 연결을 끊는 순서가 필요했다..

웹의 초창기에는 컨텐츠의 수가 많지 않았기 때문에 이런 TCP 연결은 부담되지 않았지만, 웹을 통한 멀티미디어 컨텐츠의 발달로 인해, TCP Connection 의 재사용이 요구되게 되었다.

 

웹에서의 커넥션 재사용을 Keep-alive 또는 Connection reuse 라 하며, HTTP/1.0 에서는 클라이언트가 서버에게 요청하는 Request Header에 다음과 같은 값을 통해 연결을 유지하였다.

 

Connection: keep-alive


HTTP/1.1 에서는 이 헤더를 사용하지 않더라도 모든 요청/응답이 Connection을 재사용하도록 설계되어 있으며, 필요없는 경우에만 TCP 연결을 종료하는 방식으로 변경되었다.

 

Connection: close

 

해제 시에 위와 같은 명시적 Connection 헤더를 이용하며, 그렇지 않은 경우 무조건 연결은 재사용되게 되어 있다.

 

이러한 이슈는 HTTP/2 로 웹이 한단계 더 발전하면서 해결되었다.

자세한 건 다음 링크를 참조

(https://jins-dev.tistory.com/entry/HTTP2-%ED%8A%B9%EC%A7%95%EB%93%A4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC)

 


현재 웹 표준으로는 HTTP/2 가 개발중이며 이미 공개가 되어있다. HTTP/2 에서는 구글이 프로토타입으로 제시한 SPDY 등의 프로토콜을 기반으로 한 웹 기술의 발전이 예상되는 만큼 위의 기술들 또한 보다 더 나은 방향으로 진화될 것이라 생각한다.


 많은 어플리케이션들에서 사용되는 캐시 로직과 마찬가지로 Web browser 역시 캐시를 사용한다.


웹브라우저에서의 캐시 사용은 단순히 웹페이지의 빠른 로딩을 가능하게 할 뿐 아니라, 이미 저장된 리소스의 경우

서버측에 불필요한 재요청을 하지 않는 방법을 통해 네트워크 비용의 절감도 가져올 수 있다.


이는 아주 중요한 부분으로 리소스를 캐시했다가 재활용함은 트래픽 절감과 클라이언트 측에서의 반응성 향상 및 서버의 부하 감소라는 이점까지도 가져올 수 있다.


그렇다면 웹브라우저는 어떤 항목을 어떻게 캐시하는 것일까?


HTTP Spec 에 의하면, 모든 HTTP Request / Response 는 이러한 캐싱 동작과 관련한 설정을 Header 에 담을 수 있다.

주로 사용되는 HTTP Response Header 로는 Cache-Control 과 ETag 가 있다.


(1) Cache-Control


Cache-Control 헤더는 어떻게, 얼마나 오래 응답을 브라우저가 캐싱하면 좋을지를 브라우저에게 알려준다.


Cache-Control 헤더는 브라우저 또는 다른 캐시가 얼마나 오래 어떤 방식으로 캐싱을 할지를 정의한다. 


Web browser 가 서버에 리소스를 첫 요청할 때, 브라우저는 반환되는 리소스를 캐시에 저장한다. 

Cache-Control 헤더는 몇가지 서로다른 Pair 를 Parameter 를 가질 수 있다.


 - no-cache / no-store : no-cache 파라미터는 브라우저가 캐시를 사용하지 않고 무조건 서버에서 리소스를 받아오게끔 한다.

 하지만 여전히 ETags 헤더를 체크하기 때문에, ETags 헤더의 조건에 맞는다면 서버에 직접 요청하지 않고 캐시에서 리소스를 가져온다.

 no-store 파라미터는 ETags 에 상관없이 Cache 를 사용하지 않고 모든 리소스를 다운받도록 하는 옵션이다.


 - public / private : public 은 리소스가 공개적으로 캐싱될 수 있음을 말하고 private 은 유저마다 리소스에 대한 캐싱을 하도록 한다.

 private 옵션은 특히 캐시에 개인정보가 담길 경우 중요하다.


 - max-age : max-age 는 캐시의 유효시간을 의미한다. 초단위로 입력된다.


(2) ETag


ETag 헤더는 캐시된 리소스가 마지막으로 캐시된 이후에 변했는지를 체크해주는 헤더이다.

전체 리소스를 재다운로드하는 대신, 수정된 부분을 체크하고, Same Resource 는 재다운로드하지 않는다.

ETag 는 서버에서 리소스에 대해 할당하는 Random String 으로 할당이 되며, 이값을 비교함으로써 revision 을 체크한다.

이 유효성 검사 토큰을 사용하면 리소스가 변경되지 않은 경우 이므로 추가 데이터 전송 요청을 전송하지 않는다.


ETag 값은 다시 서버에 전송해야하며, 서버는 리소스의 토큰값과 비교해서 변경되지 않은 경우 304 Not Modified 응답을 반환한다.



이를 바탕으로 브라우저 캐싱 동작을 정리해보자.




웹브라우저는 서버의 응답값을 바탕으로 재사용가능한 Response 인지 확인하고, Validation 과 Cache 의 성질, Expiration Time 에 따라 캐시 정책을 결정한다.

이에 대해 구글은 위와 같은 명확한 형태의 Decision Tree 를 제공한다.


이외에도 웹에서 사용되는 캐시 로직을 좀 더 이해하기 위해선 서버사이드의 Cache 로직 역시 이해할 필요가 있다.


캐시 정책에 있어서 왕도는 없으며, 트래픽 패턴, 데이터 유형 및 서비스 종류에 따라 알맞게 설계하는 것이 중요하다.

그 중에서도 최적의 캐시 Lifetime 의 사용, Resource Validation, 캐시 계층 구조의 결정은 반드시 고려되어야 한다.




좀 더 자세한 자료는 다음을 참고한다.

[https://thesocietea.org/2016/05/how-browser-caching-works/]

[https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=ko]



웹은 HTTP 의 특징 상 TCP와는 다르게 Stateless 프로토콜을 사용하기 때문에 HTTP 를 사용하면서 연결 관리를 위해 여러가지 도구를 사용한다.


그 중 하나가 Cookie 이며, 쿠키는 다양한 방법으로 웹서버의 동작에 도움을 준다.


먼저 쿠키에 대한 흔한 오해로, 웹사이트가 하드디스크에 저장하는 개인에 대한 정보이며 웹사이트에 접근시 사이트는 쿠키의 정보를 이용하며 새로운 정보를 저장할 수 있다는건 잘못된 정보이다.


Cookie 는 프로그램이 아니며, 개인에 대한 정보를 저장하지 못한다. 


Cookie 는 웹서버가 유저의 하드디스크에 저장하는 텍스트 조각이며 Binary 형태의 데이터가 될 수 없다.

일반적으로 쿠키는 Name-Value 쌍으로 이루어진 웹사이트 접근에 대한 정보이다.


웹서버마다 쿠키를 활용하는 방식은 다양하며, 단순히 접속에 대한 정보나 ID 만을 저장하는 데 그치지 않고, 세션 혹은 접속 시간 등 다양한 정보를 기록하기도 한다.


쿠키는 그 자체만으로 어떤 역할도 할 수 없으며, 단지 웹서버로 전송될 수 있는 정보를 가질 뿐이다.


쿠키는 HTTP 프로토콜 상의 제약에 따라 4kb 까지 저장할 수 있으며 하나의 도메인은 브라우저마다 다룰 수 있는 쿠키 갯수의 상한을 갖는다.

즉, 쿠키는 도메인별로 브라우저가 Storage 에 관리하는 형태의 정보로, 브라우저가 쿠키의 보안을 관리한다.


이렇듯 브라우저가 도메인별로 쿠키를 관리하기 때문에 서로 다른 브라우저는 서로 다른 쿠키를 갖는다. 즉, 브라우저별로 쿠키는 공유되지 않는 내부 저장 데이터이다.


쿠키는 서버와 유저의 Local storage 간의 약속이므로, 같은 Name 으로 Value 를 기록(Write) 하고 읽어오는 것(Read)이 중요하다. 

대부분 쿠키는 웹브라우저의 내부 저장소에서 Javascript 를 통해 서버에서 핸들링 할 수 있도록 처리된다.


"여기서 클라이언트가 서버에게 쿠키를 보낸다는 점이 중요한데, 대부분의 경우 특별한 제약 등을 정의하지 않으면 Cookie 는 서버로 향하는 모든 요청과 함께 전송된다."


주로 쿠키를 사용하는 목적은 다음과 같다.


- Authentication : 세션 관리.


- User Tracking : 유저 방문 추적


- Personalization : 테마, 언어 등 커스텀화된 설정에 대한 목적



웹사이트를 통해 개발한다면 브라우저의 개발자도구를 이용해 쿠키에 대한 정보를 언제든 접근 가능하다.




대부분 쿠키나 세션의 경우 브라우저에서는 Cookie API 를, 서버 측에서는 Web framework 들이 처리할 수 있는 로직을 내장하고 있으나, 

관련 작업을 할 경우 위와 같이 체크하면서 작업하면 유용하다.


Mozilla 공식 홈페이지는 쿠키의 생성 및 사용 로직이 단순하기 때문에 정해진 용도 이상의 사용, 특히 개인정보의 저장 등을 위해 사용하는 것이 위험하다고 경고하고 있다.




DNS란 웹사이트의 주소 이름을 IP Address 로 매핑시켜 기억할 수 있는 도메인으로 사이트에 접근할 수 있게 해주는 인터넷의 핵심 요소이다. 다음은 verisign 에서 제공하는 How DNS works 의 설명 내용이다.


(1) 쿼리 전송 : 브라우저가 유저로부터 입력받은 도메인 이름을 이용해 쿼리를 날린다. 쿼리는 ISP 나 무선 통신업체가 운영하는 DNS Resolver 서버에 도달하여 도메인네임을 IP로 바꾸기 위해 어떤 DNS 서버에 요청해야 하는지를 질의하게 된다.


(2) 루트 서버 : DNS Resolver 가 상호작용하는 첫번째 DNS 서버를 루트 서버라 하며, 루트 서버는 .com 과 같은 최상위 도메인에 대한 DNS 정보를 알고 있다. 이 루트서버는 쿼리를 요청한 지점에서 멀지 않은 도메인으로 전송시켜 준다.


(3) TLD 서버 : TLD 서버는 최상위 도메인 내에 차상위 도메인에 대한 주소 정보를 저장하는 서버로 쿼리가 TLD 서버에 도착하면 TLD 서버가 도메인 네임에 대한 IP주소를 답변한다.


(4) DNS : 서버를 거친 쿼리문이 도메인 네임에 대한 IP 주소를 DNS Resolver 로 전송한다.


(5) Web site : DNS Resolver 는 문의한 도메인 네임에 해당하는 IP주소를 브라우저에 알려주고 브라우저는 IP 주소로 접근하여 컨텐츠를 가져온다.



DNS 정보를 조회하는 Flow 는 다음과 같다.


1. 클라이언트(웹 브라우저)가 DNS 를 조회하는 쿼리를 발생시킨다.


2. 발생한 요청은 Local DNS 서버로 이동한다. 이 때의 요청은 다음과 같은 DNS Resolver 들을 거치게 된다.

 - 클라이언트에 저장된 로컬 네임 서버 DNS 캐시

 - Internet Service Provider(ISP) 가 제공하는 네임 서버 DNS 캐시

캐시에서 값이 있으면 해당 IP 정보를 응답값으로 전달한다.

여기서 ISP 가 제공하는 네임서버 DNS 캐시가 의무적으로 존재하지는 않는다. ISP 에 따라 존재하는 경우도 있고 없는 경우도 있다. 없다면 요청은 Local PC 의 설정만 조회해보고 바로 3번 이후로 넘어간다.


3. DNS 주소에 대한 쿼리가 Root 서버로 향한다. Root 서버는 .com 과 같은 최상위 도메인에 대한 DNS 정보를 포함한다.

도메인 정보에 따라서 일반 최상위 도메인(.com, .net, .org ...) 이나 국가 최상위 도메인(.kr, .jp ...) 으로 분류되어 각각에 알맞는 TLD 서버의 정보를 전달받는다.


4. TLD 서버 정보를 이용해 TLD 서버에서 해당 도메인 주소(tistory.com)에 대한 관리 주체 서버로 연결해준다. 


5. 도메인 주소를 관리하는 DNS 서버에서 해당 도메인 주소에 대한 IP 를 반환해준다.


6. 반환된 IP 가 응답으로 전달되면서 Local DNS 에 캐싱된다.


7. Local DNS 를 통해 클라이언트(웹 브라우저) 는 IP 정보를 얻게되고 해당 서버로 접근하게 된다.



DNS 를 관리하는 구조는 트리형태로 되어있으며 클라이언트가 트리 형태의 DNS 구조에서 쿼리를 통해 결과값을 받아가는 과정이 재귀적(Recursive)이라 하여, Resolver 를 Resulsive Resolver, Query 를 Recursive Query 등으로 부르기도 한다.



 


 SQL Injection 은 웹에서 DB에 접근하는 쿼리 자체에 공격을 가하는 것으로 해커들이 많이 이용하는 기법 중 하나이다

주로 학부과정의 컴퓨터 보안 시간에 기본적으로 다루는 내용이지만, 실제로 볼 일이 많이 없다가 대응해야할 일이 생겨서 정리해보았다. 


SQL Injection 을 이용해서 대표적으로 다음과 같은 방식의 공격이 사용된다.


(1)  인증 우회(Auth Bypass)

 : SQL 쿼리문의 TRUE/FALSE 논리 연산 오류를 이용해서 인증 쿼리문이 무조건 TRUE가 나오게끔 하는 방식이다. Select count(*) from member where uid=’입력된 아이디’ and pw = ‘입력된 비밀번호에서 입력된 아이디부분에 ‘ or 1=1# 과 같은 내용을 삽입하면 뒤의 내용이 주석처리 되면서 무력화 시킬 수 있다.


(2)  Data disclosure (데이터 노출)

 : 서버가 반환하는 Error 등을 참고해 정보를 파악해서 각 Column과 자료형을 알아내는 방식이다. www.example.com?idx=1’ having 1=1# 과 같은 방식으로 날리는 쿼리 파라미터를 분석한 뒤, 위와 같이 요청했을 때 반환되는 에러메시지를 분석해서 테이블을 유추한다. Error Based / Union Based 와 같은 공격이 이런 방식을 이용한다. 에러 메시지가 뜨지 않는 경우 Delay 함수등을 Inject 하여 응답시간으로 동작을 유추한다.

 : 그 외에도 Procedure 호출 이나 명령어 삽입 등의 공격도 있다.



 위의 내용은 SQL Injection 의 원론적인 부분이고 실무에서 쓰는 프레임워크들은 대부분 SQL Injection 에 대해 방어코드가 기본적으로 되어있다

하지만 그래도 개발자로써 기본적으로 숙지해야할 보안의 기본적인 원칙들은 다음과 같다.


(1)  Validation : 자유롭게 입력가능한 값에 대한 Validation 이 필수적이다.


(2)  Parameter : 특히 API등에서도 Parameter 는 특수문자나 SQL 명령어가 입력될 수 없도록 필터링이 필요하다. 물론 더 문제인건 입력받는 Parameter를 바로 DB로 전달하는 것이다


(3)  Prepared : SQL 입력이 되는곳은 Prepared Statement 를 통한 처리나 ORM 등을 통해 매핑시켜서 사용하는 것이 안전하다.


(4)  권한 : 데이터 다루는 권한은 최소한으로 부여한다.


(5)  에러메시지는 노출하지 않는다.


(6)  동적 SQL의 사용은 지양한다.

 



Software 를 다루는데 있어서 Proxy 는 흔히 접할 수 있는 단어이다.

많은 IT에 대해 잘 알지 못하는 사람들도 "우회" 를 목적으로 운영되는 프록시 서버에 대해 알고 있으며, Software 공학에 있어서 Proxy 는 상식처럼 알아두어야할 개념 중 하나이다. 

크게 많이 사용되는 Proxy 용어의 의미는 Proxy 서버, Proxy Design Pattern 등 다양하지만, 본 포스팅에서는 프록시 서버에 대해 다룬다.


Proxy 란 대리 혹은 중계 Agent 로써의 의미를 지니며 프록시 서버란 대리자 역할을 하는 서버를 말한다.


즉, 웹브라우저에서 Request를 보냈을 때 내 IP가 아닌 Proxy 서버의 IP로 웹서버에 접근하여 요청과 응답을 처리한 후 Proxy 서버에서 나에게 응답을 해주게 된다.


이렇게 프록시 서버를 사용하는 목적은 3가지 정도가 있다.


목적에 따른 분류


(1) 익명성과 우회접근


 : 서비스형태로 제공하는 Proxy 가 주로 이 범주에 속하며, IP를 숨기는 것이 주 목적이다. 요청을 대신 수행해주는 프록시 서버를 통해 우회하여 서버에 접근이 가능하다. 

IP 를 숨긴 채 모든 사용자가 접근가능하도록 허용하는 Open Proxy 형태가 있다.



(2) 서버의 부하를 줄여주기 위한 Filter 이자 Cache


 : Proxy 를 도구로써 사용하는 가장 대표적인 예시이다. 프록시 서버가 서비스 서버에 작업하는 위치와 네트워크 구성에 따라서 Forward Proxy / Reverse Proxy 로 구분된다.


 - Forward Proxy : 일반적인 프록시 서버를 말하며, 요청하는 Client 와 Service Server 사이에 위치하여 중간에서 요청을 중계한다. 가령 서버에 요청이 들어왔을 때, 요청은 Proxy를 거쳐 서버에 전달되며 이 과정에서 별도 작업을 처리해서 Service Server로 전달하거나, Cache 로써의 역할을 한다.


 - Reverse Proxy : 마찬가지로 Client 의 요청이 실제 Service Server의 도메인으로 이루어지는 것이 아닌 프록시 서버로 이동한다. 

Forward Proxy 와는 다르게, Service Server 들이 대게 내부망으로 구성되며 프록시에서만 연결을 허용하게 만들어 서비스를 위한 보안 채널을 구축하는 역할을 한다. 

이런 경우 Client 가 Service Server 에 직접 접근이 불가능하므로 Reverse Proxy 에서 요청을 좀 더 적극적으로 중계하는 Load Balancing 의 역할을 수행하기도 한다.


 : 이렇게 서버 측에 위치한 Cache 를 위한 Proxy 외에도 클라이언트 네트워크 쪽에서 프록시와 캐싱을 함께 수행하는 서버가 따로 존재한다. (Squid와 같은 서버가 예이다.) 

 : 이 때 캐싱 시에는 HTTP 헤더에 Cache-control : no-cache 옵션이 주로 작용하며 주로 포털 사이트에서 해당 헤더를 사용하는 경우를 볼 수 있다.

(Cache-control 옵션에 대해 얘기하자면, no-cache로 지정할 경우 캐싱을 사용하지 않고 실 서버까지 도달한다. 

그렇기 때문에 사용하려면 max-age=3600, must-revalidate 와 같이 지정해야 한다.)



(3) 서버 접근 작업 자체를 담당하는 서버


 : 다소 특수한 케이스로 주로 모니터링 및 데이터 분석을 위해 요청 자체를 기록하고 다루는 형태의 서버 엔진이 있다. 보안, ACL(Access Control List), Log / Audit 등을 위해 사용된다.



웹서비스를 구현하는 데 있어서 보통 사용되는 프록시 서버의 형태는 (1)번과 (2)번으로 Reverse Cache 의 형태가 가장 보편적이라고 할 수 있다.





ORM(Object Relational Model)은 사물을 추상화시켜 이해하려는 OOP적 사고방식과 DataModel을 정형화하여 관리하려는 RDB 사이를 연결할 계층의 역할로 제시된 패러다임으로 RDB의 모델을 OOP에 Entity 형태로 투영시키는 방식을 사용한다. 


이는 시스템에 따라, 사용하는 Database 및 DB Connector 에 따라 달라질 수 있는 데이터 매핑 구조를 객체지향형태로 통일시켜, SQL 구조의 Database를 OOP 구조의 형태로 매핑시키려는 패러다임이다.


OOP 적 구조와 SQL 구조의 차이는 데이터를 다루는 방법에서 나타난다. OOP 적 구조에서 모든 데이터는 객체이며, 각 객체는 독립된 데이터와 독립된 함수를 지닌다. 


반면 SQL 에서 데이터는 테이블단위로 관리되며 객체들을 조회하기 위한 명령어를 사용한다.

ORM 은 각 테이블 또는 구분하고자하는 데이터 단위로 객체를 구현하고, 데이터간의 관계를 형성한다. 가령 개인 정보 저장을 위한 RDB의 테이블 Personal 은 다음과 같은 객체 Personal 로 매핑될 수 있다.



이러한 ORM을 구현하는 대표적인 프레임워크가 Hibernate이며, 이를 Java 표준 방식으로 정의한 것이 JPA이다. 실무에서 개발을 할 때에는 JPA 와 Hibernate 를 같이 사용한다.


위와 같은 매핑을 ORM 을 제공하는 프레임워크들은 매우 간단한 방식으로 지원한다. 가령 JPA와 Hibernate 와 같은 Java 의 ORM 들은 Annotation 을 이용해서 간단한 매핑이 가능하다.

이러한 중간 계층을 Persistence Layer라 하며, ORM 프레임워크 들은 기존의 Entity Bean이 수행하던 Persistence Bridge 의 역할을 ORM 패러다임의 Entity 방식으로 가능하게끔 한다. 


적용되는 기준은 Data Entity와 Object 모델이 Getter와 Setter 그리고 Repository를 이용한 표준 형식으로 정의되며, RDB에서 표현할 수 있는 Join 관계 및 참조 관계, Native 쿼리 등 거의 모든 부분의 매핑이 가능하다.


ORM 은 데이터를 다루는 새로운 관점을 제시하지만 적용하는 데 있어 몇가지 고려해야할 장단점을 갖고 있다. 이는 ORM이 흔히 겪는 RDB와 OOP 사이의 아직 풀지 못한 숙제들을 말하며 다음과 같다.


1. 세밀함의 불일치(RDB 데이터 타입은 Vendor마다 다르며, 더이상 정규화가 힘들다.)


2. 하위 타입 문제 (RDB에는 상속의 개념이 없다.)


3. 동일성 문제(Java의 경우 Equals로 평가 가능하지만, DB는 PK 기준으로 한다.)


4. 연관 관계 – FK는 연관의 방향성을 갖지 못하기 때문에 N:N 모델의 경우 Link 테이블을 도입해야 한다.


5. 데이터 검색 – RDB는 한번에 많이 가져올지(메모리) 빈번하게 데이터를 가져올지(성능) 고민해야 한다.


위의 장단점은 표준 ORM 패러다임이 기존 시스템에 비해 갖는 괴리감 및 과제들이며, 많은 프레임워크들이 위의 숙제를 해결하기 위한 기능들을 제공한다.


하지만 무엇보다도 ORM 을 도입하는 데 있어서 중요하게 생각해야 할 점은 ORM 은 결코 RDB를 개발자로부터 분리시키는 기술이 아니라, 오히려 개발자에게 높은 RDB 지식을 요한다는 사실이다.

개발자는 객체 구조의 설계 시에도 Database 의 스키마를 고려해야하는 숙제를 갖게 되는 것이다.


이는 진입 장벽이 높아지는 단점으로 여겨질 수도 있으나 오히려 숙련된 개발자가 있다면, DB와 코드를 좀 더 직관적으로 연결하고 다룰 수 있게 하는 기술이 될 수 있다.


ORM 의 개념은 아직 우리나라에서는 익숙하지 않다. 아직 많은 개발자들이 SQL Mapper로 대표되는 MyBatis를 선호하는 추세이지만 전세계적으론 Application Service 기술 동향에 있어서 데이터를 다루는 기술의 하나로 각광받고 있다.



* 향후 포스팅의 카테고리를 옮길 예정입니다.



토비의 스프링을 읽어보면 Spring 을 구현하는 데 있어서 핵심이 되었던 철학들에 대해 설명을 해주는데, 그 중 하나가 PSA이다.


POJO 관련 포스팅에서 조금 언급을 했지만, Spring 은 언어가 아닌 기술 그자체에 얽매이는 것에 반감을 갖고 있다. 이는 애초에 EE 에 대한 불편함으로 대세로 떠오른 프레임워크이기도 하기 때문이다. 그리고 그러한 철학을 잘반영한 기술 중 하나가 PSA 라고 생각한다. 


*POJO 에 대한 설명은 다음 링크를 참조해보자. (http://jins-dev.tistory.com/entry/Spring-%EC%9D%98-%EA%B8%B0%EB%B3%B8%EC%9D%B4-%EB%90%98%EB%8A%94-POJO-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC?category=760012)



PSA란 Portable Service Abstraction 의 약자로 환경의 변화와 관계없이 일관된 방식의 기술로의 접근 환경을 제공하려는 추상화 구조를 말한

이는 POJO 원칙을 철저히 따른 Spring 의 기능으로 Spring 에서 동작할 수 있는 Library 들은 POJO 원칙을 지키게끔 PSA 형태의 추상화가 되어있음을 의미한다


가령 일반적인 JUnit이나 Mybatis 등의 여러 Java Framework 에서 사용가능한 라이브러리들은 Spring 에서 지원하는 JUnit 이나 MyBatis 라이브러리와 다르다. 또한 JPA를 사용할 때에 있어서도 Spring JPA가 추상화 시켜준다... 따라서 개발자가 Hibernate를 사용하건 EclipseLink 를 사용하건 신경쓸 필요가 없다.


따라서 이러한 외부 라이브러리들은 Spring에서 사용할 때 내부구현이 달라지더라도 동일한 인터페이스로 동일한 구동이 가능하게끔 설계되어 있으며 의존성을 고려할 필요가 없다.



Spring 은 이렇듯 특정 기술에 직접적 영향을 받지 않게끔 객체를 POJO 기반으로 한번씩 더 추상화한 Layer 를 갖고 있으며 이를통해 일관성있는 서비스 추상화를 만들어낸다.




Spring 의 가장 기본적인 특징 중 하나는 POJO이다. 옛날에 Java로 웹 어플리케이션을 만들기 위해서는 Servlet 클래스를 상속받아 구현하는 방식으로 직접 작성했으나 Spring을 이용하면 POJO만으로 웹 어플리케이션 작성이 가능하다

Servlet에 대한 작업은 Spring Framework가 알아서 처리해주고 개발자는 비즈니스 로직의 구현에 집중하게끔 한 것이다


Spring Servlet클래스들은 모두 추상화 라이브러리 형태로 포함되어 있다. 거기에 IoC AOP를 지원하는 컨테이너 프레임워크 라는 기능을 붙임으로써 강력한 프레임워크가 되었다


Spring Container는 객체의 생성과 관리를 담당하며 필요할 때 객체를 생성(Lazy Loading)하고 객체간의 의존관계를 관리한다. 이를 확장한 ApplicationContext는 트랜잭션 관리나 메시지 기반의 다국어 처리 등 다양한 기능을 제공한다. 스프링 컨테이너는 Bean 저장소에 해당하는 XML 설정을 참조하여 Bean의 생명주기를 관리한다.





 위의 그림은 Spring Framework의 구동 원리를 나타낸다.


(1)   웹 어플리케이션이 실행되면 Tomcat(WAS)에 의해 web.xml 이 로딩된다.


(2)   Web.xml이 등록되어 있는 ContextLoaderListener가 생성된다. 이는 ServletContextListener 인터페이스를 구현하고 있으며 ApplicationContext를 생성한다.


(3)   생성된 ContextLoaderListener root-context.xml을 로딩한다.


(4)   Root-context.xml 에 등록된 Spring Container가 구동된다. 이 때 개발자가 작성한 비즈니스 로직에 대한 부분과 DAO, VO 객체들이 생성된다.


(5)   클라이언트로부터 Web Application 요청이 들어온다.


(6)   첫 요청이 들어오면 DispatcherServlet(Servlet)이 생성된다. DispatcherServlet FrontController의 역할을 수행하며 전송받은 요청 메시지를 분석하여 알맞은 PageController에게 전달하고 응답을 어떻게 할지 결정한다. 이 때, servlet-content.xml 이 로딩되며, Controller 등이 매핑된다.


(7)   실질적인 작업은 PageController에서 일어나므로 이 클래스들을 HandlerMapping / ViewResolver 클래스라 한다. 이 작업은 ThreadPool 을 통해 할당된 유저 Thread 에서 처리된다.


(8)   요청은 매핑된 각 Controller로 전달되고 Request ModelAndView 형태의 Object 형식으로 DispatcherServlet 으로 전달한다.


(9)   DispatcherServlet 은 응답 결과값을 각 Model 오브젝트와 View page 를 통해 출력한다. 작업을 처리한 유저 Thread 를 다시 Thread Pool 에 반환한다.


(10) 다음 요청부터는 이전에 생성한 DAO, VO, Impl 클래스들과 협업한다.



+ Recent posts