프록시 (Proxy, Proxy Server), 즉시로딩, 지연로딩

박영준·2023년 1월 27일
0

Spring

목록 보기
7/58

1. 정의

  • 프록시(Proxy) = "대리" 의 의미
  • (인터넷과 관련) 특히 내부 네트워크에서 인터넷 접속 시, 빠른 액세스나 안전한 통신 등...을 확보하기 위한 중계서버
  • 클라이언트와 Web서버의 중간에 위치하고 있어, 통신을 대신 받아 주는 것 (클라이언트 ⇌ 프록시 서버⇌ Web서버)
  • 장점
    • 익명으로 컴퓨터 유지 가능 → 컴퓨터 보안 유지 가능
    • 보안 및 통제를 뚫고 나가기 위해 + 역으로 IP 추적 당하지 않기 위해 사용 → 우회 가능
    • 보안 문제로 직접 통신을 주고 받을 수 없는 사이에서, 프록시를 통해 중계 가능

2. 종류

1) 포워드 프록시

(1) 정의

  • 클라이언트의 대신, 프록시 서버가 외부 Web 서버(목적 서버)와 통신
    → 따라서, 클라이언트는 프록시 서버만을 통해 정보를 얻게 된다.
    → 따라서, Web 서버쪽에서는 프록시 서버를 통한 액세스 로그가 남는다.

  • 어떤 프록시 서버를 경우하도록 할 것인가는 클라이언트가 설정

(2) 장점

① 캐시 저장(액세스 고속화)

프록시 서버에 캐시를 저장 가능
→ 다시 동일한 페이지를 리퀘스트 했을 때, 캐시에 남아 있는 정보를 클라이언트에게 준다.
→ 결과적으로, 사이트 접속 속도 ↑

② URL 필터링

외부의 액세스는 프록시 서버를 경유하므로, 사용자 전원의 외부 웹 사이트로의 액세스를 필터링 가능
(= 클라이언트쪽의 설정을 담당)

2) 리버스 프록시

(1) 정의

  • Web 서버쪽에 위치하여, 클라이언트의 접근을 최초로 받아 리퀘스트에 해당하는 Web 서버에 배분
    (= 서버쪽의 설정을 담당)

  • 각 클라이언트가 프록시 서버에 집약된 액세스를 보내고, URL에 따라 리퀘스트 받을 Web 서버가 바뀐다.
    → 프록시 서버가 Web 서버와 같은 동작을 하므로, Web서버가 여러 개 존재하는 것을 은폐할 수도 있다.(클라이언트의 입장에서는 구분 할 수가 X)

  • 오늘날 전형적인 형태

(2) 장점

① 캐시의 저장

(포워트 프록시와 동일하게) 동일한 데이터를 얻을 때에 프록시 서버가 저장했던 내용을 돌려준다.

② 부담 분산

설정(URL에 따라 리퀘스트 받을 Web 서버가 바뀌는)으로 정적/동적 콘텐츠의 보는 곳을 나눔으로써, 메모리 사용량의 효율화
→ 로드 밸런스와 병용하면 더욱 부담을 분산 가능

③ 세큐리티 대책, 바이러스 대책

통신 시, 프록시 서버에 집약되므로
프록시 서버 내에 세큐리티 대책, 바이러스 대책을 구현하여 Web 서버로의 부정 액세스, 사용 등..을 방지 가능

3. 즉시 로딩, 지연 로딩

1) 즉시 로딩

(1) 정의

  • 조회 시, 연관된 데이터를 한번에 모두 조회

  • 엔티티를 조회할 때, 자신과 연관되는 엔티티를 조인(join)을 통해 함께 조회하는 방식

  • 예시 : Member Entity 조회 시점에 Member Entity와 연관관계를 맺고 있는 Team Entity까지 DB로 부터 함께 조회

  • @ManyToOne , @OneToOne 어노테이션의 경우 : FetchType.EAGER 을 기본 전략으로 사용

(2) 사용법

① Book과 Category 엔티티가 관계를 맺고 있을 때

@Entity
@Table(name="book")
public class Book{
	@Id
	@Column(name="no")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Integer no;
	
	@Column( name="name", nullable=false, length=100 )
	private String title;
	
	@ManyToOne(fetch = FetchType.EAGER)		// 즉시 로딩 방식
	@JoinColumn(name="category_no")		// 만약, @JoinColumn(name="category_no", nullable=false) 로 수정하면, inner join 을 수행
	private Category category;
}

public static void eager(EntityManager em) {
	Book book = em.getReference(Book.class, 1);
	Category category = book.getCategory();
			
	System.out.println(book.getTitle());
	System.out.println(category.getName());
}

(출력 되는 쿼리)

  • Book 엔티티를 조회하는 find() 메서드를 호출할 때, 조인(join)을 수행 + 이 때 Category 도 조회

  • outer join보다 inner join이 성능이 더 좋다는 것을 감안하여, 외래키의 null 허용 여부를 결정

(3) 문제점

어떤 엔티티를 조회하는데,
그 엔티티와 관련된 모든 엔티티들이 함께 조회 된다면 성능상의 문제가 발생할 것이다.

예시 1
Category와 Book 엔티티가 관계를 맺고 있을 때,
책의 이름을 사용하는 상황(book.getTitle())에서 사용하지 않는 Category 엔티티가 함께 조회되는 상황(= 즉시 로딩)
→ 카테고리 이름이 필요한 상황이 있을 수는 있으나, 항상 유용한 방식은 아니다.
→ 그래서, 카테고리 이름이 필요할 때(book.getCategory().getName()), 그제서야 조회하도록 해야한다.(= 지연 로딩)

예시 2
페이스북 "댓글 더보기"에서 즉시 로딩을 사용할 경우
"댓글 더보기"를 누르지 않았음에도, 이미 댓글이 조회 돼버린다.
→ 성능이 느려짐
→ 그래서, 이 때는 "댓글 더보기"를 클릭 했을 시, 댓글 목록을 불러오도록 한다.(= 지연로딩)

예시 3
쇼핑몰에서는 장바구니에서 '유저 + 상품'을 같이 보여줘야 한다.
→ 즉시 로딩이 유용

2) 지연 로딩

(1) 정의

  • JPA는 Entity가 실제로 사용되기 전까지 DB 조회를 지연할 수 있도록 제공

  • '실제 사용하는 시점' 에 DB에서 필요한 데이터를 가져오는 것

  • 자신과 연관된 Entity를 실제로 사용할 때, 연관된 Entity를 조회(SELECCT)하는 것

  • 지연 로딩을 사용하면, 실제 엔티티 객체 대신 가짜 객체(= 이 객체가 '프록시 객체')가 필요
    (프록시 객체는 실제 엔티티 클래스를 상속 받아서 만들어지므로, 실제 엔티티와 겉모습 동일함)

  • @OneToMany, @ManyToMany 어노테이션의 경우 : FetchType.Lazy 을 기본 전략으로 사용

  • 성능 면에서 지연 로딩을 사용하는 것이 더 유용하다. (굳이 필요없는 DB 조회 ↓)
    → 애플리케이션 개발 시에는 모두 지연 로딩으로 한 후, 성능 최적화가 필요한 부분은 즉시 로딩으로 바꾸는 전략 사용
    → 모든 연관 관계에 지연 로딩을 사용하는 것을 추천

(2) 사용법

public static void proxy1(EntityManager em) {

	// 1. getReference() 메서드를 호출하면, Book 엔티티(실제 엔티티)를 상속받은 Book Proxy(Entity proxy 객체)를 반환
    	// 단, 아직은 영속성 컨텍스트(persistence context) 에 엔티티가 없음 (지연 로딩 이므로)
    	// getReference() 메소드 : 프록시를 얻을 수 있음
	Book book = em.getReference(Book.class, 1);
	
    // 프록시를 사용하면, 엔티티가 사용될 때 까지 조회하지 않고 있다가 필요할 때 조회
    // 2. book.getTitle() 메서드를 호출해서, 책 제목 데이터를 얻는다.
    	// 단, 실제 엔티티를 참조하는 변수(target)가 있지만, 영속성 컨텍스트(persistence context)에는 Book 엔티티가 없으므로 null 을 가리키고 있음
    // 3. 따라서, getTitle() 메서드를 실행되려면 엔티티 정보가 필요하므로, DB에 접근해서 book 엔티티를 조회 (초기화 요청)    
        // SELECT 쿼리가 수행 (지연 로딩)
   // 4. DB 조회 결과 Book 엔티티가 영속성 컨텍스트(persistence context)에 저장되면, 프록시는 방금 조회된 엔티티를 참조    
   // 5. 그래서, 원래 하려고 했던 동작인 getTitle()을 수행하여, 책 제목을 반환
	System.out.println(book.getTitle());
}

  • Entity proxy 객체(Book Proxy)는 실제 엔티티(Book 엔티티)와 겉모습이 같기 때문에, 실제 엔티티를 사용하는 것처럼 느낄 수 있다.

    그러나,
    Entity proxy 객체(Book Proxy)는 실제 엔티티가 아니기 때문에
    엔티티의 정보가 필요하면, 실제 엔티티(Book 엔티티)에 접근해서 데이터를 가져온다.

  • 프록시 객체가 참조하는 실제 엔티티(Book 엔티티)가 영속성 컨텍스트(persistence context)에 생성되어 있지 않을 때,
    영속성 컨텍스트(persistence context) 에 실제 엔티티 생성을 요청 + 생성된 실제 엔티티를 프록시 객체의 참조 변수에 할당하는 과정

만약 1. 에서
이미 영속성 컨텍스트(persistence context)에 엔티티가 있는 경우라면,
getReference() 메서드를 호출해서 실제 엔티티를 반환한다. (지연 로딩 방식 X)

참고: 영속성 컨텍스트 (persistence context)

② Book과 Category 엔티티가 관계를 맺고 있을 때

@Entity
@Table(name="book")
public class Book{
	@Id
	@Column(name="no")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Integer no;
	
	@Column( name="name", nullable=false, length=100 )
	private String title;
	
	@ManyToOne(fetch = FetchType.LAZY)		// 지연 로딩 방식
	@JoinColumn(name="category_no")
	private Category category;
}

public static void eager(EntityManager em) {
	Book book = em.getReference(Book.class, 1);
	Category category = book.getCategory();
			
	System.out.println(book.getTitle());	// 2.
	System.out.println(category.getName());		// 3.
}

(출력 되는 쿼리)

Member와 Team을 자주 함께 사용한다 -> 즉시 로딩

Member와 Order는 가끔 사용한다 -> 지연 로딩

Order와 Product는 자주 함께 사용한다 -> 즉시 로딩

→ 이렇게 구분해뒀지만, 사실상 실무에서는 지연 로딩을 주로 사용하는 방향으로 가자.


참고: 프록시(Proxy)란?
참고: [JPA] 프록시, 즉시 로딩과 지연로딩
참고: Proxy Server(프록시 서버)란?
참고: [Spring JPA] 프록시( proxy )와 지연로딩
참고: [JPA] 즉시 로딩과 지연 로딩(FetchType.LAZY or EAGER)

profile
개발자로 거듭나기!

0개의 댓글