반응형

1. 개요

 수행하려는 일과 관련 없어 보이는 예외가 튀어나올 수 있을까? 있다. 바로 체인된 메서드가 저수준의 예외를 처리하지 않고 바깥으로 던져버릴때이다.

 

예외를 찾아서...

 

 예외 발생의 원인을 찾기 위해 내부 구현을 모조리 뒤져야한다는 것이다. 자연스레 결합도가 높아질것이다.

 만약 buy() 메서드에서 클라이언트에게 정확한 예외 메시지를 주고싶다는 등의 이유로 이 예외를 잡아 처리한다면 어떨까? 내부 구현을 분석한 후 적절한 예외 메시지를 던져야할것이다. 

 최종적으로 IllegalStatusException에 대한 catch 문을 추가하고 "상품 유효성 검사에 실패했습니다. 관리자에게 문의해주세요" 라는 예외를 던지도록 예외를 전환했다. 하지만 이것도 문제가된다.

 

2. checkStock() 에서 발생한 IllegalStatusException

 며칠 뒤 buy() 메서드 호출과 함께 어디선가 IllegalStatusException이 발생했다. 클라이언트에게는 "상품 유효성 검사에 실패했다"라는 예외 메시지 던져주고 있다. 그런데 확인해보니 이번 예외는 productValidation()가 아닌 checkStock() 에서 발생했고, 재고 관련 인스턴스를 생성할 때 발생했다.

 

재고 관련 문제가 발생했는데 "상품 유효성 검사에 실패했다"는 에러 메시지는 사용자와 개발자 모두를 혼란에 빠지게했다.

결론은 예외를 무작정 던져도 문제이지만, 무작정 던진 걸 상위 수준에서 무작정 catch 해서 처리하는 것도 문제라는 것이다.

 

자, 그럼 어떻게 했어야할까? productValidation()에서 예외를 잡아 본인의 추상화 수준에 맞는 예외로 전환해서 던졌어야한다. ProductValidationException과 같이 말이다. 예외를 밖으로 던지는 것 자체가 책임을 전가하는 것이기에 결합도 측면에서도 좋지 않다.

 

try{
    // 제품 유효성 체크 로직
}catch(IllegalStatusException e){
    throw new ProductValidationException("제품 유효성 체크에 실패하였습니다. 관리자에게 문의해주세요!")
}

 

이처럼 자신의 추상화 수준에 맞는 예외 클래스로 바꿔 던지는 것을 예외 번역(exception translation) 또는 예외 전환 이라고 한다.

 

3.  예외 체이닝

예외를 전환하려 했더니, 저수준 예외가 디버깅에 도움이 될것같아 예외를 살리고 싶다면 어떨까? 문제의 근본 원인인 저수준 예외를 고수준 예외에 실어보내는 방법을 사용해야 한다. 이를 예외 체이닝(exception chaining)이라고 한다.

 

try{
    // 제품 유효성 체크 로직
}catch(IllegalStatusException e){
    throw new ProductValidationException(e) // 예외 체이닝
}

 

class ProductValidationException extends Exception{
    ProductValidationException(Throwable cause){
        super(cause);
    }
}

 

대부분의 표준 예외는 예외 체이닝 생성자를 갖추고 있다. 예외 연쇄는 문제의 원인을 프로그램에서 접근(getCause 메서드)할 수 있게 해주며, 원인과 고수준 예외의 스택 추적 정보를 통합해준다.

 

정리

아래 계층의 예외를 예방하거나 스스로 처리할 수 없고, 그 예외를 상위 계층에 그대로 노출하기 곤란하다면 예외 전환을 사용하자. 무턱대고 예외를 전파하는 것보다 예외 전환이 우수한 방법이다. 하지만 남용해서는 안된다. 가능하다면 저수준 메서드가 반드시 성공하도록 해야한다. 예를들어 전달하는 하위 메서드에 매개변수를 전달하기 전에 매개변수에 대한 체크를 하는 방법이 있을것이다.

반응형
반응형

1. NAT Gateway란?

 네트워크 주소 변환 서비스를 말하며, 프라이빗 서브넷의 인스턴스이 외부 인터넷망으로 나갈 수 있도록 함과 동시에 Public IP를 부여하는 역할을 한다.

 

NAT (Network Address Translation)
 네트워크 주소 변환의 줄임말인 NAT는 IP패킷의 TCP/UDP 포트 숫자와 출발지 및 목적지의 IP 주소 등을 재기록하면서 라우터를 통해 네트워크 트래픽을 주고 받는 기술을 뜻한다. IP 를 변환하겠다는 뜻이다.
 NAT를 이용하는 이유는 대개 사설 private Network에 속한 여러개의 호스트가 하나의 공인 IP를 사용하여 인터넷에 접속하기 위함이다.

 


 

2. 인터넷 게이트웨이랑 똑같은데요??

 인터넷 게이트웨이는 서브넷을 인터넷 망과 직접적으로 연결시켜 외부로 나갈수도, 외부에서 접근할 수도 있는 구조이다. 이에 반에 NAT GateWay는 서브넷 내의 인스턴스가 외부, 즉 인터넷망으로 나갈 순 있지만, 외부에서 내부 즉, 해당 인스턴스로의 접근은 불가능하다. 이유는 외부 인터넷망으로 나갈 때에만 Public IP를 부여하기 때문이다.

 


3. AWS NAT Gateway 설정 방법

 

1) 퍼블릭 서브넷에서 NAT 게이트웨이 생성

 프라이빗 서브넷의 인스턴스는 퍼블릭 서브넷에 위치한 NAT 게이트웨이를 통해 인터넷에 연결할 수 있다. 퍼블릭 서브넷에서 NAT 게이트웨이를 생성하자. 인터넷 망으로 나갈 때 설정할 공인 IP가 필요하기에 탄력적 IP 를 새로 할당하거나, 기존에 있는 탄력적 IP를 사용해야한다.

NAT 게이트웨이 생성

 

2) 프라이빗 서브넷 라우팅 테이블에 NAT Gateway 추가

 NAT Gateway 생성이 완료되면, 프라이빗 서브넷의 라우팅 테이블에 NAT Gateway를 추가해주자. 외부로 나갈 때 NAT Gateway로 라우팅되어야 하므로 대상을 0.0.0.0/0으로 설정한다.

NAT Gateway 라우팅

 

3) 테스트

 이제 프라이빗 서브넷의 인스턴스에 SSH 접속 후 아래 명령어를 입력하여 외부와 통신이 되는지 확인하자.

curl https://tlatmsrud.tistory.com

 

통신이 된다면 NAT Gateway를 통해 외부로 나간것임을 알 수 있다.

 

반응형
반응형

1. 개요

 비지니스 코드를 재사용 하는 것이 좋은 것처럼, 예외도 재사용 하는 것이 좋다. 자바 라이브러리는 대부분 API에서 쓰기 충분한 수의 예외를 제공한다. 커스텀 예외를 사용해도 되지만 표준 예외를 사용했을 때 사용자 입장에서 익숙한 예외가 발생하므로 예외 상황 파악과 코드 이해가 쉬워진다.

 

 재사용할만한 표준 예외들을 알아보자.

 

2. 재사용할만한 예외

 

IllegalArgumentException

 호출자가 인수로 부적절한 값을 넘길 때 던지는 예외이다. 예를 들어 반복 횟수를 지정하는 매개변수에 음수를 건넬 때 쓸 수 있다.

 

public class Call {

    private static final String HI = "hello";

    public void sayHi(int cnt){

        if(cnt < 0){
            throw new IllegalArgumentException("cnt 값은 0보다 커야 합니다.");
        }

        for(int i =0; i<cnt; i++){
            System.out.println(HI);
        }
    }
}

 

 

IllegalArgumentException

 

IllegalStateException

 대상 객체의 상태가 호출된 메서드를 수행하기에 적합하지 않을 때 주로 던진다. 예를 들어 제대로 초기화되지 않은 객체를 사용하려 할 때 던질 수 있다.

 

public class Toy {

    private String weapon;

    public Toy(String weapon){
        this.weapon = weapon;
    }

    public void attack(){
        if(weapon == null){
            throw new IllegalStateException("weapon 값이 초기화되지 않았습니다. 초기화시켜주세요.");
        }

        System.out.println(weapon +" 공격!");
    }
}

 

IllegalStateException

 

NullPointerException

 일반적으로 null 값을 갖고 있는 객체나 변수를 호출할 때 발생하지만, 특정 상황에선 이를 재사용할 수 있다. null 값을 허용하지 않는 메서드에 null을 건네면 관례상 IllegalArgumentException이 아닌 NullPointerException을 던진다.

 아래와 같이 attack() 메서드를 실행하기 전, 생성자 메서드 레벨에서 NullPointerException을 사용하는 방법이다. 인스턴스 생성 시점에 멤버필드에 대한 유효성이 보장된다는 점에서 더 좋은 방법이라 생각한다.

 

public class Toy {

    private String weapon;

    public Toy(String weapon){
        if(weapon == null){
            throw new NullPointerException("weapon 을 null 로 설정할 수 없습니다.");
        }
        this.weapon = weapon;
    }

    public void attack(){
        System.out.println(weapon +" 공격!");
    }
}

NullPointerException

 

UnsupportedOperationException

 클라이언트가 요청한 동작을 대상 객체가 지원하지 않을 때 던진다. 보통 구현하려는 인터페이스의 메서드 일부를 구현할 수 없을 때 쓰는데, 예를 들어 원소를 넣을 수만 있는 List 구현체에 누군가 remove 메서드를 호출하면 이 예외를 던질 것이다.

 

public class NoRemoveList<E> extends ArrayList<E> {

    @Override
    public boolean remove(Object o) {
        throw new UnsupportedOperationException("remove는 지원하지 않습니다.");
    }
}

 

UnsupportedOperationException

 

이 외에도 어떤 시퀀스의 허용 범위를 넘을 때 사용하는 IndexOutOfBoundException, 단일 스레드에서 사용하려고 설계한 객체를 여러 스레드가 동시에 수정하려 할 때 사용하는 ConcurrentModificationException 등이 있다.

 

3. 재사용하지 말아야할 예외

 Exception, RuntimeException, Throwable, Error는 재사용하지 말자. 이 예외들은 다른 예외들의 상위 클래스이기 때문에 예외 상황에 대한 정보를 명확하게 전달하기 어렵다.

 

4. 정리

 상황에 부합한다면 항상 표준 예외를 재사용하자. 단, 표준 예외를 사용하기 전 API 문서를 참고해 그 예외가 어떤 상황에서 던져지는지 꼭 확인해야 한다. 예외의 이름 뿐 아니라 예외가 던져지는 맥락도 부합할 때만 재사용한다. 더 많은 정보를 제공하길 원한다면 표준 예외를 확장해도 좋다.

 

반응형
반응형

서브넷이 뭔가요?

서브넷은 서브 네트워크를 말하며, 기존 네트워크 영역을 분할해 더 작은 크기의 네트워크 영역으로 쪼갠 네트워크이다. (말 그대로 서브 네트워크...) AWS에서의 서브넷은 VPC 내에 생성하는데, VPC 가 가진 CIDR 블록(기존 네트워크 영역) 내에서 더 작은 CIDR 블록(더 작은 크기의 네트워크 영역) 로 쪼갠 네트워크 영역을 말한다.

 

아래와 같이 VPC를 10.0.0.0/16 으로 CIDR 대역을 설정한 후 10.0.10.0/25, 10.0.20.0/25 CIDR 블럭을 갖는서브넷을 각각 생성하였다. 즉, A 서브넷은 10.0.10.0 ... 10.0.10.127 IP 대역을, B 서브넷은 10.0.20.0 ... 10.0.20.127의 IP 대역을 갖게 되는것이다.

서브넷팅

 


퍼블릭 서브넷, 프라이빗 서브넷??

서브넷 유형을 말하며, 이 두 유형은 서브넷을 생성할 때 지정하는게 아니라 서브넷에 할당된 라우팅 테이블에 의해 지정된다. 퍼블릭의 의미는 인터넷 망, 정확히 말하면 인터넷 게이트웨이와 직접 연결되어 있는 것을 말한다.

 

다시말하면 퍼블릭 서브넷은 라우터에 의해 인터넷 게이트웨이와 직접 연결되는 서브넷이고, 프라이빗 서브넷은 인터넷 게이트웨이와 직접 연결되지 않은 서브넷이다. 라우팅 테이블 설정에 따라 퍼블릿 서브넷이 프라이빗 서브넷이 될수도, 그 반대가 될수도 있다.

 

퍼블릭 서브넷과 브라이빗 서브넷

 

 


인터넷 게이트웨이가 뭔가요?

인터넷 게이트웨이란 VPC와 인터넷 간에 통신할 수 있게 해주는 VPC 구성요소를 말한다. 줄여서 IGW라고 부른다.

 


라우팅 테이블에 IGW만 추가하면 외부와 통신이 가능한건가요?

IGW에 대한 라우팅 정보를 추가하기만 하면 해당 서브넷 내 리소스들이 인터넷 망으로 나갈 수 있을까? 그렇지 않다. 서브넷 내에 퍼블릭 IP를 가진 리소스가 존재하지 않기 때문이다.

 인터넷 게이트웨이를 설정했다면, NAT 게이트웨이를 설정하거나, 서브넷 상의 리소스에 퍼블릭 IP를 할당해야만이 인터넷 게이트웨이를 통해 인터넷에 연결할 수 있다.

반응형
반응형

AWS VPC 설명서 기반으로 스터디한 내용입니다.

https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/what-is-amazon-vpc.html

 

Amazon VPC란 무엇인가? - Amazon Virtual Private Cloud

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

 

개요

 예전 AWS 로 인프라를 구성한 적이 있는데, 구성 당시에는 누군가 만들어놓은 메뉴얼을 따라하기만 했지, 각각의 리소스들이 왜 필요한지, AWS 내에서 어떤 역할을 하는지에대한 고민은 하지 않았다. 그래서 이번 기회에 AWS 구조와 용어에 대해 이해하고 싶었고, 가장 기본인 VPC부터 공부하게 되었다.

 


Amazon VPC가 뭔가요?

아마존(Amazon) 에서 제공하는 격리된(Private) 가상(Virtual) 클라우드(Cloud) 서비스를 말한다. 조금 풀어 말하면, 아마존과 같은 퍼블릭 클라우드 환경 일부분을 고객 전용으로 프라이핏하게 사용할 있는 가상 네트워크 말한다. 가상 네트워크라는 말이 알쏭달쏭한데, 글을 읽다보면 이해할 수 있을것이다.

 

"VPC는 리전의 모든 가용 영역에 적용됩니다." ??

Amazon VPC 사용 설명서의 첫 시작 문구이다. 첫 시작부터 발걸음을 돌리고 싶게 만드는 알쏭달쏭한 문구인데, 첫 문구인만큼 이 의미를 이해하는 것이 중요하다고 생각했다. 먼저 리전과 가용 영역에 대해 알아보자.

 


리전(Region)과 가용영역(Availability Zone)

리전 (Region)

리전이란 전 세계에서 데이터 센터를 클리스터링하는 지리적 위치를 말한다. 여기서 지리적 위치라고 표현한 이유는 실제 물리적인 위치보다 더 넓은 범위를 표현하는 용어이기 때문이다. 누군가 "너 어디살아?" 라고 물었을때 "나 서울시 xx구 xx아파트 203동 101호에 살아" 라고 하지 않고 "나 서울살아" 라고 하는것과 같다고 생각하면 된다.

 

데이터 센터
용어만 들으면 데이터가 저장된 센터? DB를 말하는건가? 라고 착각이 들 수 있다. 클라우드 컴퓨팅에서의 데이터 센터란 컴퓨팅 시스템 및 하드웨어 장비가 저장된 물리적 위치를 말한다. 즉, 클라우딩 관련 장비가 저장된 센터를 말한다.

 

Region

 

 

위 그림을 보면 알 수 있듯이 클라우드 서비스를 제공하기 위한 데이터 센터의 지리적 위치는 미국 동부, 서부, 서울, 도쿄 등 지역별로 고루 퍼져있다.

 

가용영역 (Availability Zone)

 리전별로 하나의 데이터 센터만을 운용하고 있을까? 아니다. 최소 2개 이상의 데이터 센터를 가져야 리전으로써의 조건이 성립된다. 즉, 서울 리전에는 실제로 2개 이상의 데이터 센터들이 어딘가에서 운용되고 있는 것이다. 그리고 이렇게 퍼져있는 데이터 센터들을 논리적으로 묶어놓은 것을 가용영역이라 한다.

 

Region - AZ - IDC

 

위 그림은 리전과, 가용영역, 그리고 실제 데이터 센터를 도식화 한것이다.

 


 

"VPC는 리전의 모든 가용 영역에 적용됩니다." !!

다시 돌아가서 이 문구의 의미는 뭔지 생각해보자. 해석하면 "VPC라는 가상 네트워크는 리전 내에 있는 모든 가용영역에 위치시킬 수 있다는 뜻이고 이 말은 하나의 VPC 내에 생성되는 리소스들을 여러 데이터 센터에 위치시킬 수 있다는 뜻이다."

아래 그림과 같이 말이다. VPC가 가상의 네트워크이기 때문에 여러 가용영역을 공유 사용할 수 있는것이다.

 

여러 AZ에 접근가능한 VPC

 

 

VPC 에는 자체 IP가 없고 IP 대역(CIDR) 을 설정하던데...

 VPC를 생성한다고 해서 VPC 자체에 대한 IP가 할당되지 않는다. 말 그대로 "가상의 네트워크"이기 때문이다. 대신 VPC 내에 실제 리소스를 생성할 때에는 해당 리소스에 IP가 할당되는데, 이때 할당되는 IP의 범위, 즉 CIDR를 VPC 생성 시 설정해야 한다. VPC를 생성할 때 IP 프로토콜과 CIDR를 설정하는 이유가 바로 이것이다.

 

VPC 설정의 CIDR 블록 설정부분

 

IPv4 프로토콜을 사용할 경우 CIDR 블록 크기 설정 시 RFC1918 규격과 AWS 자체 VPC CIDR 블럭 규칙에 따라 CIDR를 설정해야 한다. 이를 준수하는 CIDR 블록 크기는 아래로 한정된다. 

 

10.0.0.0/16 ~ /28

172.16.0.0/16 ~ /28

192.168.0.0/16 ~ /28

 

 

RFC 1918
프라이빗 IP의 국제 규격으로 아래 대역 범위를 갖는다.

RFC 1918

 


 

달랑 VPC 만 생성해주지 않아요~

VPC를 생성하면 VPC만 생성되는게 아니라 기본 리소스인 기본 DHCP 옵션 세트, 기본 네트워크 ACL, 기본 보안그룹, 기본 라우팅 테이블도 함께 생성된다. 각각에 대해 알아보자.

 

첫째, 기본 DHCP 옵션 세트

 

DHCP가 뭔가요?

 Dynamic Host Configuration Protocol의 약자로 네트워크에 위치한 컴퓨터 및 기타 장치에 IP 주소와 같은 네트워크 정보를 자동으로 할당하기 위한 프로토콜을 말한다. 네트워크 설정을 DHCP 서버가 중앙 집중식으로 관리하는 클라이언트/서버 모델인 것이다.

 이전에는 네트워크에 위치한 컴퓨터 및 기타 장치의 IP 주소를 수동으로 할당했지만, 오늘날에는 DHCP를 사용해 동적으로 할당하고 있다. 생소하게 느껴질 수 있지만 사실 대부분의 컴퓨터 사용자들이 이 프로토콜을 사용하여 네트워크 설정을 자동화했을 것이다. 필자의 맥북또한 마찬가지인데, 네트워크 탭의 IPv4의 구성 방식이 DHCP를 사용하도록 설정되어 있어 IP, DNS서버, 게이트웨이 주소 등을 따로 설정하지 않아도 자동으로 할당되는 것을 확인할 수 있다.

필자의 맥북 네트워크 설정

 

 

DHCP를 사용하면 뭐가 좋나요?

네트워크 설정을 자동화할 수 있고, 수동 IP 할당 시 발생할 수 있는 IP 충돌 문제를 예방할 수 있다.

 

DHCP 옵션 세트가 뭔가요?

EC2 인스턴스가 실행될 때 DHCP를 통해 자동으로 네트워크 설정이 되도록 DHCP 서버로 요청하는데, 이 DHCP 관련 설정이 담긴 세트이다. 각 리전마다 각기 다른 기본 DHCP 옵션 세트를 갖고 있다.

 

DHCP 옵션 세트가 AWS에서 어떻게 쓰이는지 알려주세요

VPC 내 EC2와 같은 리소스가 실행되면 IP, DNS 서버와 같은 네트워크 설정을 위해 Amazon DHCP 서버로 요청하게 된다. 그럼 DHCP 서버는 VPC 내 설정된 DHCP 옵션 세트를 로드하게 되는데, 이 옵션 세트에 따라 IP 주소와 DNS 서버와 같은 네트워크 설정을 해당 리소스에 할당하게 된다.

 

 참고로 리소스에 네트워크 설정이 정상적으로 할당된 경우 해당 리소스는 자신에게 할당된 IP 정보를 자동으로 라우팅 테이블에 등록하게 되는데, 이러한 과정으로 인해 내부 리소스간의 네트워킹이 가능한 것이다.

 

AWS에서의 DHCP 옵셧 세트 사용 방식

 

그래서 기본 DHCP 옵션 세트는 뭐라고요?

기본 DHCP 옵션 세트란 리소스의 네트워킹 설정을 위해 DHCP 서버가 참조하는 기본옵션들을 말한다. VPC 라는 가상 네트워크 환경에 설정한 CIDR 대역에 맞는 IP로 할당되어야 하지 않겠는가? 이러한 외부 정보가 없다면 어떤 IP를 할당해야하는지에 대한 기준이 잡히지 않을 것이다. (이건 필자의 지극히 주관적인 생각입니다.)

 


둘째, 기본 네트워크 ACL

 

네트워크 ACL이 뭔가요?

네트워크 ACL이란 Network Access Control List의 약자로 '서브넷 수준'에서 특정 인바운드 또는 아웃바운드 트래픽에 대한 접근 제어 리스트를 말한다. 아래는 서브넷이 2개인 VPC 내에서 네트워크 ACL의 역할을 알려주는 그림이다.

 

네트워크 ACL

 

트래픽이 VPC로 들어오면 라우터에서 라우팅 테이블을 확인해 트래픽을 타겟으로 보낸다. 이때 네트워크 ACL로 하여금 해당 트래픽이 서브넷으로 들어가고 나갈 수 있는지를 제어하는 것이다. 네트워크 레벨에서의 방화벽인 셈이다.

 

그래서 기본 네트워크 ACL은요?

AWS에서 기본으로 제공하는 네트워크 ACL로, VPC 내 서브넷을 생성할 경우 네트워크 ACL을 설정해야 하는데, 설정하지 않을 경우 자동으로 할당되는 네트워크 ACL이다. 기본 네트워크 ACL의 정책은 모든 인바운드 및 아운바운드에 대한 IPv4, IPv6 트래픽을 허용한다.

기본 네트워크 ACL의 아웃바운드 규칙
기본 네트워크 ACL의 인바운드 규칙

 


셋째, 기본 보안그룹

 

보안그룹이 뭔가요?

보안 그룹은 VPC 내 리소스에 대한 접근을 제어하는 그룹을 말한다. 예를 들어 특정 IP 에 대한 인바운드를 차단하는 보안 그룹을 만들고, 2개의 EC2 인스턴스의 보안 그룹에 이를 적용한다면, 특정 IP가 두 EC2 인스턴스로 접근하지 못하도록 차단한다.

 

네트워크 ACL과 같은거 아닌가요?

인바운드, 아운바운드에 대한 접근을 제어한다는 점에서 비슷하지만, 적용 레벨이 다르다. 네트워크 ACL은 서브넷 레벨에서, 보안그룹은 리소스 레벨에 적용된다.

 

그래서 기본 보안그룹은요?

VPC를 생성할 경우 기본으로 제공되는 보안그룹이다. 모든 트래픽에 대해 인바운드와 아웃바운드를 허용하도록 정책이 설정되어 있다

 

보안그룹

 

위 그림은 VPC 내 위치한 두 개의 EC2가 기본 보안 그룹을 적용한 상황이다. 기본 보안그룹 설정했으니 모든 포트 및 IP로부터 오는 트래픽을 받을 수 있게 된다. 단, 기본 보안그룹은 인터넷 게이트웨이 또는 NAT 게이트웨이로부터 오는 트래픽을 거부하도록 설정되어 있다. 만약 NAT 게이트웨이나 인터넷 게이트웨이를 사용한다면 커스텀 시큐리티 그룹을 만들어 각 인스턴스에 적용하면 된다.

 


기본 라우팅 테이블

 

라우터부터 알고가자

라우터란 네트워크 데이터 전송을 위해 최적 경로를 선택한 후 네트워크간 통신 있도록 도와주는 인터넷 장비이다.

그렇다면 라우터는 어떻게 '최적의 경로' 찾아낼 수 있는걸까? 그건 바로 라우터가 가진 '라우팅 테이블' 참고했기 때문이다.

 

라우팅 테이블이 뭐에요?

라우팅 테이블이란 네트워크에서 목적지 주소를 통해 물리적 목적지에 도달하기 위한 경로들이 저장된 테이블이다. 라우터로 하여금 최적의 경로를 선택하도록 도와주는 역할을 한다.  라우터가 가진 라우팅 테이블은 목적지에 도달하기 위해 거쳐야할 다음 라우터의 정보를 가지고 있다.

 

라우팅 테이블은 누가 작성하는 건가요?

관리자가 수동으로 작성할 수도 있지만, 일반적으로 라우팅 프로토콜을 통해 자동으로 라우팅 테이블이 만들어진다. 라우팅 프로토콜(Routing Protocol)이란 라우터끼리 경로 정보를 교환하는 프로토콜로 RIP, BGP, OSPF 프로토콜이 있다.

 

그래서 기본 라우팅 테이블은요?

VPC 만들면 기본으로 적용되는 라우팅 테이블이다. 서브넷을 생성할 라우팅 테이블을 명시적으로 할당해야하는데, 이를 하지 않을 경우 이 기본 라우팅 테이블이 서브넷에 할당되게 된다.

 기본 라우팅 테이블에는 로컬 라우팅만 포함된다. 그런데 VPC 내에 NAT 게이트웨이를 생성하면 VPC 기본 라우팅 테이블에 0.0.0.0/0 트래픽에 대한 타겟 경로를 NAT 게이트웨이로 자동 추가한다. 외부로 나갈때 NAT 게이트웨이를 통해 자동으로 IP가 변경하도록 하기 위함이다. 이를 암시적 서브넷 연결이라고 한다.

 

NAT
Network Address Translation(네트워크 주소 변환)의 약자로 네트워크 주소인 IP를 변환하는 용어이다.
NAT 게이트웨이
IP를 변환시켜주는 장비를 말하며, 일반적으로 사설 네트워크에 속한 호스트가 하나의 공인 IP 주소를 사용하여 인터넷망에 접속하기 위해 사용한다.

 

 이 라우팅 테이블에 설정된 타겟에 따라 서브넷의 유형이 결정된다. 서브넷에 할당된 라우팅 테이블에 인터넷 게이트웨이가 추가되어 있을 경우 퍼블릿 서브넷, 인터넷 게이트웨이가 없다면 프라이빗 서브넷으로 구분된다. 이 내용은 아래 게시글을 참고해보면 좋다.

https://tlatmsrud.tistory.com/172#comment14007882

 

[AWS] 서브넷이란? / 퍼블릿 서브넷 / 프라이빗 서브넷

서브넷이 뭔가요?서브넷은 서브 네트워크를 말하며, 기존 네트워크 영역을 분할해 더 작은 크기의 네트워크 영역으로 쪼갠 네트워크이다. (말 그대로 서브 네트워크...) AWS에서의 서브넷은 VPC

tlatmsrud.tistory.com

 

반응형
반응형

개요

 RAG 프로세스 중 외부 데이터를 LOAD하는 단계에서 쓰이는 LangChain의 Document Loader에 대해 알아보자.

 


Document Loader의 종류

Document Loader는 다양한 소스의 데이터를 문서의 데이터로 로드하는 클래스이다. txt 파일, 웹페이지, 유튜브 비디오 스크립트, PPT, CSV 등 등 다양한 데이터를 로드할 수 있다.

 

1) Text Loader

텍스트 파일을 읽어온다. 

from langchain_community.document_loaders import TextLoader

loader = TextLoader("/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/text.txt")
loader.load()

 

[Document(metadata={'source': '/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/text.txt'}, page_content='hi hello,\nis text file\n\n안녕하세요\n텍스트 파일입니다.')]

 

 

2) CSV Loader

CSV 파일을 로드한다. CSV 파일은 IBM에서 제공하는 샘플 CSV 파일을 사용했다. 로우별로 page_content가 분리되고, \n 문자를 구분자로 컬럼에 대한 데이터들이 로더되는 것을 확인할 수 있다.

샘플 CSV 파일

from langchain_community.document_loaders.csv_loader import CSVLoader

loader = CSVLoader(file_path = "/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/Catalog_v2.csv")
loader.load()

 

[Document(metadata={'source': '/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/Catalog_v2.csv', 'row': 0}, page_content='\ufefflevelType: CATEGORY\ncode: Street Lighting\ncatalogType: PRODUCT\nname: Street Lighting\ndescription: Category code for Street Lighting\nsourceLink: http://lighttree.com/Street Lighting'),
 Document(metadata={'source': '/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/Catalog_v2.csv', 'row': 1}, page_content='\ufefflevelType: CATEGORY\ncode: Pedestrian Lighting\ncatalogType: PRODUCT\nname: Pedestrian Lighting\ndescription: Category code for Pedestrian Lighting\nsourceLink: http://lighttree.com/Pedestrian Lighting'),
 Document(metadata={'source': '/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/Catalog_v2.csv', 'row': 2}, page_content='\ufefflevelType: CATEGORY\ncode: Traffic Signal Poles\ncatalogType: PRODUCT\nname: Traffic Signal Poles\ndescription: Category code for Traffic Signal Poles\nsourceLink: http://lighttree.com/Traffic Signal Poles'),
 Document(metadata={'source': '/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/Catalog_v2.csv', 'row': 3}, page_content='\ufefflevelType: CATEGORY\ncode: Controls\ncatalogType: PRODUCT\nname: Controls\ndescription: Category code for Controls\nsourceLink: http://lighttree.com/Controls'),
 ...

 

 

3) Directory Loader

 디렉토리 내 파일들을 로드한다.현재 path 경로에 txt와 csv 파일이 있는데, 이 두가지 파일을 모두 로더하는 것을 확인할 수 있다.

from langchain_community.document_loaders import DirectoryLoader

loader = DirectoryLoader(path = "/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/", glob='*.*')
loader.load()

 

[Document(metadata={'source': '/{경로}/text.txt'}, page_content='hi hello,\n\nis text file\n\n안녕하세요\n\n텍스트 파일입니다.'),
 Document(metadata={'source': '/{경로}/Catalog_v2.csv'}, page_content='\n\n\nlevelType\ncode\ncatalogType\nname\ndescription\nsourceLink\n\n\nCATEGORY\nStreet Lighting\nPRODUCT\nStreet Lighting\nCategory code for Street Lighting\nhttp://lighttree.com/Street Lighting\n\n\nCATEGORY\nPedestrian Lighting\nPRODUCT\nPedestrian Lighting\nCategory code for Pedestrian Lighting\nhttp://lighttree.com/Pedestrian Lighting\n\n\nCATEGORY\nTraffic Signal Poles\nPRODUCT\nTraffic Signal Poles\nCategory code for Traffic Signal Poles\nhttp://lighttree.com/Traffic Signal Poles\n\n\nCATEGORY\nControls\nPRODUCT\nControls\nCategory code for Controls\nhttp://lighttree.com/Controls\n\n\nCATEGORY\nDownlights\nPRODUCT\nDownlights\nCategory code for Downlights\nhttp://lighttree.com/Downlights\n\n\nCATEGORY\nRetrofit Downlights\nPRODUCT\nRetrofit Downlights\nCategory code for Retrofit Downlights\nhttp://lighttree.com/Retrofit Downlights\n\n\nCATEGORY\nAmbient\nPRODUCT\nAmbient\nCategory code for Ambient\nhttp://lighttree.com/Ambient\n\n\nCATEGORY\nBulbs\nPRODUCT\nBulbs\nCategory code for Bulbs\nhttp://lighttree.com/Bulbs\n\n\nCATEGORY\nControllers\nPRODUCT\nControllers\nCategory code for

 

 

4) HTML Loader

 html 파일을 로드한다. 포스팅에 참고하고 있는 LangChain 페이지를 html 파일로 저장 후 로더를 통해 로드를 해봤다. 결과값을 보면 HTML 내에서 '컨텐츠'로 활용할 수 있는 HTML 태그를 제외한 내용들을 문서형식으로 추출하고 있다.

 

LangChain.html

 

from langchain_community.document_loaders import UnstructuredHTMLLoader

loader = UnstructuredHTMLLoader("/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/LangChain.html")

data = loader.load()
data

 

HTML 태그가 없는 형태

 

5) JSON Loader

json 형식의 파일을 로드한다.

 

sample.json

 

!pip install jq # 필요한 패키지 설치

from langchain_community.document_loaders import JSONLoader

import json
from pathlib import Path
from pprint import pprint

file_path='/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/sample.json'
data = json.loads(Path(file_path).read_text())
pprint(data)

결과

 

 

 

특정 노드의 값을 추출할 수도 있다. 아래는 IOUnitIDs 키에 대한 노드를 추출했다.

from langchain_community.document_loaders import JSONLoader

import json
from pathlib import Path
from pprint import pprint

loader = JSONLoader(
    file_path='/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/sample.json',
    jq_schema='.IOUnitIDs',
    text_content=False)

data = loader.load()
pprint(data)

 

[Document(
	metadata={'source': '/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/sample.json', 'seq_num': 1}
    , page_content='{"cics:ABEND-1": "EXEC CICS ABEND", "cics:ASKTIME-1": "EXEC CICS ASKTIME", "cics:FORMATTIME-1": "EXEC CICS FORMATTIME", "cics:GET CONTAINER-1": "EXEC CICS GET CONTAINER", "cics:LINK-1": "EXEC CICS LINK [LGSTSQ]", "cics:PUT CONTAINER-1": "EXEC CICS PUT CONTAINER", "cics:RETURN-1": "EXEC CICS RETURN", "pgm:PROCEDURE DIVISION-1": "PROCEDURE DIVISION", "sql:CLOSE-1": "EXEC SQL CLOSE [TESTMULTI,Zip_Cursor,CusClaim_Cursor]", "sql:FETCH-1": "EXEC SQL FETCH [Zip_Cursor]", "sql:FETCH-2": "EXEC SQL FETCH [CusClaim_Cursor]", "sql:FETCH_ROWSET_NEXT-1": "EXEC SQL FETCH ROWSET NEXT [TESTMULTI]", "sql:OPEN-1": "EXEC SQL OPEN [TESTMULTI]", "sql:OPEN-2": "EXEC SQL OPEN [Zip_Cursor,CusClaim_Cursor]", "sql:SELECT_INTO-1": "EXEC SQL SELECT INTO [POLICY,ENDOWMENT,MOTOR]", "sql:SELECT_INTO-2": "EXEC SQL SELECT INTO [POLICY,HOUSE]", "sql:SELECT_INTO-3": "EXEC SQL SELECT INTO [CUSTOMER,MOTOR]", "sql:SELECT_INTO-4": "EXEC SQL SELECT INTO [POLICY,COMMERCIAL]", "sql:SELECT_INTO-5": "EXEC SQL SELECT INTO [POLICY,COMMERCIAL]", "sql:SELECT_INTO-6": "EXEC SQL SELECT INTO [POLICY,CLAIM]"}')]

 

 

6) PDF Loader

PDF를 로드한다. 페이지별 배열 형태로 로드되며 메타 데이터로 페이지 번호를 제공한다. 

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("/content/drive/MyDrive/Colab Notebooks/langChain_document_Loader_study_file/sample.pdf")
pages = loader.load_and_split()
pages

 

필자의 이력서 PDF 파일을 INPUT으로 넣어본 결과 오타 없이 잘 출력되는 것을 확인했다. 하지만 PDF 내 이미지 파일 형태로 이력서가 박혀있는 경우에는 해당 내용을 읽어오지 못했다. 

 

예를들어 아래는 PDF는 내 이미지 형태로 이력서가 들어있는데, 이 경우 pageContent에 이력서 내용이 포함되지 않는다.

한국산업인력동단 제공 PDF

 

 

pageContent에 이력서 내용이 없음

 

 

참고로 PDF Loader는 종류도 많고, 각각에 대한 성능의 장단점이 존재한다고 하니 잘 찾아보고 사용하자.

 

7) WEB Loader

웹사이트를 로드하여 컨텐츠 정보를 추출한다. 아래 갤럭시 링 관련 기사에 대한 웹사이트 컨텐츠를 추출해보았다. 웹페이지 내 HTML이나 이미지는 모두 제거된, 텍스트 데이터들만 추출된 것을 확인할 수 있다.

 

갤럭시링 관련 기사

from langchain_community.document_loaders import WebBaseLoader # 웹페이지 문서를 로드하는 클래스.

# 리뷰 | 드디어 베일 벗은 삼성 ‘갤럭시 링’, 체험 후 느낀 3가지
loader = WebBaseLoader("https://www.ciokorea.com/news/344012")
data = loader.load()

data

 

 

결과 #1

 

결과 #2

 

 

 

 

이 외에도 LangChain의 Document Loader는 Markdown, Microsoft Office DOCS, XLSX, PPT 파일 등 다양한 형식의 파일에 대한 Loader를 지원하고 있다. 물론, 모두 무료는 아니다. 자세한 내용은 아래 LangChain 공식문서를 참고하여 사용하면 된다.

 


출처

LangChain 공식문서 https://python.langchain.com/v0.1/docs/modules/data_connection/document_loaders/file_directory/

 

File Directory | 🦜️🔗 LangChain

This covers how to load all documents in a directory.

python.langchain.com

 

RAG 위키독스 https://wikidocs.net/231645

 

2-2-3. 디렉토리 폴더 (DirectoryLoader)

### DirectoryLoader 이용하여 특정 폴더의 모든 파일을 가져오기 `DirectoryLoader`를 사용하여 디렉토리 내의 모든 문서를 로드할 수 있습니다. `Di…

wikidocs.net

 

반응형
반응형

개요

 검사 예외 (Checked Exception)과 런타임 예외(Unchecked Exception), 에러의 개념과 차이에 대해 숙지하고 있지만, 어떤 상황에서 이러한 예외들을 적용할지에 대한 명확한 기준은 잡혀있지 않았다. 필자의 경우 모든 예외를 런타임 예외로 던졌다. 검사 예외를 사용할 경우 동일 트랜잭션 내 예외 발생 시 롤백하지 않는다는 것이 이유였다. 이번 아이템을 통해 예외처리에 대한 보다 명확한 기준을 잡도록 하자.

 


검사 예외(Checked Exception)는 언제? 

호출하는 쪽에서 복구하리라 여겨지는 상황에 사용한다.

 

위 내용이 검사 예외와 런타임 예외를 구분하는 기본 규칙이다.

검사 예외를 던지면 호출자는 예외에 대한 처리가 강제된다. 여기서의 예외는 '회복 가능한 예외'이다. 본인이 발생시킨 예외에 대해 호출자가 회복이 가능하다고 판단된다면 검사 예외를 던지면 된다.

 

* 아마 검사 예외에 트랜잭션에 대해 알아본 독자들이라면 검사 예외일 때는 트랜잭션 롤백이 되지 않는다는 것을 알고 있을 것이다. 검사 예외가 발생했고, 로직에서 복구했는데 트랜잭션이 롤백이 된다면, 복구시킨들 무슨 소용이 있으랴... 트랜잭션과 검사 예외에 대한 관계를 생각해보면 '회복 가능한 예외'일때 검사 예외를 던지는 것을 더 쉽게 이해할 수 있을것이다.

 


비검사 예외(Unchecked Exception) 는 언제?

 호출하는 쪽에서 복구가 불가능하리라 여겨지는 상황에 사용한다.

 

비검사 예외는 회복 불가능한 예외이다. Runtime Exception 과 Error 이 두가지로 구성되는데 이 둘 모두 회복이 불가능하다고 여겨질 때 사용하며, 통상적으로 잡지(catch) 말아야 한다. 자신만의 커스텀 예외로 예외 전환을 하는 목적으로 사용하는것은 상관 없지만, 이를 잡아 '복구'시킬 필요는 없다. 오히려 복구시키면 문제가 된다. (feat. 트랜잭션)

 

이처럼 복구가 불가능한 것을 책에서는 '프로그래밍 오류'라고 칭한다.


복구 가능 여부는 어떻게 판단하나?

복구할 수 있는지 아닌지는 명확히 구분되지 않는다. 예를들어 시스템 자원이 고갈된 원인이 엄청난 양의 배열을 생성한 것이라면 프로그래밍 오류라고 할 수 있지만, 폭발적인 요청에 의해 일시적으로 자원이 부족하여 발생했다면 시간을 두고 재요청을 하는 방식으로 복구할 수 있다. 결국, 복구 가능하냐, 불가능하냐에 대한 기준. 검사 예외, 비 검사 예외를 사용하는 것에 대한 기준은 오롯이 API 설계자의 판단에 달렸다. 복구가 가능하다고 믿는다면 검사 예외를, 그렇지 않다면 런타임 예외를 사용할 것이다.

 


정리

복수할 수 있는 상황이라면 예외 검사를, 프로그래밍 오류라면 비검사 예외를 던지자. 확실하지 않다면 비검사 예외를 던지자.

반응형
반응형

개요

 예외를 예외 상황에서만 사용하지 않는 케이스에 대해 알아보고, 어떤 문제를 야기하는지 이해해보자.

 


예외 상황에서 사용하지 않는 예

MyClass[] range = new MyClass[5];
range[0] = new MyClass();
range[1] = new MyClass();
range[2] = new MyClass();
range[3] = new MyClass();
range[4] = new MyClass();

try{
    int i = 0;
    while(true){
        range[i++].myMethod();
    }
}catch (ArrayIndexOutOfBoundsException e){

}
System.out.println("종료");

 

try catch 문 안에 반복문이 있고, range 배열을 순회하며 myMethod()를 호출하고 있다. (myMethod는 단순 print 문을 호출함) 반복을 하다 range[1++] 에서 배열의 최대 길이를 넘어갈 경우 ArrayIndexOutOfBoundsException 예외가 발생하는데, 이를 예상하여 예외를 잡아 처리하고 있다. 여기서 발생한 예외는 예외 상황이라고 하기엔 민망하기에 catch 문 안에 아무런 처리를 하지 않고있다. 즉, 일반적인 제어의 흐름에 사용한 것이다.

 

뭐 어찌됐던간에 range 를 순회하여 myMethod를 호출하는 것에는 문제가 없다. 


개발자의 의도 파악하기

이를 대체할 수 있는 방법은 여러가지가 있겠지만 위 코드를 작성한 개발자의 의도는 뭐였을까? 바로 성능을 높이기 위해서이다. JVM은 배열에 접근할 때마다 경계를 넘지 않는지 검사하는데, 일반적인 반복문도 배열 경계에 도달하면 종료한다. 어쨌든 배열 경계에 도달하면 예외를 발생시켜 종료하기에 검사 코드를 제거한 것이다. 하지만 이는 잘못된 추론이다. 이에 대한 근거를 알아보자.

 


잘못된 추론

 

첫째, 예외는 예외 상황에 쓸 용도로 설계되었다.

JVM 입장에서 예외의 쓰임이 잘못된 것이다. 쓰임이 잘못됐다면, JVM이 지원하는 기능들을 사용하지 못할 확률이 높다.

 

둘째, 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화해 없애준다.

이게 JVM 이 지원하는 기능 중 하나이다.

 

셋째, 코드를 try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.

JVM의 기능을 사용하지 못하게 되었다.

 


위 코드에 대한 성능 테스트

테스트를 해보면 예외를 사용한 쪽이 표준 관용구보다 훨씬 느리다는 것을 알 수 있다.

// 테스트 데이터 셋팅
MyClass[] range = new MyClass[10000];
for(int i =0; i<10000;i++){
    range[i] = new MyClass();
}


// 예외 사용
long start = System.currentTimeMillis();
try{
    int i = 0;
    while(true){
        range[i++].myMethod();
    }
}catch (ArrayIndexOutOfBoundsException e){

}
System.out.println("걸린 시간 : "+ (System.currentTimeMillis() - start)); // 40~43

// 표준 반복 관용구 사용
start = System.currentTimeMillis();
for(MyClass a : range){
    a.myMethod();
}

System.out.println("걸린 시간 : "+ (System.currentTimeMillis() - start)); // 20~22
System.out.println("종료");

 

일반적인 반복문이 예외처리를 한 반복문보다 2배는 빠르다. 배열을 순회하는 표준 코드를 JVM이 최적화했다는 것을 간접적으로나마 확인할 수 있었다. 성능이 개선될 줄 알았던 예외를 사용한 반복문은 오히려 느리고, 코드를 헷갈리게 (왜 이렇게 코드를 짰지? 뭐가 있나? 라는 생각이 들지 않는가) 하는데 끝나지 않는다. 버그가 발생하고 디버깅이 어려워진다.

 


디버깅을 어렵게 하는 코드

아래 코드는 try 문 내에서 호출되는 myMethod() 이며, 보면 arr[10] 에 "lastDance"라는 문자열을 입력한다. 길이가 10이니 arr[10] 에 접근하는 부분에서 ArrayIndexOutOfBoundsException 예외가 발생할 것이고, 예외 로그를 확인한 개발자는 이를 수정할것이다.

static class MyClass{

    String[] arr = new String[10];

    public void myMethod(){
        System.out.println("call my method");
        arr[10] = "lastDance";
    }
}

 

그런데 실제로는 예외 로그가 발생하지 않는다. 이를 호출한 main 메서드의 catch에 의해 아무런 처리를 하지 않기 때문이다. 개발자는 예외 로그가 찍히지 않으니 문제가 없다고 생각하고 넘어가버리게 된다. 결국 예외는 오직 예외 상황에서만 써야 한다. 절대로 일상적인 제어 흐름용으로 쓰여선 안된다.

오류 없이 실행된 것 같은(?) 로그

 

 


정리

예외는 예외 상황에서 쓸 의도로 설계되었다. 제어 흐름에서 사용해서는 안된다. 코드를 최적화하려다 오히려 망가뜨릴 수 있다.

반응형
반응형

1. 개요

 JVM의 구성요소 중 하나인 네이티브 메서드의 사용에 대한 내용이다. 아직까지 네이티브 메서드를 직접 사용하는 것에 대한 필요성을 느끼지 못하고 있지만, 직접 사용하게 될 경우 어떤 주의사항이 있는지 알아보자.

 


2. 네이티브 메서드와 JNI

 자바에서의 네이티브 메서드란 C나 C++ 같은 프로그래밍 언어로 작성된 메서드를 말한다. 그렇다면 자바에서 C와 C++ 과 같은 언어로 작성된 코드를 직접 호출할 수 있을까? 아니다. JVM 내에 두 언어 간 중간다리 역할을 하는 인터페이스가 구성되어 있다. 이를 JNI라고 한다.

 참고로 JNI 는 JVM 의 구성 요소 중 하나이다. 이에 대해 알고싶다면 아래 게시글을 읽어보면 좋다.

https://tlatmsrud.tistory.com/148#google_vignette

 

[Java] JVM, JDK, JRE / 차이 / JVM 구조

JVM이 뭐야? JVM(Java Virtual Machine)은 바이트코드(.class)를 OS에 특화된 코드(기계어)로 변환하고, 이를 실행하는 '가상의 머신'이다. 특정 OS에 특화된 코드로 변환하기때문에 OS 종속적이다. JVM은 JRE에

tlatmsrud.tistory.com

 


3. 네이티브 메서드의 사용 목적

 

1) 레지스트리와 같은 플랫폼 특화 기능을 사용하기 위해

 레지스트리는 윈도우 플랫폼에서 사용하는 기능으로, 윈도우 OS의 설정을 담고 있는 DB를 말한다. 즉, OS 설정을 건드릴 때 네이티브 메서드 사용이 하나의 선택지가 될 수 있다.

 

2) 네이티브 코드로 작성된 기존 라이브러리를 사용하기 위해

 

3) 성능 개선을 위해

 성능에 결정적인 영향을 주는 부분을 네이티브 언어로 작성하면 성능 개선을 가져올 수도 있다.

 

 하지만 성능 개선을 위한 네이티브 메서드 사용은 권장하지 않고 있다. 자바가 발전함에 따라 대부분의 작업에서 다른 플랫폼에 견줄만한 성능을 내고 있기 때문이다. 예를들어 자바 1.1 시절 BigInteger는 C로 작성한 네이티브 메서드를 JNI를 통해 사용했으나 자바 3 에서 업데이트되며 원래의 네이티브 구현보다 더 빨라졌다. 

 


4. 네이티브 메서드의 단점

 네이티브 언어는 OS를 직접 건드리기에 안정성을 보장하지 않는다. 잘못 사용할 경우 애플리케이션의 버그를 유발하거나 메모리를 훼손할 수 있다. 가비지 컬렉터가 네이티브 메모리는 자동으로 회수하지 못하고 추적할 수도 없다.

 이식성도 낮다. 자바에서 JNI를 사용하지 않으면 네이티브 메서드를 사용하지 못한다. JNI를 구성한다해도 이 과정에서 접착 코드(glue code)를 작성해야 하는데, 이는 복잡한 작업이고 가독성도 떨어진다.

 


5. 정리

 네이티브 메서드가 성능을 개선해주는 일은 많지 않다. 네이티브 코드 안에 숨은 단 하나의 버그가 애플리케이션 전체를 훼손할 수 있으므로 신중히 고민하고 사용해야한다.

반응형
반응형

개요

박싱된 기본 타입과 기본타입의 차이를 알아보고, 왜 후자의 사용을 지향하는지 알아보자.

 


기본 타입이란?

자바에서의 타입은 '데이터 타입'을 말한다. 이는 해당 데이터가 메모리에 어떻게 저장되고, 어떻게 처리되어야 하는지를 명시적으로 알려주는 역할을 한다.

자바에서는 여러 형태의 '타입'을 미리 정의하여 제공하는데, 이것을 자바의 기본타입(==Primitive type) 이라고 한다. 참고로 기본 타입 외에도 String, List 와 같은 참조 타입이 있다.

 기본 타입은 8 종류가 있으며, 크게는 정수형, 실수형, 문자형, 논리형 타입으로 나뉜다.

 


정수형 타입 (4종류)

정수란 부호를 가지고 있으며, 소수 부분이 없는 수를 의미한다. int, long, short, byte 타입이 있다.

정수형 타입 메모리의 크기 데이터의 표현 범위
byte 1바이트 -128 .. 127
short 2바이트 -32,768 ~ 32,767
int 4바이트 -2,147,483,648 ~ 2,147,483,647
long 8바이트 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

 

실수형 타입 (2종류)

실수란 소수부나 지수부가 있는 수를 가리키며, 정수보다 훨씬 더 넓은 표현 범위를 가진다. float, double 타입이 있다.

실수형 타입 메모리의 크기 데이터의 표현 범위
float 4바이트 3.4 * 10의 -38승 ... 3.4 * 10의 38승
double 8바이트 1.7 * 10의 -308승 ... 1.7 * 10의 308승

 

 

문자형 타입 (1종류)

컴퓨터는 2진수밖에 인식하지 못하므로 어떤 문자를 어떤 숫자에 대응시키는 약속을 했다. 대표적으로 C언어는 아스키 코드를 사용해 문자를 표현한다. 아스키 코드는 영문 대소문자 및 몇가지 기호를 사용하는 7비트 문자 인코딩 방식이다. 문자 하나를 7비트로 표현하므로 총 128개의 문자를 표현할 수 있다.

 

자바에서는 유니코드를 사용하여 문자를 표현한다. 문자 하나를 16비트로 표현하므로, 총 65,536 개의 문자를 표현할 수 있기에 각 나라의 모든 언어를 표현할 수 있다.

문자형 타입 메모리의 크기 데이터의 표현 범위
char 2 바이트 0 ... 65,536

 

 

논리형 타입 (1종류)

논리형은 참이나 거짓 중 한 가지 값만을 가질 수 있는 타입을 의미한다. boolean 타입이 있다.

boolean 형의 기본값은 false 이며, 1 바이트의 크기를 가진다.

논리형 타입 메모리의 크기 데이터의 표현 범위
boolean 1바이트 true 또는 false

 


 

박싱된 기본 타입이란?

자바에서는 앞서 말한 기본 타입에 대응하는 참조 타입이 하나씩 있는데, 이를 박싱된 기본타입이라고 한다.

예를들어 int, double, boolean 에 대응하는 박싱된 기본 타입은 Integer, Double, Boolean이다.

 


기본 타입과 박싱된 기본 타입의 차이

 

첫째, 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 식별성이란 속성을 갖는다.

박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다. 메모리의 주소가 다르기 때문이다.

 

둘째, 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 null을 가질 수 있다.

null 에 대한 체크가 이루어지지 않는다면 NPE가 발생할 수 있다.

 

셋째, 기본 타입이 박싱된 기본 타입보다 처리 시간, 메모리 사용면에서 효율적이다.

박싱된 기본 타입이 메모리를 더 잡아먹기 때문이다.

 

이 세가지를 무시하고 생각없이 사용했다가는 문제가 발생할 수 있다. 이를 무시했을 때 발생하는 문제들을 예제로 알아보자.

 


예제 1. 오름차순 정렬

Comparator<Integer> naturalOrder = (i, j) -> (i<j) ? -1 : (i == j ? 0 : 1);
System.out.println(naturalOrder.compare(new Integer(1),new Integer(3)));

 

함수형 인터페이스인 Comparator 의 구현체를 람다식으로 구현한 후, 호출하는 예제이다. i 보다 j 가 클 경우 -1, 같을경우 0, i 가 클 경우 1을 리턴한다. naturalOrder.compare 메서드의 매개변수로 값을 넣어 테스트했을 때는 문제는 발생하지 않는 것처럼 보인다. new Integer(1), new Integer(3)을 넣었을 땐 -1이, 반대로 넣었을땐 1이 리턴되기 때문이다. 그렇다면 위 코드의 문제가 뭘까?

 

 

 

바로 같은 값에 대해서는 0을 리턴하지 않는 점이다.

 

첫번째 검사 (i<j) 는 잘 동작한다. i, j가 참조하는 오토박싱된 Integer 인스턴스는 비교를 위해 기본 타입 값으로 변환된 후 비교 연산을 한다. 그런데 두 번째 검사인 (i == j) 에서는 '객체 참조'의 식별성을 검사하게 된다. i와 j 가 같은 값을 갖고있다고 할지라도 인스턴스가 다르기에 false 가 출력되고 결국 1을 반환한다. 즉, 박싱된 기본 타입에 == 연산자를 사용하게 될 경우 문제가 발생하는 것이다.

 


예제 2. 기이하게 동작하는 프로그램 예제

public class UseCase {

    static Integer i;

    public static void main(String[] args) {

        if (i == 42)
            System.out.println("믿을 수 없군!");
    }
}

 

이 프로그램의 실행 결과는 어떨까?

 

 

이 프로그램의 결과는 "믿을 수 없군!"을 출력하지 않지만, 그 전에 기이한 결과를 보여준다. i == 42를 검사할 때 NullPointerException을 던지는 것이다. 원인은 (i == 42) 코드에서 null을 참조하고 있는 참조 타입 i와 42라는 기본 타입을 비교하기 위해 i를 언박싱하게 되는데, 이때 null 참조를 언박싱하므로 NPE 예외가 발생한다. 기본 타입과 박싱된 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 언박싱되기 때문이다. 

 


예제 3.느린 반복문 예제

Long sum = 0L;

for(long i = 0; i <= Integer.MAX_VALUE; i++){
    sum += 1;
}

 

이 프로그램은 지역변수 sum을 박싱된 기본 타입으로 선언하여 느려졌다. sum += 1 부분에서 언박싱과 박싱이 반복해서 일어나기 때문이다. 체감될 정도로 성능이 느려진다.

 


박싱된 기본 타입은 언제 쓰는게 좋을까?

그렇다면 박싱된 기본 타입은 아래와 같은 상황에서 써야한다.

첫째, 컬렉션의 원소, 키, 값으로 쓴다. 컬렉션은 기본 타입을 담을 수 없기때문이다.

둘째, 타입 매개변수로 사용한다. 타입 매개변수로 기본 타입을 지원하지 않기 때문이다.

셋째, 리플렉션을 통해 메서드를 호출할 때도 박싱된 기본 타입을 사용해야 한다.


정리

기본 타입과 박싱된 기본 타입 중 하나를 선택해야 한다면 기본 타입을 사용하자. 간단하고 빠르며, 앞서 말했던 언박싱으로 인한 버그, 성능 이슈를 예방할 수 있기 때문이다. 또한 기본 타입을 박싱하는 작업은 필요 없는 객체를 생성하는 부작용을 나을 수 있다. 박싱된 기본 타입을 꼭 사용해야한다면 위와 같은 문제 상황들을 이해하고 적절히 사용해야 한다.

 

반응형

+ Recent posts