top bar

글 목록

2017년 7월 28일 금요일

[Elasticsearch] 'Docker Compose' 로 ELK 스택 구성


 이번엔 Docker를 이용해 ELK 스택을 구성해 보겠다. Docker는 LXC 기술중 하나로서, 이식성이 뛰어난 격리된 프로세스 컨테이너를 통해 어플리케이션을 구동하는 방식이다. Docker 관련 문서들은 차고 넘치니, 따로 정리하지는 않겠다.

Docker Compose는 이러한 Docker 컨테이너 여러개를 조합(Compose)하여 설정 할 수 있게 해주고, 따라서 다수의 컨테이너 설정을 하나의 파일에서 가능하게 해 준다. 또한 설정된 컨테이너를 한번에, 또는 각각 하나씩 구동 할 수도 있다.

하지만 단 하나의 컨테이너를 구동할때도 Docker Compose가 유용하다.
가령, 아래의 tomcat 컨테이너를 띄우는 Docker 명령이 있다고 하자.

$ docker run -it --rm -p 8888:8080 tomcat:8.0

 위의 설정과 동일한 컨테이너를 Docker Compose로는 아래와 같이 설정하여 구동할 수 있다.

version: '2'
services:
  tomcat:
    image: tomcat:8.0
    ports:
      - 8888:8080

뭔가 텍스트가 더 많이 들어 간 것 같지만 기분 탓일 거다. 일단 깔끔하게 YAML 형식을 지원하는 Docker Compose 설정이 보기가(가독성이) 더 좋아 보인다. 위의 기본 docker 명령 예제에서 입력한 옵션은 많지 않지만, 훨씬 더 복잡한 옵션을 입력하고 컨테이너를 구동한다고 했을때 옵션을 입력하기도, 확인하기도 어려울 것이다. 따라서, 복잡한 하나 또는 여러개의 컨테이너 설정, 그리고 그 컨테이너들간의 관계를 설정 해야할 때는 당연히 뒤도 안보고 Docker Compose를 사용해야 한다.



docker-compose.yml 작성



ELK를 위한 docker-compose.yml 파일의 기본 골격은 아래와 같다.

version: '2'
services:
 elasticsearch:
  .
  .
  .

 logstash:
  .
  .
  .

 kibana:
  .
  .
  .

하나씩 살펴보자.

1) Elasticsearch
elasticsearch:
  image: docker.elastic.co/elasticsearch/elasticsearch:5.4.0
  container_name: elasticsearch
  volumes:
   - ./elasticsearch/config/elasticsearch.yml:/usr/share
/elasticsearch/config/elasticsearch.yml
   - ./elasticsearch/data:/usr/share/elasticsearch/data
  environment:
   ES_JAVA_OPTS: "-Xmx2048m -Xms2048m"
  ports:
   - 9200:9200
   - 9300:9300

'volumes' 설정을 통해서 외부의 파일시스템과 컨테이너 내부의 파일시스템을 마운트한다. [외부디렉토리]: [내부디렉토리] 와 같은 형식으로 마운트할 수 있는데, 위에서 보는것과 같이 파일 자체(elasticsearch.yml)도 마운트 가능하다.

'environment'로 elasticsearch 인스턴스의 JVM 옵션을 줄 수 있다. 'ports' 또한 컨테이너 외/내부의 포트를 설정하여 해당 포트를 통한 통신을 가능하게 해 준다. 별거 없다. ㅇㅇ

2) Logstash
 logstash:
  image: docker.elastic.co/logstash/logstash:5.4.0
  container_name: logstash
  command: logstash -f /usr/share/logstash/pipeline/logstash.conf
  volumes:
   - ./logstash/config/:/usr/share/logstash/config/
   - ./logstash/pipeline/:/usr/share/logstash/pipeline/
  environment:
   LS_JAVA_OPTS: "-Xmx2048m -Xms2048m"
  ports:
   - 10080:10080
  depends_on:
   - elasticsearch

elasticsearch 설정과 흡사하다. 단, 'command' 설정으로 컨테이너를 구동할때 'logstash -f ... ' 명령어로 logstash 를 실행하도록 한다. 물론 '-f' 옵션 다음의 오는 경로는 컨테이너 내부 파일 경로이다.

'depends_on' 은 'elasticsearch' 컨테이너가 시작한 후에 logstash 컨테이너를 구동하도록 하는 설정이다.

3) Kibana
 kibana:
  image: docker.elastic.co/kibana/kibana:5.4.0
  container_name: kibana
  volumes:
   - ./kibana/config/:/usr/share/kibana/config
  ports:
   - 5601:5601
  depends_on:
   - logstash

설명할 건 다 했으니 생략.



docker-compose 구동



'docker-compose.yml' 파일이 위치하고 있는 디렉토리에서,
아래와 같은 명령을 통해 구동 한다.

$ docker-compose up -d

로그를 보고 싶다면 아래 명령어를 사용한다.

$ docker-compose logs -f --tail="100" [service_name]

설정파일의 전체 내용은 아래에 있다.

https://gist.github.com/JuPyoHong/17362d9e0b64256b627dc063b00cc357


2017년 7월 24일 월요일

[Elasticsearch] node settings must not contain any index level settings


엘라스틱 서치 클러스터 환경을 구축하다가 만난 에러.
5.x 버전 이후부터 더이상 'elasticsearch.yml' 설정 파일 안에 샤드와 레플리카 갯수 설정을 할 수 없다. 아래와 같은 에러를 만나기 때문.

elasticsearch    | [2017-07-24T06:53:06,197][WARN ][o.e.c.s.SettingsModule   ] [xxxxx]
elasticsearch    | *********************************************************************
elasticsearch    | Found index level settings on node level configuration.
elasticsearch    |
elasticsearch    | Since elasticsearch 5.x index level settings can NOT be set on the nodes
elasticsearch    | configuration like the elasticsearch.yaml, in system properties or command line
elasticsearch    | arguments.In order to upgrade all indices the settings must be updated via the
elasticsearch    | /${index}/_settings API. Unless all settings are dynamic all indices must be closed
elasticsearch    | in order to apply the upgradeIndices created in the future should use index templates
elasticsearch    | to set default values.
elasticsearch    |
elasticsearch    | Please ensure all required values are updated on all indices by executing:
elasticsearch    |
elasticsearch    | curl -XPUT 'http://localhost:9200/_all/_settings?preserve_existing=true' -d '{
elasticsearch    |   "index.number_of_replicas" : "1",
elasticsearch    |   "index.number_of_shards" : "10"
elasticsearch    | }'
elasticsearch    | *********************************************************************

위에서 볼 수 있듯이 아주 친절하게 설명을 해준다.

결국 해당 설정을 하기 위해서는 '/{index}/_settings', '/_all/_settings' 등의 PUT API를 사용하거나, 'index template' 을 사용할 수 밖에 없다.

왜 이렇게 바뀌었는지는 다음에 알아보도록 하자...



2017년 7월 19일 수요일

[잡담] 기술 말하기

커뮤니케이션과 기술 글쓰기



몇년전에 친구녀석이 나에게 이런말을 한 적이 있다.

"IT 개발자는 거의 혼자 하는 일이 많지 않아?"

워딩은 위와 비슷했지만 그 뉘앙스는 마치, "개발자들은 사무실 구석에 박혀서 혼자 일에 몰두하는 히키고모리 아니냐"라는 느낌이어서 상당히 불쾌 했었던 기억이 있다. 그 말에는 별다른 반박을 하지 않고 넘어 갔었다. (한마디 해줄껄.. ㅋㅋ)

단언컨데, 개발자는 '커뮤니케이션 능력'이 가장 중요하게 여겨지는 직업들 중 하나다. 실무에서 다루는 코드베이스는 보통 엄청나게 거대한 데다가, 보통 작게는 십수명에서부터 수십명의 개발자가 하나의 형상관리 저장소를 바라보고 (물론 Git과 같은 분산 버전 관리 시스템이라면 각 로컬에 저장소를 clone 하겠지만, 결국 원격 저장소는 하나다.) 업무를 진행하게 된다. 때문에 조직내의 안정화 된 룰을 기반으로 한 실무자간의 효율적이고 명확한 커뮤니케이션이 없을 때 프로젝트가 엉망진창이 될 것은 안 봐도 비디오다.



* 브랜치 중심의 개발
git 기반의 개발 프로세스 에서는 수많은 개발자들이 브랜치를 만들고 삭제하고 
Merge하는 일이 수도 없이 일어 난다. 그것도 하나의 코드베이스를 가지고 말이다.


내가 직전에 근무했던 회사 대표님은, 코드의 품질을 아주 많이 강조하는 분이셨다. 그분은 그것과 동시에 '기술 글쓰기' 도 중히 여기셨는데, '기술 글쓰기' 사내 교육을 만들어 개발자들로 하여금 필수로 수강하게 할 정도였다. 이는 방금 위에서 언급한 "커뮤니케이션"과 맥락을 같이 한다.

요즘 중소기업에서부터 대기업, 각종 벤처회사에 이르기까지 회사 업무의 대부분이 전산화 되어있고, 주로 '이메일'을 주고 받으며 소통을 한다. 특히나 개발자들간의 이메일을 통한 커뮤니케이션은 "기술 글쓰기" 그 자체다. 따라서 이러한 글쓰기에 능숙하지 않으면 서로 각자의 메일 본문을 이해하기가 힘들게 되면서 메일 핑퐁이 늘어나게 되고, 업무 효율은 그만큼 떨어 질 수 밖에 없다.


기술 말하기



'기술 글쓰기'는 이만큼 중요하다.
하지만 나는 더 나아가 '기술 말하기'에 대해서 이야기 하고 싶다.

예를 들어, 'AOP'에 대해서 말로 설명한다고 해보자.
분명히 아래처럼 말하는 사람이 있을 것이다.

"메서드의 앞뒤로 무언가 작업을 하게 해주는 거 아니에요?"

당연히 틀린말은 아니지만 아주 많이 부족하고 '허접'한 설명이다. 만약 경력이 적지않은 개발자가 이런식으로 말을 한다면 실력을 의심해야 할 필요가 있다. 좀 더 정확한 설명은 아래와 같을 것이다.

"'횡단 관심사'를 모듈화 하여 여기저기 산만하게 퍼져있는 코드의 중복을 제거하고, 단일책임원칙(SRP)에 충실한 클래스를 설계하는데 도움을 준다. 흔히 AspectJ나 Spring AOP가 많이 쓰이며, Advice를 작성하고, Pointcut을 설정하여 적용하게 된다. 보통은 트랜잭션 이나, 로깅, 인증 처리에 사용된다."

물론 위의 문장도 완벽하게 AOP를 설명하고 있지는 않지만,
'전자' 보다는 확실히 명확하고 기술적인 말하기다.

종종 '기술 글쓰기' 보다 '기술 말하기'를 더 많이 해야 할 때가 있다. 메일로 글을 주고 받는 것보다 직접 찾아가 말로 설명하는것이 때로는 훨씬 빠르고 정확하기 때문이다. 하지만 '빠를' 수 는 있을지언정 '정확'하게 전달하기란 쉽지 않다. 이 때문에 '기술 말하기'의 중요성을 강조하고 싶은 것이다.

물론 어려운 일이다. 나 스스로도 항상 커뮤니케이션에 신경 쓰려고 노력하고, 대화 할 때 좀더 정확하게 전달 될 수 있는 단어와 용어를 선택하려고 애쓰지만 잘 안될 때가 많다.

그러니까 이 글은, 나 자신에게 바치는 글이 되겠다.
더 노력하고 더 내공을 쌓아야 한다.


2017년 7월 16일 일요일

[Javascript] 함수 호출 방법과, this 스코프의 설정

개요



오늘은 자바스크립트에서 함수를 호출하는 방식을 정리하려고 한다.

각각 함수호출 방법과 그 방법을 사용할때 'this' 객체는 서로 상이하기 때문에, 잘 알아두고 사용해야 유효범위(이하 '스코프')의 혼란을 예방할 수 있다.


함수는 '객체'다



놀랍게도 자바스크립트 에서의 함수는 '객체' 다. 정확히 말하면 값으로 취급 될 수 있는 '1급 객체' 다. 이 말 뜻은, 함수 자체에도 속성을 가질 수 있다는 것이며, 그 속성은 또 다른 함수가 될 수 있다는 말이다.

따라서 함수는 아래와 같이 '그냥' 호출 할 수도 있지만,

function func() {
    // do something
}

func();

아래처럼 함수 객체의 속성 메서드인 'call', 'apply', 'bind' 로도 호출 될 수 있다.

func.call(...);
func.apply(...);
func.bind(...);


함수 호출과 'this'



1) 기본

아래처럼 일반 함수 호출에서의 'this' 는 'window'(머리객체) 이다. ('nodejs'의 경우는 'global')

function func() {
    console.log(this); // window
}

하지만 'use strict' 를 사용하면 undefined 이다.

function func() {
    'use strict'
    console.log(this); // undefined
}


2) 객체 속성 으로서의 함수

하지만 객체에 속한 '메서드' 라면, 얘기가 조금 달라진다.
아래에서 'this' 는 객체 자신이다.

const someone = {
  name: 'Someone',
  sayHello: function(otherName) {
    console.log(this.name + ' says hello to ' + otherName);
  }
};

someone.sayHello('thomson'); 
// 결과 > 'Someone says hello to thomson'


하지만 위에서 언급한 call, apply 메서드를 사용하면, 첫번째 파라메터로 전달되는 객체를 해당 함수 스코프에서의 'this' 로 할당 할 수 있다. 아래와 같다

const alex = {
    name: 'Alex'
};

someone.sayHello.call(alex, 'thomson'); 
// 'Alex says hello to thomson'

someone.sayHello.apply(alex, ['thomson']); 
// 'Alex says hello to thomson'

첫번째 파라메터로 'alex' 객체를 넘겨주었다. 때문에 'sayHello' 메서드 내부의 'this'는 'alex' 객체이다.
따라서 위와 같은 결과가 출력된다

하지만 'bind' 메서드는, 위의 call이나 apply와는 조금 다르게 동작한다.

const bound = someone.sayHello.bind(alex, 'thomson'); 
bound();
// 'Alex says hello to thomson'

'bind' 메서드는 특정 함수의 'this'와 파라메터를 설정한 뒤, 해당 함수를 반환한다.
따라서 bound() 와 같은 방식으로 파라메터를 넘기지 않고 호출해도 call, apply와 같은 결과가
출력 되는 것이다.

또 한가지, bind 메서드는 아래와 같이도 동작한다.

function func(x, y) {
    return x + y;
}

const bound = func.bind(null, 10);
console.log(bound(20)); // 30

bind 메서드의 첫번째 파라메터로 특정 객체를 함수 내부의 this 로 설정할 수 있지만 위의 예제에선 필요 없으므로 'null' 을 넘겼다. 그리고 두번째 파라메터로 10을 넘겼는데, 이는 'func' 함수의 첫번째 파라메터인 'x' 값을 고정 시키겠다는 의미이다. 따라서 'bound(20)' 의 결과는 '30' 이 된다.


2017년 7월 11일 화요일

[Elasticsearch] Elastic 라이센스 등록

 엘라스틱 진영의 생태계는 시간이 지날 수록 고도화 되는 것 같다. 그에따라 다양한 도구들과 기능들이 제공되고 있는데, 아쉽게도(?) 대부분이 유료 라이센스를 필요로 하는 기능들이다. 하지만 기본적인 ELK Stack을 구축한다고 했을 때, 무료 라이센스인 'BASIC' 버전 만으로도 충분히 쓸만한 시스템을 구축 할 수 있다.

아래 웹 페이지로 접근하여, 라이센스별 기능 제공 여부를 확인 할 수 있다.














위 처럼 'BASIC' 버전의 'Free License' 를 선택하면 라이센스 등록 페이지로 이동하고, 이름과 이메일, 국가 등의 간단한 정보만 입력하면 '라이센스 json 파일'을 입력한 이메일로 받을 수 있다.

License Update 방법



간단하다. 라이센스 파일은 위에서 말한 것 처럼 'json' 파일로 다운로드가 되는데, 이 파일을 xpack의 rest api 로 밀어 넣어 주면 된다.

$ curl -XPUT -u elastic:changeme 
'http://10.213.128.227:9200/_xpack/license?pretty&acknowledge=true' 
-H "Content-Type: application/json" -d @[license file name].json

여기서 주의할 점은, elasticsearch 클러스터 서버에 'X-Pack' 플러그인이 설치 되어 있어야 한다는 것이다. 만약 설치가 안되어 있다면, '/_xpack' 으로 시작하는 api 자체를 사용할 수 없다.


License 확인



아래의 rest api로 등록된 라이센스 정보를 알 수 있다

$ curl http://10.213.128.190:9200/_license

결과는 아래와 같다.

{
  "license": {
    "status": "active",
    "uid": "blahblah",
    "type": "basic",
    "issue_date": "2017-06-08T00:00:00.000Z",
    "issue_date_in_millis": 1496880000000,
    "expiry_date": "2018-06-08T23:59:59.999Z",
    "expiry_date_in_millis": 1528502399999,
    "max_nodes": 100,
    "issued_to": "Hong Ju Pyo (Coupang)",
    "issuer": "Web Form",
    "start_date_in_millis": 1496880000000
  }
}

basic 라이센스의 유효기간은 '1년'이며, 1년이 지났다면 역시 무상으로 라이센스를 갱신할 수 있다. 즉, 평생 무료라는 얘기..

2017년 7월 9일 일요일

[Spring] POJO의 이해

개요



'POJO'란, 'Plain Old Java Object'의 약자이다. 이 용어의 유래를 잘 찾아보면 굉장히 허무할 것이다. 이유는 아래와 같다.

2000년 즈음에, 마틴파울러는 '평범한' JAVA 오브젝트에 비즈니스 로직을 넣어 사용하는 것이 기존에 널리 사용되던 EJB 빈보다 뛰어난 점에 주목하고 있었는데, 이러한 장점에 비해 폼나는 용어가 없어서 만들었다는 것이 바로 'POJO' 라는 것이다. 링크 참고

아래의 spring.io 웹사이트에, 스프링 관점에서 POJO의 이해를 돕는 글이 있다.

https://spring.io/understanding/POJO#understanding-pojos

위의 글 또한, POJO를 한 문장으로 잘 설명하고 있다.






여기서 'be bogged down' 이라는 문구가 낯설었는데, 이 숙어의 뜻을 찾아보니 '너무 복잡하거나 어려워서 다른것을 할 수 없는' 이라는 뜻이었다. 따라서 위 문장은 "프레임워크에 의해 확장된 즉, 그것으로 인해 'bogged down' 되지 '않은' 자바 오브젝트" 정도의 뜻이 되지 않을까 싶다. 뭐 이러한 뜻풀이는 난해 할 수도 있으니, 그냥 '평범한 자바 오브젝트' 라고 이해해야 겠다.


예시를 통한 이해



가령, 'JMS'를 통해 메시지 수신의 기능을 하는 클래스를 설계한다고 해보자. 아래와 같을 것이다.

public class ExampleListener implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            try {
                System.out.println(((TextMessage) message).getText());
            }
            catch (JMSException ex) {
                throw new RuntimeException(ex);
            }
        }
        else {
            throw new IllegalArgumentException("Message type error");
        }
    }
}

위 클래스는 특정 메시징 솔루션(JMS)의 인터페이스를 구현(implements) 하고 있다. 다시 말하면 해당 인터페이스에 '종속'되어 있는 것이다. 따라서 다른 대안적인 메시징 솔루션을 적용하려고 한다면 매우 큰 변경 비용이 들 것은 자명하다. (당연히, 거대한 코드 베이스를 가지고 있는 대규모 프로젝트의 경우를 말한다)

이것을 'POJO'의 관점에서 접근한다면, 메시지를 처리하는 클래스가 특정 솔루션에 종속되지 않은, (원문에서는 'solution free'라고 표현한다) 상태로 설계 되어야 한다. 아래와 같다.

@Component
public class ExampleListenerPojo {

    @JmsListener(destination = "myDestination")
    public void processOrder(String message) {
        System.out.println(message);
    }
}

이것이 스프링이 추구하는 'POJO' 클래스의 모습이다. 'JMS'와 관련된 확장 코드가 보이지 않으며, 따라서 'JMS'와 커넥션을 연결하고 메시지를 처리하는 책임은 모두 어노테이션에 위임 된 것을 볼 수 있다.

그리고 만약 'JMS'에서 'RabbitMQ'로 메시징 솔루션을 변경한다고 했을때, 개발자는 '@JmsListener' 어노테이션을 '@RabbitListener' 어노테이션으로 교체 하기만 하면 된다. 또한 이러한 설계는 라이브러리에 종속적이지 않아 단위 테스트를 용이하게 해 준다. 여기서 스프링 'POJO'의 강력함이 드러난다고 볼 수 있다.

이렇듯 'POJO'를 통해, 스프링은 개발자의 코드와 외부 라이브러리의 'Coupling' 을 최소화 시켜준다. 이것은 다시말하면 개발자가 작성한 서비스 코드는 그 서비스 자체로 주체가 되는 것이 아니라 전체 어플리케이션의 한 part로서 동작하고, 전체 어플리케이션에 '연결'(wiring) 되게 함으로서, 좀더 확장성 있는 소프트웨어 설계를 가능하게 해준다는 것이다. 또한 이것은 'Inversion Of Control(IOC)'과 'Dependency Injection(의존성 주입)' 의 메인 컨셉이기도 하다.

하지만 단순히 클래스 설계시 'implements'와 'extends' 키워드를 사용하지 않는다고 해서 POJO 인가? 라고 했을때는 조금은 의아하다. 스프링을 사용한 프로젝트는 이미 어노테이션으로 떡칠(?)되어 있을 것이고, 이 어노테이션 자체도 특정 라이브러리에 종속되어 있기 때문이다.

'스프링은 완전한 POJO를 제공한다'에 대해선 반대의 입장이지만,
흠.. 내공이 부족해서 일까. POJO의 관해선 조금 더 연구가 필요 할 것 같다.



2017년 7월 6일 목요일

[MySQL] 주요 스토리지 엔진(Storage Engine) 간단 비교

MySQL은 크게 아래의 2가지 구조로 되어 있다.


  • 서버 엔진 : 클라이언트(또는 사용자)가 Query를 요청했을때, Query Parsing과, 스토리지 엔진에 데이터를 요청하는 작업을 수행.

  • 스토리지 엔진 : 물리적 저장장치에서 데이터를 읽어오는 역할을 담당.


여기서 중점적으로 봐야할 것은 '스토리지 엔진' 인데, 그 이유는 데이터를 직접적으로 다루는 역할을 하므로 엔진 종류 마다 동작원리가 다르고, 따라서 트랜잭션, 성능과 같은 주요 이슈에도 밀접하게 연관되어 있기 때문이다.

MySQL의 스토리지 엔진은 'Plug in' 방식이며, 기본적으로 8가지의 스토리지 엔진이 탑재 되어 있다. 아래의 명령으로도 탑재된 스토리지 엔진을 확인 할 수 있다.

mysql> SHOW ENGINES;

위에서 말한 것과 같이 Plug 'in' 방식 이기때문에 스토리지 엔진 교체 작업이 비교적 간단하다. 또한 기본 탑재 플러그인 외에 서드파티에서 제공하는 다양한 스토리지 엔진을 손쉽게 적용할 수 있다는 것이 장점이다.

각 플러그인의 '.so'(Shared Object) 파일을 얻었다면 아래와 같이 설치/삭제 할 수 있다.

INSTALL PLUGIN ha_example SONAME 'ha_example.so';

UNINSTALL PLUGIN ha_example;

그리고 'CREATE TABLE' 문을 사용하여 테이블을 생성할 때, 맨 마지막 구문에 스토리지 엔진의 이름을 추가함으로 아주 간단하게 설정 할 수 있다. 아래와 같다.

-- ENGINE=INNODB not needed unless you have set a different
-- default storage engine.
CREATE TABLE t1 (i INT) ENGINE = INNODB;
-- Simple table definitions can be switched from one to another.
CREATE TABLE t2 (i INT) ENGINE = CSV;
CREATE TABLE t3 (i INT) ENGINE = MEMORY;

자세한것은 공식 문서 참고

이 포스팅에서는 가장 많이 쓰인다고 알려진 스토리지 엔진인 MyISAM, InnoDB, Archive
의 3가지 스토리지 엔진을 비교/정리 하려고 한다.


  • InnoDB : 따로 스토리지 엔진을 명시하지 않으면 default 로 설정되는 스토리지 엔진이다. InnoDB는 transaction-safe 하며, 커밋과 롤백, 그리고 데이터 복구 기능을 제공하므로 데이터를 효과적으로 보호 할 수 있다.

    InnoDB는 기본적으로 row-level locking 제공하며, 또한 데이터를 clustered index에 저장하여 PK 기반의 query의 I/O 비용을 줄인다. 또한 FK 제약을 제공하여 데이터 무결성을 보장한다.

  • MyISAM : 트랜잭션을 지원하지 않고 table-level locking을 제공한다. 따라서 multi-thread 환경에서 성능이 저하 될 수 있다. 특정 세션이 테이블을 변경하는 동안 테이블 단위로 lock이 잡히기 때문이다.

    텍스트 전문 검색(Fulltext Searching)과 지리정보 처리 기능도 지원되는데, 이를 사용할 시에는 파티셔닝을 사용할 수 없다는 단점이 있다.

  • Archive : '로그 수집'에 적합한 엔진이다. 데이터가 메모리상에서 압축되고 압축된 상태로 디스크에 저장이 되기 때문에 row-level locking이 가능하다.

    다만, 한번 INSERT된 데이터는 UPDATE, DELETE를 사용할 수 없으며 인덱스를 지원하지 않는다. 따라서 거의 가공하지 않을 원시 로그 데이터를 관리하는데에 효율적일 수 있고, 테이블 파티셔닝도 지원한다. 다만 트랜잭션은 지원하지 않는다.


아래는 상세 도표다. (수시로 참고 해야겠당..)

InnoDB Storage Engine

Storage limits64TBTransactionsYesLocking granularityRow
MVCCYesGeospatial data type supportYesGeospatial indexing support Yes
B-tree indexesYesT-tree indexesNoHash indexesNo
Full-text search indexesYesClustered indexesYesData cachesYes
Index cachesYesCompressed dataYesEncrypted dataYes
Cluster database supportNoReplication supportYesForeign key supportYes
Backup / point-in-time recoveryYesQuery cache supportYesUpdate statistics for data dictionaryYes

MyISAM Storage Engine

Storage limits256TBTransactionsNoLocking granularityTable
MVCCNoGeospatial data type supportYesGeospatial indexing supportYes
B-tree indexesYesT-tree indexesNoHash indexesNo
Full-text search indexesYesClustered indexesNoData cachesNo
Index cachesYesCompressed dataYesEncrypted dataYes
Cluster database supportNoReplication supportYesForeign key supportNo
Backup / point-in-time recoveryYesQuery cache supportYesUpdate statistics for data dictionaryYes

Archive Storage Engine

Storage limitsNoneTransactionsNoLocking granularityRow
MVCCNoGeospatial data type supportYesGeospatial indexing supportNo
B-tree indexesNoT-tree indexesNoHash indexesNo
Full-text search indexesNoClustered indexesNoData cachesNo
Index cachesNoCompressed dataYesEncrypted dataYes
Cluster database supportNoReplication supportYesForeign key supportNo
Backup / point-in-time recoveryYesQuery cache supportYesUpdate statistics for data dictionaryYes



2017년 7월 5일 수요일

[Javascript] 생성자의 이해

시작에 앞서 잡설..



 과거 웹개발을 공부할때나, 아니면 커리어 초기에 개발업무를 진행 할때 '자바스크립트' 라는 언어는 그저 'onclick' 따위의 이벤트를 핸들링 하는 것과 Ajax를 이용한 비동기 요청을 구현하는 용도로 밖에 사용하지 않았다. 좀 더 활용했다라고 한다면 'JQuery UI' 를 이용해 캘린더 붙이는 정도...?

 하지만 새로운 회사로 이직하여 시작한 프로젝트는 npm 환경을 기반으로 하는 그야말로 트렌디한 온갖 자바스크립트 도구, 라이브러리, 프레임워크의 향연이었다. 또한 ECMAScript6 이후부터의 문법적 발전을 통해서, 객체의 해체할당, let과 const의 block scope, class 선언 방식 등장, 달라진 모듈 import와 export 문법 등등... 여러가지 새로운 개념을 익혀야 했다.

이러한 변화에 흐름에 당당히 맞서서(!?) 나는 요즈음 나비책, 부엉이책, 이름을알수없는새책 등을 탐독하고 있다 (코뿔소책은 그 두께에 엄두가 안나서 pass.. 그래도 필요한 부분은 틈틈히 보고 있다..)
모두 적어도 몇년전에 출판된, 비교적 과거의 문법과 이론을 담고 있는 책들이다.

그렇다고 변화에 흐름을 미련하게 역행 하겠다는 건 절대 아니다. 다만 우리가 역사를 연구 함으로 미래에 대한 통찰력을 얻는 것처럼, 자바스크립트의 과거 이론을 통해서 현재의 변화를 더 잘 이해하고 싶은 것 뿐이다.

생성자에 관해 정리하려다가 잡설이 길어졌다. 정리에 앞서, 자바스크립트 생성자에 대한 더 깊은 이해를 할 수 있도록 도와주신 실력자 'javarouka'님께 감사 드린다.


생성자 문법 정리



자바스크립트의 생성자는 다름아닌 '함수' 다. 자바의 경우 생성자는 일반 메소드와 시그니쳐가 비슷하지만, 자바의 생성자는 메소드처럼 호출 할 수 없다. 반드시 'new' 키워드와 함께 사용해야 한다.

하지만 자바스크립트의 생성자 함수는 'new'와 함께 사용할 수도, 그냥 일반적인 함수처럼 사용할 수도 있다. 아래의 예제 처럼 말이다.

function MyConstructor(prop) {
    this.prop = prop;
}

var obj1 = MyConstructor("prop value");
var obj2 = new MyConstructor("prop value");

하지만 obj1과 obj2 할당된 값은 아래와 같은 차이가 있다.

console.log(obj1) // undefined
console.log(obj2) // MyConstructor
console.log(obj2.prop) // "prop value"

obj1은 값이 정의되지 않은 'undefined' 값이고, obj2는 정상적으로 MyConstructor의 객체와 prop 속성값인 "prop value" 문자열이 할당 된 것을 볼 수 있다.

'new' 키워드와 함께 사용된 생성자 함수는 묵시적으로 객체를 생성하여 'this' 에 바인딩한다. 그리고 동적으로 속성을 정의한 뒤에 역시나 묵시적으로 'this' 를 반환한다. 따라서 obj2에는 정상적인 객체가 생성되어 메모리에 할당 되는 것이다.

하지만 생성자를 'new' 키워드와 사용하지 않으면 일반 함수 처럼 호출이 되고, 이때, 'this'는 브라우져의 자바스크립트 실행 환경에서 '머리객체(Head Object)'인 'window' 이다. 따라서  MyConstructor 생성자를 일반 함수로서 호출 하게 되면 머리객체인 'window'에 동적으로 'prop'속성이 생성되며, 아무것도 반환하지 않으므로 obj1는 'undefined'가 된다.

'window' 객체에 'prop' 속성이 생성되었는지 확인해보자.




.
.
.









 'window' 객체의 속성을 나열했더니 추가된 prop 이 보인다

이러한 생성자 함수를 'new'키워드와 사용하지 않을때 주의 사항은, 바로 위의 예제에서 'window' 머리객체에 'prop'이 추가 된 것처럼 절대 전역 유효범위를(global scope)를 어지럽혀선 안된다는 것이다. 이유는 자명하다. 전역 머리객체에 개발자가 의도하지 않은 속성이 추가 될 수 있고, 이는 찾아내기 어려운 버그 발생으로 이어지기 때문이다.


한편, 생성자 함수를 'new' 키워드와 함께 사용한다고 무조건 해당 생성자의 객체를 반환할까?
그것도 아닌것 같다.

function ReturnByObj() {
    this.key = "hello";
    return new Object();
}

function ReturnByString() {
    this.key = "hello";
    return "ret value";
}

const returnByObj = new ReturnByObj();
const returnByString = new ReturnByString();

console.log(returnByObj); // Object 객체
console.log(returnByObj.key); // undefined
console.log(returnByString); // ReturnByString 객체
console.log(returnByString.key); // "hello"

생성자 함수(처럼 보이는) ReturnByObj와 ReturnByString 에 'return' 문을 추가했다. 이 두 함수를 'new' 키워드와 함께 사용한 코드이다.

위의 console 문 주석을 보면 알 수 있듯이 'new' 키워드와 함께 사용해도, return 문의 값이 '객체' 라면,
해당 객체가 반환된다. 'ReturnByObj' 객체가 반환 되지 않는다는 말이다. 하지만 'ReturnByString' 생성자 함수 처럼 원시값(primitive type)을 리턴한다면, 우리가 기대했던대로 해당 생성자내부에서 생성된 'this' 객체('ReturnByString' 객체)가 반환된다.

중요한 사실은, 이러한 문법을 이해해야 생성자 함수를 일반 함수처럼 호출하거나, 일반 함수를 'new' 키워드와 같이 호출하는 등의 오용과 실수를 줄일 수 있다는 사실이다.



2017년 7월 3일 월요일

[Javascript] Closure의 변수 공유 문제

 무언가 난해한 개념으로 이해되는 'lexical scope'(어휘적 유효범위)는, 그냥 '유효범위' 라고 생각해도 상관이 없을 듯 싶다. (이하 유효범위라 하겠다) 자바스크립트는 함수를 실행할때, 해당 함수가 정의되었을 때의 유효범위를 사용하여 실행 한다. 아래의 코드를 보자.

function funcCreator() {
  const x = 10;
  function lexicalScopeFunc() {
    console.log(x);
  };
  return lexicalScopeFunc;
}

funcCreator()();

위의 코드는 'funcCreator' 함수를 실행하여 'lexicalScopeFunc' 가 반환되게 하고, 반환된 함수를 즉각 호출한다.  해당 함수를 호출 하면 console 창에 참조 'x' 의 값인 '10'이 출력된다. 

정작 'lexicalScopeFunc' 함수'만' 본다면, 참조 'x'는 해당 함수의 scope 에서 정의 되었다. 그런데 어떻게 console창에 참조 'x'의 값이 정상적으로 출력이 되는 것일까? 바로 해당 lexicalScopeFunc 함수가 실행될때 해당 함수가 정의 되었던 유효범위, 즉 'funcCreator' 함수의 scope 를 이용하여 실행하기 때문이다. 다시말해서, lexicalScopeFunc 함수는 자신이 선언된 funcCreator 함수 내부의 유효범위와 연결된 채 존재하는 함수인 것이다.

바로 이러한 개념을 '클로져(Closure)' 라고 한다.

이러한 클로져 개념에는 아주 고전적으로 논의되는 문제가 있는데, 바로 '변수 공유' 문제이다. 아래의 코드를 보면 문제가 무엇인지 명확해 진다.

var closureList = [];
for(var i = 0; i < 10; i++) {
  closureList.push(() => {
    console.log(i);
  });
}

for(var idx in closureList) {
    closureList[idx]();
}

closureList 배열안에 삽입한 요소는 각각 클로져 함수인데, 자신이 선언된 유효범위의 'i' 값을 출력한다. 때문에 위의 코드를 실행 했을때 우리는 console창에 0부터 9까지 출력되는 것을 기대할 것이다.

그러나 정작 코드를 실행해 보면 '10' 이 10번 출력된다. 이유를 살펴보자면, closureList 안에 삽입된 각각의 클로져 함수가 해당 유효범위(여기서는 전역범위가 될것이다)의 'i' 변수를 공유하기 때문이다. for문이 종료될때 변수 'i' 는 값이 10이 되고, 각각 클로져는 최종 값이 10인 변수 'i'를 출력하기때문에 그러한 결과가 나오는 것이다.

이를 해결하려면 어떻게 해야할까?

우선은 IIFE(Immediately Invoked Function Expressions)를 사용하여 유효범위를 강제한다. 
아래와 같은 코드이다.

var closureList = [];
for(var i = 0; i < 10; i++) {
  closureList.push(
    (function() {
      var localVar = i;
      return function() {
        console.log(localVar);
      };
    })()
  );
}

for(var i = 0; i < 10; i++) {
    closureList[i]();
}

closureList에 함수 객체를 삽입할때, IIFE를 통해 독립적인 유효범위를 생성하여 그안에서 함수를 반환하여 배열에 삽입한다. 이렇게 되면 IIFE의 유효범위 안에서 'i'는 'localVar' 라는 변수로 바인딩되고, 독립적인 유효범위 안의 변수가 된다.

그러나... 위 코드가 단순한 예제이기에 망정이지, 조금이라도 복잡한 비즈니스 코드로 작성한다면 IIFE는 읽기에 무척 짜증이 날 것 같다.

아주 단순한 해결 방법이 있는데, ECMAScript6 에서 제공하는 'let' 을 이용하여 변수를 선언하는 것이다.
아래의 코드를 다시 보자.

var closureList = [];
for(let i = 0; i < 10; i++) {
  closureList.push(function() {
    console.log(i);
  });
}

for(var idx in closureList) {
    closureList[idx]();
}

차이가 보이는가? closureList 배열에 함수를 삽입하는 for 구문안에서 인덱스를 'let'으로 선언했다. 다들 알겠지만 'var' 선언 방식의 scope는 함수의 유효범위를 사용한다. 하지만 'let'은 대부분의 프로그래밍 언어가 그렇듯이 '블록'의 유효범위이다. 때문에 반복되는 for 구문의 블록이 해당 let 변수의 독립적인 유효범위로 작용하기 때문에 우리가 원하는 0, 1, 2, .. 9 가 출력이 되는 것이다.