인프라/네트워크(기초) 2020. 4. 12. 15:57

 

필자는 그동안 프록시서버와 게이트웨이를 혼동해서 용어를 많이 사용했었던 것 같다. 사실 글을 쓰는 지금까지도 이 두개의 차이점을 100% 명확히 구분짓기 힘들지만, 범용적으로 사용되는 프록시서버와 게이트웨이를 뜻을 알아본다.

 

Proxy Server(프록시 서버)

위키에는 프록시 서버에 대한 설명이 아래와 같이 나와있다.

 

프록시 서버 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다. 서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리켜 '프록시', 그 중계 기능을 하는 것을 프록시 서버라고 부른다.
프록시 서버 중 일부는 프록시 서버에 요청된 내용들을 캐시를 이용하여 저장해 둔다. 이렇게 캐시를 해 두고 난 후에, 캐시 안에 있는 정보를 요구하는 요청에 대해서는 원격 서버에 접속하여 데이터를 가져올 필요가 없게 됨으로써 전송 시간을 절약할 수 있게 됨과 동시에 불필요하게 외부와의 연결을 하지 않아도 된다는 장점을 갖게 된다. 또한 외부와의 트래픽을 줄이게 됨으로써 네트워크 병목 현상을 방지하는 효과도 얻을 수 있게 된다.

 

HTTP Protocol 관점에서 조금 더 설명을 더하자면, 프록시 서버는 클라이언트와 서버 사이에 위치하여, 클라이언트의 모든 HTTP 요청을 받아 서버에 전달한다.(대개 요청을 수정한 후에) 이 애플리케이션(프록시서버)은 사용자를 위한 프록시로 동작하며 사용자를 대신해서 서버에 접근한다. 프록시는 주로 보안을 위해 사용된다. 즉, 모든 웹 트래픽 흐름 속에서 신뢰할 만한 중개자 역할을 한다. 또한 프록시는 요청과 응답을 필터링한다. 예를 들어 회사 내부망에서 외부로 요청(메이븐, 그래들 라이브러리 다운로드 등)을 신뢰할만한 요청인지 확인해서 회사 내부정책에서 인정한 인가한 서버만 접속가능하도록 하는 등의 기능이다.

 

밑은 용도에 따른 Proxy server 종류이다.

  • Caching Proxy Server : 이전 클라이언트의 요청 내용과 응답 컨텐츠를 저장해 두었다가 동일한 요청이 들어오면 저장된 컨텐츠를 전송한다. 이 방법을 이용하면 높은 트래픽에 대한 대응이 가능하다. 비용 절감 효과도 있을 수 있기 때문에 Caching Proxy를 자주 사용한다.
  • Web Proxy : 웹 트래픽에 초점이 맞춰진 Proxy 서버이다. 가장 일반적인 형태를 Web Cache이다. 어떤 프록시 서버는 이기종간의 컨텐츠를 변환하는 일을 하기도 한다.
  • Forward Proxy : 일반적으로 사용하는 프록시 방식이다. 프록시 서버는 클라이언트와 애플리케이션 서버사이에 위치하여 클라이언트가 타겟인 서버에 애플리케이션 서비스를 요청할 때, 프록시 서버로 요청을 보내게 된다. 프록시서버는 그 사이에서 중계자 역할을 하게된다.(애플리케이션 서버에게 클라이언트가 누구인지 감춰진다.)
  • Reverse Proxy : 기본적인 구성은 Forward Proxy와 동일하지만, 클라이언트는 Proxy Server 뒤에 있는 타겟서버의 URL이 아닌 Proxy Server의 URL로 요쳥한다. 이를 통해 애플리케이션 서버는 외부로부터 감춰지는 효과를 보게된다.(클라이언트에게 애플리케이션 서버가 무엇인지 감춰진다.)

 

우리가 많이 사용하는 Nginx 같은 녀석도 프록시서버라고 볼수도 있겠다. Nginx는 위에서 설명한 대부분의 기능을 모두다 제공한다.(캐시, 포워드 프록시, 리버스 프록시)

 

Gateway(게이트웨이)

 

위키에는 게이트웨이 서버를 아래와 같이 설명한다.

 

게이트웨이는 컴퓨터 네트워크에서 서로 다른 통신망, 프로토콜을 사용하는 네트워크 간의 통신을 가능하게 하는 컴퓨터나 소프트웨어를 두루 일컫는 용어, 즉 다른 네트워크로 들어가는 입구 역할을 하는 네트워크 포인트이다. 넓은 의미로는 종류가 다른 네트워크 간의 통로의 역할을 하는 장치이다. 또한 게이트웨이를 지날 때마다 트래픽(traffic)도 증가하기 때문에 속도가 느려질 수 있다. 쉽게 예를 들자면 해외여행을 들 수 있는데 해외로 나가기 위해서 꼭 통과해야하는 공항이 게이트웨이와 같은 개념이다.
즉, 게이트웨이는 서로 다른 네트워크 상의 통신 프로토콜(protocol,통신규약)을 적절히 변환해주는 역할을 한다.

 

게이트웨이는 프록시 서버와 비슷하게 클라이언트(혹은 서버)와 서버끼리 통신 사이에 중개자로 동작하는 서비스이다. 하지만 용도가 조금 다르다. 게이트웨이는 주로 HTTP 트래픽을 다른 프로토콜로 변환하기 위해 사용한다. 마치 게이트웨이는 언제나 스스로가 리소스를 갖고 있는 진짜 서버인 것처럼 요청을 다룬다. 클라이언트는 자신이 게이트웨이와 통신하고 있음을 알아채지 못할 것이다.

 

두 컴퓨터가 네트워크 상에서 서로 연결되려면 동일한 통신 프로토콜을 사용해야 하는데, 만약 요청은 HTTP 요청이고 백엔드에서 데이터를 가져오려면 FTP 통신이 필요하다면 중간에 게이트웨이가 두 프로토콜을 호환가능하도록 HTTP->FTP, FTP->HTTP를 대신 해주는 대행자가 되는 것이다.

 

Gateway(게이트웨이)

각각의 의미가 무엇이며 용도에 대해 알아보니 둘의 차이점을 조금이나마 알수 있었다. 둘다 중개자 역할임은 동일하지만 각각의 용도가 다르다는 것을 차이점으로 볼 수 있을 것 같다.

 

프록시 서버는 컨텐트 캐시, 보안, 필터링 등의 역할을 하는 중개자라면 게이트웨이는 서로 다른 네트워크 통신에서 서로 다른 프로토콜을 호환가능하게 하는 특별한 서버라고 볼 수 있을 것 같다.

 

여기까지 간단하게 프록시서버와 게이트웨이 서버가 무엇인지 둘의 차이점을 다루어보았다.

posted by 여성게
:
Web/JPA 2019. 2. 4. 16:42

JPA - 즉시로딩과 지연로딩(FetchType.EAGER,FetchType.LAZY) 그리고 프록시



만약 회원이라는 엔티티 객체와 팀이라는 엔티티 객체가 있고 회원:팀 = N:1 연관관계를 맺고 있다고 가정하자. 만약 회원이라는 엔티티를 데이터베이스에서 조회했을 경우 팀이라는 엔티티 객체를 같이 로딩해서 사용할 수 도 있겠지만 진짜 회원만 사용할 목적으로 엔티티객체를 조회 할 수도 있다. 그렇다면 만약 필요하지 않은 연관관계 객체의 로딩을 뒤로 미룬다면 어떻게 할까? 이것은 불필요한 데이터베이스 조회 성능을 최적화 할 수 있는 기회가 될 수 있을 것이다. 예를 들어 연관관계가 List 필드로 되어있고 연관된 객체가 수만개라면? 그리고 해당 List연관관계의 엔티티는 필요하지 않은 상황이라면? 이럴경우에는 지연로딩이라는 패치전략을 사용할 수 있다. 그리고 필요한 시점에 그 객체를 데이터베이스에서 불러올 수 있다. 






지연로딩, FetchType.LAZY 그리고 프록시 객체



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Entity
@Table(name = "MEMBER_TB")
@Getter
@Setter
public class MemberFetchTypeLazy {
    @Id
    private String id;
 
    private String name;
 
    @ManyToOne(fetch = FetchType.LAZY)
    private TeamFetchType team;
}
 
@Entity
@Table(name = "TEAM_TB")
@Getter
@Setter
public class TeamFetchType {
    @Id
    private String id;
 
    private String name;
 
    @OneToMany(mappedBy="team")
    private List<MemberFetchTypeLazy> members = new ArrayList<MemberFetchTypeLazy>();
}
cs


지연로딩 전략은 위와 같이 연관관계를 명시해주는 어노테이션에 fetch = FetchType.Lazy 처럼 명시 해줄 수 있다. 위의 소스 설명은 회원엔티티를 조회할때 팀엔티티를 즉시로딩하지 않고 지연로딩할 것이라는 소스이다. 이 말은 회원엔티티를 조회할때 팀회원을 같이 데이터베이스에서 가져오지 않고, MemberFetchTypeLazy.getTeam() 으로 실제로 팀엔티티가 사용될 때 데이터베이스에서 해당 엔티티를 조회해오는 것이다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class FetchTypeTest {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //엔티티매니저 팩토리 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
 
        //엔티티매니저 생성
        EntityManager em = emf.createEntityManager();
 
        //트랜잭션 획득
        EntityTransaction tx = em.getTransaction();
 
        try {
            tx.begin();
 
            TeamFetchType team = new TeamFetchType();
            team.setId("team_1");
            team.setName("team_1");
 
            em.persist(team);
 
            MemberFetchTypeLazy member = new MemberFetchTypeLazy();
            member.setId("member_1");
            member.setName("윤여성");
            member.setTeam(team);
 
            em.persist(member);
 
            tx.commit();
        }catch (Exception e1) {
            // TODO: handle exception
            tx.rollback();
        }finally {
            em.close();
        }
        /*emf.close();*/
 
        //엔티티매니저를 새로 생성한 이유는 영속성컨텍스트가 비어있는 상태에서 조회하기 위함(em.clear()도 가능)
        EntityManager em2 = emf.createEntityManager();
 
        MemberFetchTypeLazy findMember = em2.find(MemberFetchTypeLazy.class"member_1");
 
        System.out.println(findMember.getTeam().getName());
        System.out.println();
    }
}
cs






(디버깅화면 잘보세요 !) 

첫번째 결과 사진을 보면 회원엔티티를 조회하면 team필드에는 id=null,name=null이 들어가있는 것이 보일 것이다. 즉, 데이터베이스에서 조회해오지 않는다. 그리고 Console에도 보면 회원엔티티만을 조회하고 있다.


두번째 결과 getTeam().getName() 이후 Console에는 팀을 조회하는 SQL이 생겨나있는 것을 볼 수 있다. 이말은 회원엔티티를 조회할때는 팀엔티티를 가져오지 않고 진짜 팀엔티티가 사용될 시점에 데이터베이스에서 팀 엔티티를 조회해오는 것이다. 


여기서 디버깅화면을 보면 team필드에 이상한 인스턴스이름이 들어가 있는 것을 볼 수 있다.



바로 지연로딩의 핵심이되는 프록시라는 객체가 team필드가 레퍼런스하고 있는 것이다. 맞다. JPA는 지연로딩에 프록시전략을 이용한다. 프록시는 간략히 얘기하면 대행자이다. 팀회원을 조회할때 team에는 실제 팀엔티티가 들어가는 것이 아니고 프록시 객체가 들어가는 것이고, 실제 팀엔티티를 사용할때 이 프록시객체가 대행자역할을 하여 팀엔티티를 바라보고 대신 팀엔티티관련 데이터를 리턴해주는 것이다.(하지만 만약 Team 엔티티가 이미 영속성컨텍스트에 들어가있다면 지연로딩설정을 해도 즉시로딩과 동일하게 영속성 컨텍스트에서 Team엔티티를 가져온다. 그래서 위의 예제는 영속성컨텍스트를 초기화? 할 목적으로 엔티티매니저 인스턴스를 새로 생성하였다.)



프록시 객체를 실제 엔티티를 상속하고 있기 때문에 겉모양을 그대로이다. 그리고 프록시를 내부적으로 실제 엔티티에 대한 레퍼런스를 갖고 직접 실제객체의 데이터를 리턴해준다. 


그리고 한가지더 얘기할 것은 위와 같이 Member.getTeam을 조회할때는 프록시 객체가 엔티티를 초기화요청을 한다. 하지만 엔티티의 필드중 컬렉션으로 되어있는 필드를 초기화하려면 Team.getMembers.get(i)처럼 직접 실제 인덱스로 데이터를 조회할 경우 엔티티초기화요청을 프록시 객체가 보내게 된다. 


<엔티티 필드 중에 컬렉션은 진짜 컬렉션으로 저장될까?>


JPA는 필드중 컬렉션으로 된 타입이 있다면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경한다. 이것을 컬렉션 래퍼라고 한다. 디버깅을 해보면 컬렉션으로 된 타입의 필드가 있다면 PersistentBag라는 컬렉션래퍼로 반환되는 것을 볼 수 있다.





FetchType.EAGER,즉시로딩


 

즉시로딩은 어노테이션만 EAGER로 바꾸어주면 된다. 즉시로딩은 말그대로 즉시 연관된 엔티티도 다 조회를 해오는 것이다. 어려운 설정은 없다. 하지만 하나 주의해야될 것이 있다. 즉시로딩은 연관된 엔티티를 따로따로 조회하는 것이 아니라, 조인을 이용해 하나의 쿼리로 데이터를 가져오기 때문에 만약 외래키에게 널을 허용한다면? 외부조인을, 외래키가 널을 허용하지 않는다면 내부조인을 이용해서 가져온다. (선택적 비식별관계, 필수적 비식별관계)


이것이 주의해야될 상황인것이 만약 특정상황에서 필요한 데이터를 가져오지 못하는 상황이 발생 할 수 있다.(내부조인,외부조인) 

- 외부조인이라면 null값이 저장된 데이터는 가져오지 않는다. 즉, 필요할 수 있는 데이터를 외래키null을 허용함으로써 가져오지 못한다.


<주의사항>

-컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.

 :A 테이블을 N,M 두 테이블과 일대다 조인한다면 SQL 실행 결과가 N곱하기 M이 되면서 너무 많은 데이터를 반환할 수 있고 결과적으로 애플리케이션 성능이 저하될 수 있다.





JPA 기본 패치전략



- @ManyToOne, @OneToOne : 즉시로딩(FetchType.EAGER)

- @OneToMany,@ManyToMany :  지연로딩(FetchType.LAZY)


하지만 대게 정말 필요한 상황을 제외하고는 LAZY, 지연로딩하는 것을 권고한다고 한다.



<optional 속성>


@ManyToOne, @OneToOne (optional = false, true)

 false : 내부 조인

 true : 외부조인


- @OneToMany,@ManyToMany (optional = false, true)

 false : 외부 조인

 true : 외부조인


여기서 특징이라면 엔티티의 컬렉션을 조회할때는 optional 속성과 무관하게 무조건 외부조인을 이용한다는 점이다.


   


posted by 여성게
: