2018의 게시물 표시

(스프링) Ioc컨테이너와 DI

이미지
<IOC 컨테이너를 통해 애플리케이션이 만들어지는 방식> 결국 스프링 애플리케이션이란, POJO클래스와 설정 메타정보를 이용해 IOC 컨테이너가 만들어주는 오브젝트의 조합이다. RootBeanDefinition을 보면 bean클래스가 private volatile Object beanClass; 로 되어있는 것을 볼 수 있다. 이는 volatile을 선언하게 되면 cpu cache에 해당 값을 저장하는 과정 없이 오직  MainMemory에서 값을 읽고, 쓰는 과정을 거친다. 즉, 멀티쓰레드에서 한개의 쓰레드가 read&write를 하더라도 다른 쓰레드에서 읽기를 하는 경우 값이 변하더라도 항상 일관된 값을 유지하게 해준다.

(Effective Java) 익명 클래스보다는 람다를 사용하라_람다와 스트림

이미지
람다를 왜 JDK 1.1에서는 함수객체를 만드는 주요 수단은 익명 클래스였다. 그러나 익명클래스 방식은 코드가 너무 길어 자바는 함수형 프로그래밍에 적합하지 않았다. 결국 이러한 문제로 이를 람다로 교체됨 (더욱 간결하게 표현할 수 있음) 람다는 이름이 없고 문서화도 못 한다. 따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 쓰지 말아야 한다. 람다는 한 줄일때 가장 좋고 길어야 세 줄안에 끝내는게 가장 좋다. 세 줄을 넘어가면 가독성이 심하게 나빠진다. 람다의 시대가 열리며 익명클래스 -> 람다로 대체 되었지만, 람다는 함수형 인터페이스에서만 쓰인다. 예컨대 추상클래스의 인스턴스를 만들 때 람다를 쓸 수 없으니, 익명 클래스를 써여한다. 마지막으로 람다는 자신을 참조할 수 없다. this키워드는 바깥 인스턴스를 가르킨다. 람다를 직렬화하는 일은 극히 삼가야한다. 정리: 함수객체를 구현하기 위해 익명클래스를 도입, 그러나 코드가 길어지는 단점이 존재하여 이를 람다로 대체하기 시작함 그러나 람다를 사용하지 못하는 경우가 있는데 1. 자기 자신을 참조하는 this를 사용하는 경우 2. 코드길이가 너무 길어지는 경우 오히려 문서화를 할 수 없음 3. 직렬화를 하는 경우는 삼가야 한다.

(Docker) Elastic, logstash, kibana(Docker-compose)

version: '2.2' services:   elasticsearch:     image: docker.elastic.co/elasticsearch/elasticsearch:6.2.1     container_name: elasticsearch     hostname: elasticsearch     environment:       - cluster.name=docker-cluster       - bootstrap.memory_lock=true       - "ES_JAVA_OPTS=-Xms512m -Xmx512m"     ulimits:       memlock:         soft: -1         hard: -1     volumes:       - esdata1:/usr/share/elasticsearch/data     ports:       - 9200:9200     logstash:     image: docker.elastic.co/logstash/logstash:6.2.1     container_name: logstash     hostname: logstash     environment:       - "LS_JAVA_OPTS=-Xms512m -Xmx512m"     ports:       - 9600:9600     volumes: ...

(Kubernetes) StatefulSet(스테이트 풀 셋)

리플리카컨트롤러, 리플리케이션셋, 디플로이먼트는 모두 상태가 없는(stateless) 포드들을 관리하는 용도 였습니다. 스테이트풀셋(StatefulSets)은 단어의 의미 그대로 상태를 가지고 있는 포드들을 관리하는 컨트롤러 입니다. 스테이트풀셋을 사용하면 볼륨을 사용해서 특정 데이터를 기록해두고 그걸 포드가 재시작했을때도 유지할 수 있습니다. 여러개의 포드를 띄울때 포드 사이에 순서를 지정해서 지정된 순서대로 포드가  실행되게 할수도 있습니다. 이런식으로 어떠한 상태를 가지고 있어야 할때 사용하는게 스테이트풀 셋입니다.

(Kubernetes) Deployment vs Pod

둘의 차이는 Pod는 단순히 Pod만 생성하는 것이지만 Deployment는 Pod를 생성할 때, 실패하는 경우를 감시하고 만약 특정한 숫자 이상의 포드를 생성하라고 명령한 경우는 그 숫자가 될 때까지 지속적으로 pod를 생성하게한다.  If you don’t want a Deployment to monitor your pod (e.g. your pod is writing non-persistent data which won’t survive a restart, or your pod is intended to be very short-lived), you can create a pod directly with the create command.

(AWS) ElasticBeansTalk 로 도커 컨테이너 배포하기

1. Single Container인 경우 Dockerfile 을 생성하여 이미지를 사용자 지정하고 Docker 컨테이너를 Elastic Beanstalk에 배포합니다. Dockerrun.aws.json  파일을 생성하여 기존 Docker 이미지에서 Elastic Beanstalk으로 Docker 컨테이너를 배포합니다. 애플리케이션 파일, 모든 애플리케이션 파일 종속성,  Dockerfile  및  Dockerrun.aws.json  파일이 포함된  .zip 파일을 만듭니다.  Dockerfile  또는  Dockerrun.aws.json  파일만 사용하여 애플리케이션을 배포하는 경우 파일을 .zip 파일로 압축할 필요가 없습니다. 2. MultiContainer인 경우 docker-compose.yml 을 Dockerrun.aws.json 으로 변경한다. 이를 쉽게해주는 pip 라이브러리가 존재한다. (https://container-transform.readthedocs.io/en/latest/) ElasticbeansTalk 생성시 멀티 컨테이너로 옵션을 변경하여 생성한다. 그후 생성한 Dockerrun.aws.json파일을 업로드 한다. 업로드가 되더라도, 로그를 보게되면 에러로그가 있을 수 있다. 이는 외부에서 도커 컨테이너로 접속시 아이피 주소를 인식하지 못하는 경우이다. 이를 위해 다음과 같이 변경한다. localhost:9092 -> 생성한 (url):(port번호) (외부 접속시) 참고 사이트 : https://rmoff.net/2018/08/02/kafka-listeners-explained

(Docker) Docker compose 에서 ports와 expose의 차이

둘다 포트를 명시하지만 차이점이 존재한다. 만약 호스트(즉, 컨테이너 밖인 로컬)에서 해당 컨테이너로 접속하기 위해서는 ports에 해당값을 명시한다. Expose는 컨테이너 간의 포트를 바라볼때 사용한다.

(kafka) 장애 복구 정리

이미지
1. WAS -> Kafka (연결불가능) 이 경우 Producer(소셜엔진) 에서 카프카로 데이터 전송시 exception이 발생 이 exception을 소셜엔진엔서 callback을 이용해 실패로그로 저장하던지 MongoDB에 저장하여 실패 데이터를 기록 이와 같은 케이스는 다음과 같은 방법으로 실패를 처리할 수 있다 1) retries 옵션에 적당한 값을 설정해 실패 시 재시도 (엄청난 지연 발생가능) 2) 전송 실패시 발생하는 Exception을 catch에 큐에저장 후 다시 재전송 실패가 지속되면 카프카 문제로 판단해 실패로그를 저장 혹은 MongoDB 에 해당 데이터 저장 참고 : http://readme.skplanet.com/?p=13042 Scenario 1 - Fire-and-forget with a failed node and partition leader fail-over (acks=0) 이 케이스는 프로듀서에서 acknowledgements를 받지 않는다. 즉, 전송만 계속하는 것으로 만약 현재 카프카 호스트의 Leader 가 다운된다면 가정) 약 10만개 메시지 전송 중, 3만개 메시지 전송 중, 카프카 leader가 down되었을 때 새로운 leader를 선출하게된다. 이 텀동안 데이터 유실이 발생 약 6700여개 메시지 전송 실패 Scenario 2 - Acks=1 with a failed node and partition leader fail-over ack0=1인 경우, Leader노드가 fail된 경우. 이 케이스 또한 데이터 유실이 발생한다. 그러나 acks=0 인 경우보다 데이터 유실 발생이 더 적다. 테스트) 10만 메시지를 한개의 Producer에서 전송했을 때, 3만번쨰에서 Leader가 죽고, 새로운 Leader 가 선출되었을때, 약 5개의 데이터 손실 발생, 약 9만개 메시지는 Producer 에 응답함 ...

(Kafka) 카프카 스트림즈 아키텍처

이미지
카프카 스트림즈에 들어오는 데이터는 카프카 토픽 메시지이다. 스트림과 카프카 토픽의 관계는 다음과 같다. 각 스트림 파티션은 카프카의 토픽 파티션에 저장된 정렬된 메시지이다. 스트림 데이터 레코드는 카프카 해당 토픽의 메시지(키+값)이다. 데이터 레코드의 키를 통해 다음 스트림(=카프카 토픽) 으로 전달된다. 카프카 스트림즈는 입력 스트림의 파티션 개수만큼 태스크를 생성한다. 각 태스크에는 입력 스트림(즉 카프카 토픽) 파티션들이 할당되고, 이것은 한번 정해지면 입력 토픽의 파티션에 변화가 생기지 않는 한 변하지 않는다. 각 입력스트림으로 부터 할당받은 태스크 파티션은  변화하지 않는다. 머신이 중지되더라도, 해당 태스크가 재시작해야한다.  카프카 스트림즈는 사용자가 스레드의 개수를 지정할 수 있게 해주며, 1개의 스레드는 1개이상의 태스크를 처리할 수 있습니다. 다음 그림은 1개의 스레드에서 2개의 스트림 태스크가 수행되는 모습을 보여준다.

(Kafka) 카프카 스트림즈란?

이미지
카프카 스트림즈는 카프카에 저장된 데이터를 처리하고 분석하기 위해 개발된 클라이언트 라이브러리이다. 카프카 스트림즈는 이벤트 시간 과 처리 시간 을 분리해서 다루고 다양한 시간 간격 옵션을 지원하기에 실시간 분석을 간단하지만 효율적으로 진행할 수 있습니다. 카프카 스트림즈는 스파크 스트림이나 스톰과 같이 스트림 처리를 하는 프로세서들이 서로 연결되어 형상, 즉, 토폴로지를 만들어서 처리하는 API 이다.  스트림 : 끈임없이 전달되는 데이터 세트를 의미한다. 스트림에 기록하는 단위는 키-값 형태이다.  스트림 처리 애플리케이션 : 카프카 스트림 클라이언트를 사용하는 애플리케이션으로, 하나 이상의 프로세서 토폴로지에서 처리되는 로직을 의미한다. 프로세서 토폴로지는 스트림 프로세서가 서로 연결된 그래프를 의미한다. 스트림 프로세서 : 프로세서 토폴로지를 이루는 하나의 노드를 말하며, 노드들은 프로세서 형상에 의해 연결된 하나의 입력 스트림으로부터 데이터를 받아서 변환한 다음 다시 연결된 프로세서에 보내는 역할을 한다.

(엘라스틱 서치) FORBIDDEN/12/index read-only / allow delete 이슈

이미지
이유는 디스크 용량이 5%이하인 경우 자동으로 false처리하게 된다. 그러므로 두가지 방법이 있는데 첫 번째, elasticsearch.yml파일 안에 cluster.routing.allocation.disk.threshold_enabled: false 처리한다. 해당 인덱스 정보를 확인한 결과 GET activitylog-index/_settings read_only_allow_delete : true인 것을 볼 수 있다. 이를 해결하려면, PUT activitylog-index/_settings {   "index": {     "blocks" : {       "read_only_allow_delete": "false"     }   } }

(kafka) configued broker.id 1 doesn`t match store broker.id 0 이슈 문제 해결

한 호스트에서 여러 브로커를 생성하여 테스트 할 시 위와같은 에러가 발생할 수 있는데 이는 server-0.properties server-1.properties server-2.properties  각 파일에서 카프카가 저장시킬 로그 디렉토리를 분리해야한다. log.dirs = /tmp/kafka-logs-1 log.dirs = /tmp/kafka-logs-2 log.dirs = /tmp/kafka-logs-3

(zookeeper) systemd 등록 및 설명

리눅스 서버를 운영하거나 애플리케이션 서비스를 운영하는 경우에는 여러 종류의 프로세스들을 잘 관리하는 것도 매우 중요하다. 예를들어, 예기치 않게 서버의 오작동으로 인해 리부팅된 경우 어떤 프로세스는 반드시 자동으로 시작돼야 하는 경우도 있고, 반대로 어떤 프로세스는 반드시 관리자가 수동으로 시작해줘야 하는 경우가 있습니다. 이렇게 다양한 상황에 따라 효율적으로 프로세스들을 관리하기 위한 방법으로 여러 프로세스들을 systemd에 등록해 운영할 수 있다. ========================================================== 주키퍼에서는 zookeeper-server.service라는 파일명으로 만든다. <zookeeper-server.service> [Unit] Description=zookeeper-server After=network.target [Service] Type=forking User=root Group=root SyslogIdentifier=zookeeper-server WorkingDirectory=/usr/local/zookeeper Restart=always RestartSec=0s ExecStart=/usr/local/zookeeper/bin/zkServer.sh start ExecStop=/usr/local/zookeeper/bin/zkServer.sh stop ========================================================== 1. [Unit] 일반적인 옵션을 나타낸다. 가장 많이 사용하는 옵션은 실행 순서를조정하는 After, Before이다. - Description : 해당 유닛에 대한 상세한 설명을 나타내며 이 내용은 systemctl status 명령어에 표시된다. - After : 유닛이 시작되는 순서를 조정하며 After에 저장된 유닛이 실행된 이후 시작된다. - Before : 유닛이 시작되는 ...

(안드로이드) Handler post 메소드에 대한 설명

Handler 객체가 Post 메소드 안에 Runnable객체를 넣는 의미가 직접적으로 이해 되지 않아 문서를 본 결과. The  post  versions allow you to enqueue Runnable objects to be called by the message queue when they are received 상대방 측에서 메시지 큐로 부터 수신 했을 떄, 해당 Runnable 즉, 쓰레드 안에서 정의된 로직을 호출한다는 것이다.

(스칼라) build.sbt 설명

1. name : 프로젝트 이름 2. version : 프로젝트 버전 3. scalaVersion : 스칼라 버전 4. libraryDependencies : 의존하는 라이브러리 들을 설정한다. 또한 해당 라이브러리가 빌드 시점 중 어느 시점에 의존할지 설정한다. (예를들어 컴파일할 떄, 패키징 할 때) 5. assemblyOption in assembly : set-assembly  플러그인의 옵션 libraryDependencies에는 sbt가 관리하는 프로젝트가 의존하는 라이브러리를 설정하는데, Seq형식으로 설정한다. 여러개가 존재하는 경우 쉼표를 통해 구분하며, 하나의 의존라이브러리는 "<groupId> % <artifactId> % <version>" % "<configuration>" 형식으로 지정한다. <configuration>은 해당 라이브러리가 어느 의존 단계에 포함되어야 할지를 의미한다. provided 인 경우는 컴파일 단계에는 클래스패스에 포함되고, 패키지 단계에서는 포함되지 않는 것이다.

(하둡) core-site.xml 파일이란?

로그 파일, 네트워크 튜닝, I/O튜닝, 파일 시스템 튜닝, 압축 등 하부 시스템 설정 파일 core-site.xml 파일은 HDFS와 맵리듀스에서 공통적으로 사용할 환경정보를 설정한다. core-site.xml파일이 없을 경우 core-default.xml을 오버라이드 한다. 참고 :  http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/core-default.xml

(Git) reset과 revert의 차이 및 설명

이미지
1. reset은 그 이전으로 이력을 다시 돌리는 것이고 2. revert는 이전 이력은 그대로 두고, 그 되돌릴 커밋의 코드만 원복시키는 것 ========================================================== 1.  Reset  1. git reset <옵션> <돌아가고싶은 커밋> 옵션의 종류는 hard, mixed, soft 세가지 존재 예) 해당 커밋이후의 이력은 표를 예매하고, 팝콘과 사이다를 구매 1) hard 돌아가려는 이력이후의 모든 내용을 지워버린다. 이렇게 하면 표를 예매하고, 팝콘과 사이다를 구매했던 모든 것들이 지워지고 모든것이 초기화 $ git reset --hard a3bbb3c 2) soft 돌아가려 했던 이력으로 되돌아 갔지만, 이후의 내용이 지워지지 않고, 해당 내용의 인덱스(또는 스테이지)도 그대로 있는 상태. 즉, 바로 다시 커밋할 수 있는 상태로 남은 것이다. 기억은 되돌렸지만, 표와 팝콘과 사이다는 손에 들려 있는 상태 $ git reset --soft a3bbb3c 3) mixed(옵션을 적지 않으면 mixed 로 동작) 역시 이력은 되돌려진다. 이후에 변경된 내용에 대해서는 남아있지만, 인덱스는 초기화된다. 커밋을 하려면 다시 변경된 내용은 추가해야하는 상태 $ git reset --mixed a3bbb3c 또는 되돌아가는 커밋을 커밋 해쉬를 통해서 직접 지정할 수도 있고, 현재부터 몇개의 커밋을 되돌릴 수 있다. $ git reset HEAD~6 위는 현재부터 6개 이전 이력으로 돌아가라고 상대적으로 지정 2. Revert Revert는 상태를 되돌린다고 볼 수 있다. 스포를 당한 커밋을  revert하고 현재 작성중인 코드만 본다면 reset과 동일한 (hard옵션 준거만 뺴고) 결과를 가집니다. 하지만 이력은 같지 않습니다. 이전...

(알고리즘) 세그먼트 트리

(Java8) 스트림 병렬화와 CompletableFuture 병렬화

I/O 가 포함되지 않은 계산 중심의 동작을 실행할 떄는 스트림 인터페이스가 가장 구현하기 간단하며 효율적일 수 있다.(모든 스레드가 계산작업을 수행하는 과정에서는 프로세서 코어수 이상의 스레드를 가질 필요가 없다.) 반면 작업이  I/O 를 기다리는 작업을 병렬로 실행할 때는  CompletableFuture 가 더 많은 유연성을 제공하며 대기/계산(W/C) 의 비율에 적합한 스레드 수를 설정할 수 있다. 특히 스트림의 게으른 특성 때문에 스트림에서 I/O를 실제로 언제 처리할지 예측 하기 어려운 문제도 있다. ========================================================== 병렬 스트림에서는 스트림이 사용하는 쓰레드 풀의 크기가 고정되어 있어서 상점 수가 늘어나는경우 즉, 데이터 크기가 늘어나는 경우 유연하게 대응 할 수 없다.

(Spark) 스파크 처리 모델(RDD)

이미지
1. RDD 의 구조와 특징 RDD는 대량의 데이터를 요소로 가지는 분산 컬렉션이다. 거대한 배열과 리스트 등의 자료구조를 생각하면 이해하기 쉽다. RDD는 여러 머신으로 구성된 클러스터 환경에서  분산처리를 위해 설계되었고, 내부는 파티션이라는 단위로 나뉜다. 스파크는 파티션이 분산처리 단위이다. RDD를 파티션 단위로 여러머신에서 처리하므로 한 대의 머신으로 처리할 수 있는 것보다 더 큰 데이터를 다룰 수 있다 사용자는 예를들어 HDFS 등의 분산 파일 시스템에 있는 파일 내용을 RDD로 로드하고, RDD를 가공하는 형식으로 대량의 데이터를 분산처리할 수 있다. 스파크에서는 이런 가공은 변환(transformation) 이라 할 수 있다. 그리고 RDD의 내용에 따라 액션이라는 처리를 적용하여 원하는 결과를 얻는다. 이밖에도 RDD에는 불변(immutable) 즉, 내부 요소 값이 변경 불가한 성질과, 생성이나 변환이 지연 평가되는 성질이 있따. 2. RDD 다루기 RDD에는 변환과 액션 두 종류의 처리를 적용할 수 있다. 변환(Transformation) 변환이란 RDD를 가공하고 그 결과 새로운 RDD를 얻는 처리다. 변환 처리 후의 RDD가 가지는 요소는 변환 처리 전의 RDD에 들어있던 요소를 가공하거나 필터링해 생성된다. 변환은 다시 두 종류로 구분된다. 첫 번째, 변환 처리 전의 RDD가 가지는 요소를, 같은 RDD의 다른 요소들과 관계없이 처리할 수 있는 종류 사각형은 RDD 둥근 사각형은 파티션 그 안에 있는 것은 요소이다. filter 요소를 필터링한다. map 각 요소에 동일한 처리를 적용한다. flatmap 각 요소에 동일한 처리를 적용하고 여러 개의 요소를 생성한다.

(Spark) 스파크의 특징

1. 활용 배경 반복적인 처리와 연속으로 이루어지는 변환처리의 고속화 시행착오에 적합한 환경 제공 서로 다른 처리를 통합해 이용할 수 있는 환경 제공 1-1. 반복적인 처리와 연속으로 이루어지는 변환처리의 고속화 특정한 데이터셋에 대해 반복처리와 연속으로 이루어지는 변환처리를 고속화할 목적으로 개발되었다. 그전에는 맵리듀스가 널리 활용 되었다. 데이터의 지역성을 의시간 처리와 내결함성 그리고 확장성 등의 기능을 종합적으로 제공함으로써 복수의 머신으로 구성된 환경을 통한 병렬분산머신 처리를 더 쉽게 실현할수 있게 되었고, 그 결과 맵리듀스는 대량의 데이터 처리가 필요한 사용자들에게 널리 보급되었다. 맵리듀스는 기본적으로 입력데이터를 스토리지에서 읽고 복수의 머신에서 분산처리를 수행한 후 그 결과를 스토리지에 보존한다. 이 처리가 한번에 끝나지 않는 경우에는 데이터 플로형식으로 처리되어 읽기 -> 분산처리 -> 보존을 반복 수행한다. 맵 리듀스의 단점은 중간과정을 모두 메모리에 담을 수 없다는 것이다. 하지만 하드웨어의 발전으로 메모리의 크기가 커지면서 모든 데이터를 메모리에 담을 수 있게 되며 스파크를 사용하게 되었다. 1-2. 시행착오에 적합한 환경 제공 지금까지는 데이터의 통계분석과 머신러닝과 같은 처리를 하려면 표 계산 소프트웨어, 파이썬, R, 매틀랩 또는 BI툴을 이용했다. 이러한 것들은 결과를 바로 얻을 수 있는 것이 특징이었고, 따라서 시행착오를 겪어가며 데이터 해석을 한정적으로 하기에 적합한 환경이라 할 수 있었다. 하지만 한 대의 서버에서 처리할 수 있는 용량을 넘어서는 데이터셋에 대해서는 이런 종류의 툴이 현실적이지 않다. 거대한 용량의 데이터를 다룰 떄는 스케일 아웃과 같은 방법을 선택하고 여러 대의 머신으로 구성해 병렬 분산처리를 수행할 필요가 있다. 1-3. 다양한 처리를 통합할 수 있는 유연한 환경 배치처리, 스트림처리, SQL처리, 머신러닝 그...

(하둡) 클러스터에서 실행하기

1. 잡 패키징  로컬 잡 실행자는 잡을 실행할 떄 단일 JVM을 사용하므로 모든 잡에 필요한 모든 클래스가 로컬의 클래스 경로에 존재하면 문제없이 잘 동작한다. 하지만 분산환경은 조금 복잡하다. 잡을 시작할 떄  필요한 모든 클래스를 잡 JAR파일에 패키징해서 클러스터로 보내야한다. * 클라이언트 클래스 경로 hadoop jar <jar>로 지정하는 클라이언트의 클래스 경로는 다음과 같이 구성된다. 잡 JAR 파일 잡 JAR 파일의 lib 디렉토리에 있는 모든 JAR파일과 classes 디렉토리 HADOOP_CLASSPATH에 정의한 클래스 경로 이는 가끔 로컬 잡 실행자를 잡 JAR 파일 없이 실행 (hadoop CLASSNAME) 할 때 HADOOP_CLASSPATH 의존 클래스와 라이브러리를 지정해야 하는 이유를 설명해준다. * 태스크 클래스 경로 클러스터(의사분산 모드)에서 맵과 리듀스 태스크는 개별 JVM 으로 실행되며 클래스 경로로 HADOOP_CLASSPATH를 지정해도 소용없다.  HADOOP_CLASSPATH는 클라이언트 측 설정이며 따라서 잡을 제출하는 드라이버 JVM의 클래스 경로에만 해당되기떄문이다. 잡 JAR파일 잡 JAR파일의 lib 디렉토리에 있는 모든 JAR파일과 classes디렉토리 -libjars 옵션이나 DistributedCache 혹은 Job의 ddFileToClassPath()메소드를 사용해서 분산 캐시에 추가한 모든 파일 * 의존 라이브러리 패키징 클라이언트와 태스크의 클래스 경로를 지정하는 다양한 방식이 있고, 각 방식에 따라 잡의 의존 라이브러리를 포함하는 옵션이 있다. 라이브러리를 푼 후 잡 JAR에 넣고 다시 패키징 한다. 잡 JAR의 lib디렉토리에 라이브러리를 패키징한다. 잡 JAR와 다른 위치에 라이브러리를 두고, 이를 HADOOP_CLASSPATH와 -libjars를 이...

(Docker) Docker-Compose 작성법 및 명령어

1. version https://docs.docker.com/compose/compose-file/compose-versioning/ ========================================================== 1. List stacks or apps $ docker stack ls 2. Run the specified Compose file $ docker stack deploy -c <composefile> <appname> 3. List running services associated with an app $ docker service ls 4. List tasks associated with an app $ docker service ps <service> 5. Inspect task or container $ docker ========================================================== 2. Service 이 항목밑에 실행하려는 서비스를 기록한다 3. volumes docker run 으로앱 컨테이너를 실행할 때 --volume 을 사용하여 데이터를 로컬컴퓨터와 도커 컨테이너의 데이터를 연결하는 기능 4. environment  doker run 에서 -e 옵션에 적었던 내용들이다. 5. healthcheck 검사에 사용할 명령 (test)을 3초간격으로 열번시도하는 것 예) healthcheck :        test: "pg_isready -h localhost -p 5432 -q -U postgres"        interval: 3s       timeout: 1s        retries: 10 ======...

(Docker) Docker File 명령어

1. FROM 어떤 이미지를 기반으로 할지 설정한다. Docker 이미지는 기존에 만들어진 이미지를 기반으로 생성한다. 예) <이미지이름>:<태그> 형식이다. (ubuntu:14.04) 2. MAINTAINER 메인테이너 정보이다. 3.  RUN Shell 스크립트 혹은 명령을 실행한다. 이미지 생성ㅈ 중에는 사용자 입력을 받을 수 없으므로  apt-get install 명령에서 -y 옵션을 사용한다. (yum install도 동일) 4.  VOLUME 호스트와 공유할 디렉토리 목록이다.  docker run 명령에서 -v 옵션으로 설정할 수 있다. 예) -v /root/data:/data  호스트의 /root/data 디렉토리를 도커 컨테이너의 /data 디렉토리에 연결한다. 5. CMD 컨테이너가 시작되었을 때, 실행할 실행 파일 또는 스크립트이다. 6. WORKDIR CMD 에서 설정한 실행 파일이 실행될 디렉토리이다. 7. EXPOSE 호스트와 연결할 포트번호

(하둡) 직렬화

이미지
직렬화는 네트워크 전송을 위해 구조화된 객체를 바이트 스트림으로 전환하는 과정이다. 역지렬화는 바이트 스트림을 일련의 구조화된 객체로 역전환하는 과정이다. 직렬화는 프로세스간 통신과 영속적인 저장소와 같은 분산 데이터 처리의 독특한 두 영역에서 나타난다. 하둡 시스템에서 노드 사이의 프로세스 간 통신은 원격 프로시저 호출(RPC) 을 사용하여 구현한다. RPC프로토콜은 원격 노드로 보내기 위한 메시지를 하나의 바이너리 스트림으로 구성하기 위해 직렬화를 사용하고, 그 후 원격 노드에서 바이너리 스트림을 원본 메시지로 재구성하기 위해 역직렬화를 사용한다. 일반적으로 RPC직렬화 포맷이 유익한 이유는 다음과 같다. 간결성  간결한 포맷을 사용하면 데이터 센터에서 가장 희소성이 높은 자원인 네트워크 대역폭을 절약할 수 있다. 고속화 프로세스 간 통신은 분산 시스템을 위한 백본을 형성하기 때문에 직렬화와 역직렬화는 가능한 오버헤드가 작아야한다. 확장성 프로토콜은 새로운 요구사항을 만족시키기 위해 점차 변경되므로 클라이언트와 서버 사이의 통제 방식과 관련된 프로토콜의 발전도 직관적이어야한다. 예를들어 새로운 인자를 메소드 호출에 추가할 수 있어야하고, 새로운 서버는 기존 클라이언트에서(새로운 인자없이) 예전 포맷의 메시지도 수용할 수 있어야한다. 상호운용성 일부 시스템을 위해 다양한 언어로 작성된 클라이언트를 지원하는 편이 좋으며, 이를 가능하도록 포맷을 설계할 필요가있다. 하둡은 Writeable이라는 매우 간결하고 빠른 자체 직렬화 포맷을 사용한다. 그러나 확장하거나 자바외에 다른 언어를 사용하는 것은 어렵다.  에이브로는(Writable의 일부 한계를 극복하기 위해 설계된 직렬화시스템) 자바 기본자료형을 위한 Writable래퍼 char(IntWritable로 저장가능)를 제외한 모든 자바 기본 자료형을 위한 Writable 래퍼가 존...

(Python) Django 제네릭 뷰 오버라이딩

각 제네릭 뷰에서 제공하는 속성과 메소드가 많아서 모든 것을 설명하기는 어렵다. 여기서는 오버라이딩에서 자주 사용하는 속성과 메소드를 살펴보겠다. * 속성 오버라이딩 1. model 기본 뷰(View, TemplateView, RedirectView) 3개를 제외하고는 모든 제네릭 뷰에서 사용하는 속성이다. 뷰가 출력할 데이터가 들어 있는 모델을 지정한다. model 대신 queryset속성으로 지정할 수 도 있다. 다음 표현은 동일하다 model = Bookmark queryset = Bookmark.objects.all() 2. queryset 기본 뷰(View, TemplateView, RedirectView) 3개를 제외하고는 모든 제네릭 뷰에서 사용하는 속성이다. 뷰가 출력할 데이터가 들어 있는 모델을 지정한다. 출력대상이 되는 QuerySet객체를 지정한다. queryset속성을 지정하면 model은 무시된다. 3. template_name TemplateView를 포함해 모든 제네릭 뷰에서 사용하는 속성이다. 템플릿 파일 명을 문자열로 지정한다. 4. context_object_name TemplateView를 포함해 모든 제네릭 뷰에서 사용하는 속성이다. 템플릿 파일에서 사용할 컨텍스트 변수명을 지정한다. 5. paginate_by ListView와 날짜 기반 뷰에서 사용한다. 페이징 기능이 활성화된 경우에, 페이지당 몇 개 항목을 출력할 것인지 정수로 지정한다. 6. date_field 날짜 기반 뷰에서 기준이 되는 필드를 지정한다. 이 필드를 기준으로 년/월/일을 검사한다. 이 필드의 타입은 DateField 또는 DateTimeField 이어야 한다. 7. make_object_list 8. form_class FormView, CreateView, UpdateView 에서 사용한...

(Python) CSS selector

body > div.container > div:nth-child(1) > div.col-md-9.content > ul:nth-child(3) > li:nth-child(15) > a 에서 nth-child는 지원을 하지 않는다. 그래서 다음과 같이 변경한다. body > div.container > div:nth-of-type(1) > div.col-md-9.content > ul:nth-of-type(2) > li:nth-of-type(16) > a 이를 다음과 같이 변경가능하다. div > div.col-md-9.content > ul > li > a

(Python) CSS selector에서 점(.)과 샵(#)의 차이

#은 보통 id에 해당 하는 부분으로 html 파일에서 한 개만 존재 . 은 클래스 이름으로 해당 클래스를 여러 군데 사용할 수 있다.

(하둡) 맵리듀스에서 압축사용하기

입력 파일이 압축되면 맵 리듀스는 파일 확장명을 통해 사용할 코덱을 결정하고 파일을 읽을 떄 자동으로 압축을 해제할 것입니다. 맵리듀스 잡의 출력을 압축하려면 잡 환경설정에서 mapreduce.output.fileoutputformat.compress 속성을 true로 설정하고, 사용할 압축 코덱의 클래스 이름을 mapreduce.output.fileoutputformat.compress.codec 속성에 지정하라. <맵리듀스 압축 속성> 속성명  타입  기본값 설명 mapreduce.output.fileoutputformat.compress 불린 FALSE 출력압축여부 mapreduce.output.fileoutputformat.compress.codec 클래스명 org.apache.hadoop.io.compress.DefaultCodec 출력 압축에 사용할 코덱 mapreduce.output.fileoutputformat.compress.type 문자열 RECORED 순차 파일 출력에 사용할 압축 유형(NONE, RECORD, BLOCK) * 맵 출력 압축 맵리듀스 애플리케이션이 압축되지 않은 데이터를 읽고 쓰더라도 맵 단계에서 임시 출력을 압축하면 이익이 있다. 맵 출력은 디스크에 기록되고 네트워크를 통해 리듀서 노드로 전송되는데, 이때 단순히 LZO나 LZ4, Snappy와 같은 빠른 압축기를 사용하여 전송할 데이터 양을 줄이면 성능을 향상 시킬 수 있기 때문이다. <맵출력 압축속성> 속성명                                ...

(하둡) 어떤 압축 포맷을 사용해야 하는가?

하둡 애플리케이션은 거대한 데이터 셋을 처리하기 때문에 압축의 장점을 충분히 활용할 필요가 있다. 어떠한 압축 포맷을 사용할지는 파일크기, 포맷, 처리에 사용되는 도구 같은 고려 사항에 의해 결정된다. 압축과 분할 모두를 지원하는 시퀀스파일, 에이브로, ORCFile, 파케이 같은 컨테이너 파일 포맷을 사용하라. 보통 LZO, LZ4, Snappy 같은 빠른 압축형식이 적당하다. 상당히 느리긴 하지만 bzip2같이 분할을 지원하는 압축 포맷을 사용하라. 또는 분할을 지원하기 위해 색인  애플리케이션에서 파일을 청크로 분할하고, 지원되는 모든 압축 포맷(분할에 관계없이) 을 사용하여 각 청크를 개별적으로 압축하라. 이 경우 압축된 청크가 거의 HDFS 블록 하나의 크기가 되도록 청크 크기를 선택해야한다. 파일을 압축하지말고 그냥 저장하라 파일의 크기가 매우크면 전체 파일에 대한 분할을 지원하지 않는 압축 포맷은 권장하지 않는다. 그 이유는 지역성 을 보장하지 않으면 상당히 비효율적인 맵리듀스 애플리케이션이 되기때문이다.

(하둡) 압축

1. 압축  파일 압축은 저장공간을 줄이고, 네트워크 또는 디스크로부터 데이터 전송을 고속화 할 수 있는 두가지 커다란 이점이 있다. 압축포맷 도구 알고리즘 파일 확장명 분할 가능 DEFLATE N/A DEFLATE .deflate No gzip gzip DEFLATE .gz No bzip2 bzip2 bzip2 .bz2 Yes LZO lzop LZO .lz0 No LZ4 N/A LZ4 .lz4 No Snappy N/A Snappy .snappy No 모든 압축 알고리즘은 압축과 해제가 빨라 질 수록 공간이 늘어나는 희생을 감수해야 하기 때문에 공간과 시간은 트레이드 오프 관계에 있다. 위 표의 도구는 보통 9개의 옵션(-1은 스피드 최적화, -9는 공간 최적화를 의미)를 제공함으로써 어느정도 이러한 트레이드 오프에 대한 제어를 가능하게 한다. 다음 명령은 가장 빠른 압축 메소드를 사용해서 file.gz라는 압축 파일을 생성한다. %gzip -1 file 각 도구는  각기 다른 압축 특성이 있다.  gzip은 일반적인 목적의 압축 도구이고, 공간/시간 트레이드 오프의 중앙에 위치한다. bzip2는 gzip보다 더 효율적으로 압축하지만 대신 더 느리다. bzip2의 압축 해제 속도는 압축속도보다 더 빠르지만 여전히 다른 포맷에 비해 더 느리다. 한편 LZO, LZ4, Snappy 는 모두 속도에 최적화 되었고, gzip보다 어느정도 빠르지만 압축 효율은 떨어진다. Snappy와 LZ4는 압축 해제 속도 측면에서 LZO보다 매우 빠르다 '분할 가능' 열은 압축 포맷이 분할을 지원하는지 여부를 알려준다. 예를들어, 스트림의 특정 지점...

(하둡) 자바 인터페이스

1. 하둡 URL로 데이터 읽기 하둡 파일 시스템에서 파일을 읽는 가장 쉬운 방법은 java.net.URL객체로 데이터를 읽는 스트림을 하나 여는 것이다. 자바가 하둡의 hdfs URL 스킴을 인식하기 위해서는 약간의 작업이 필요 이 작업은 FsUrlStreamHandlerFactory의 인스턴스와 함께 URL클래스의 setURLStreamHandlerFactory()메소드를 호출하여 수행된다. 이 메소드는 JVM하나당 한번씩만 호출할 수 있기 떄문에 일반적으로 정적 블록에서 실행된다. 이러한 한계는 프로그램의 일부 다른 부분 에서 URLStreamHandlerFactory를 설정하면 하둡URL로부터 데이터를 읽을 수 없게 됨을 의미한다. 2. 파일시스템 API로 데이터 읽기 앞에서 설명했듯이 애플리케이션에서 URLStreamHandlerFactory 설정이 불가능한 경우가 있다. 이떄는 파일에 대한 입력 스트림을 열기위해 FileSystem API를 사용해야한다. 하둡시스템의 파일은 하둡 Path객체로 표현된다. 여기서 Path는 hdfs:://localhost/user/tom/quangle.txt같은 하둡 파일 시스템 URL을 의미한다. FileSystem은 일반적인 파일시스템 API로, 첫 단계는 접근할 파일시스템(여기서는HDFS)에 대한 인스턴스를 얻는 것이다. FIleSystem인스턴스를 얻을 수 있는 정적 팩토리 메소드는 다음과같다. public static FileSystem get(Configuration conf) throws IOException public static FIleSystem get(URI uri, Configuration conf) throws IOException public static FIleSystem get(URI uri, Configuration conf, String user) throws IOException Configuration객체는 클라이언트나 서버의 환경 ...

(Django) RestFramework 페이징 처리(DB 및 기타등등)

1. settings.py파일에 REST_FRAMEWORK = {    'DEFAULT_PAGINATION_CLASS' : 'study.pagination.TestPagination',    'PAGE_SIZE' : 10 } 2. study디렉토리(settings.py파일 있는 곳) 하위에 from rest_framework.pagination import PageNumberPagination class TestPagination(PageNumberPagination):   page_size_query_param = 'page_size' 적용한다. 그럼 데이터 조회시 자동으로 10개씩 잘라서 조회한다.

(Django) Router 사용

urls.py파일에 router 등록시 router = DefaultRouter() router.register(r'testName', views.~~', r'test) urlpatterns = [  url(r'^learning', include(router.urls)), ] 하게되면 testName이 router의 이름이 되고 url경로는 http://127.0.0.1:8000/learining/test 로 잡힌다

(하둡) 관계형 시스템 vs 맵리듀스

* 전통적인 RDBMS                vs                맵리듀스 데이터크기 : 기가바이트        vs               페타바이트 접근방식 :    대화형과 일괄처리 방식        vs      일괄 처리 방식 변경 :          여러번 읽고 쓰기                  vs       한번 쓰고 여러번 읽기 트랜잭션 :        ACID                          vs            없음 구조 :          쓰기 기준 스키마             vs           읽기 기준 스키마 무결성 :       높음                               vs           ...

(MongoDB) Sharding 및 Replication 구성하기 ver3.4

이미지
먼저 1. mongod서버 2. config서버 3. mongos가 필요하다 * mongod는 몽고 데이터를 저장하는 곳 * mongos는 request의 라우터 역할을 한다. * config는 데이터를 저장하지 않고 mongos로 부터 request가 들어왔을 때, 해당 데이터가 어느 샤드에 있는지 알려준다. * mongos는 config서버의 데이터를 캐쉬한다. 1. mongod 구성 * SHARD1 > --shardsvr --port 27017 --dbpath /data/db --replSet reply_replica (Secondary) > --shardsvr --port 27012 --dbpath /data/shard1 --replSet reply_replica  (Primary) > --shardsvr --port 27013 --dbpath /data/shard2 --replSet reply_replica (Secondary) *SHARD2 --shardsvr --port 28017 --dbpath /data/db1 --replSet shard2_replset (Primary) --shardsvr --port 28117 --dbpath /data/db1_replica1 --replSet shard2_replset (Secondary) 2. config 서버 구성 > mongod --configsvr --port 27010 이제 샤드를 등록해야 하는데 config서버에 해당 샤드 들을 등록해야하는데 ver3.4부터는 샤드의 replica만 등록 가능하다 그러므로 replica를 만들어야한다. 일단 Primary에 해당하는 27017포트에 해당하는 녀석에 접속한다 > mongo localhost:27017 그 후 rs.initiate()로 replicaSet을 구성한다. 해당 27017포트는 Primary가 된다 그 후 rs...

(MongoDB) ConfigServer 3.4 특징

config servers: Config servers store metadata and configuration settings for the cluster. As of MongoDB 3.4, config servers must be deployed as a replica set (CSRS).

(MongoDB) Mongos 프로세스

* Mongos 프로세스의 주요 특징 빅데이터를 샤드 서버로 분배해 주는 프로세스 하나이상의 프로세스가 활성화 된다. Application Server에서 실행이 가능하다 CONFIG 서버로 부터 Meta-Data를 캐시한다.

(MongoDB) 메모리영역 체크 명령어

1. db.serverStatus().mem {  "bits" : 64 //시스템 처리사양(64비트)  "resident" : 42 //Working Set 저장을 위한 메모리크기  "virtual" : 275, //Virtual 메모리 현재크기  "mapped" : 80m //Mapped File 현재크기 } 2. db.serverStatus().extra_info {  "page_faults" : 11722, //현재 메모리 페이지 폴트 수   "ramMB" : 3885 , //현재 시스템 메모리 크기수 }

(MongoDB) WiredTiger 엔진 특징

1. Document-Level Lock 제공으로 다수의 사용자가 트랜잭션 위주의 데이터를 빠르게 처리할 수 있도록 동시성을 제공합니다. 2. 인메모리 구조의 개선으로 몽고 디비 서버의 빠른 처리 성능이 개선되었습니다. 3. Memory Mapping 저장엔진은 단일 CPU 중심의 프로세싱 구조라면 wiredTiger 저장엔진은 Multi-Core 를 활용할 수 있는 시스템 구조이다. 다중쓰레드를 통해 집중화를 최소화 하였으며 동시성을 향상 시켰습니다. 4. CheckSums기능을 통해 시스템 장애 또는 저장 장치 장애로 부터 발생하는 데이터 유실을 최소화 할 수 있습니다. File system의 훼손 상태를 분석할수 있는 기능이 추가되었습니다. 5. 압축(Compression)기능을 통해 저장공간의 최소화가 가능합니다. 기본적으로 Snappy Compression 기능을 제공 

(Kafka) 토픽과 파티션의 이해

카프카는 고성능, 고가용성 메시징 애플리케이션으로 발전하는데 토픽과 파티션이라는 데이터 모델을 이용한다. 토픽은 메시지를 받을 수 있도록 논리적으로 묶은 개념이고, 파티션은 토픽을 구성하는 데이터 저장소로서 수평 확장이 가능한 단위이다. 1. 토픽의 이해 카프카 클러스터는 토픽이라 불리는 곳에 데이터를 저장한다. 토픽은 메일주소 시스템이라고 생각하면 쉽다. 토픽이름은 249자 미만으로 영문, 숫자, ' . ' , ' _ ', ' - ' 를 조합하여 자유롭게 만들 수 있다. 1개의 메시지를 보내는데 1초가 걸린다면, 4개의 메시지를 각각 보내게 되면 4초가 걸리게 된다. 큐시스템에서 한 가지 제약조건은, 메시지의 순서가 보장되어야 한다. 그렇게 때문에 이전메시지 처리가 완료된 후 다음 메시지를 처리하게 된다. 그래서 토픽안에 4개의 파티션을 만들어서 보낸다면 1초가 걸리게 된다. 무조건 파티션수를 늘려야 할까? 다음과 같은 단점이 있다. 파일 핸들러의 낭비 -> 각 파티션은 브로커의 디렉토리와 매핑 되고, 저장되는 데이터마다 2개의 파일(인덱스와 실제 데이터)이 있습니다. 카프카에서는 모든 디렉토리의 파일들에 대해 파일 핸들을 열게 됩니다. 결국 파티션의 수가 많을 수록 파일 핸들 수 역시 많아지게 되어 리소스를 낭비하게 됩니다. 장애 복구 시간 증가 - >   카프카는 높은 가용성을 위해 리플리케이션을 지원한다. 브로커에는 토픽이 있고, 토픽은 여러 개의 파티션으로 나뉘어지므로 브로커에는 여러 개의 파티션이 존재한다. 또한 각 파티션마다 리플리케이션이 동작하게 되며, 하나는 파티션의 리더이고 나머지는 파티션의 팔로워가 된다. 만약 브로커가 다운되면 해당 브로커에 리더가 있는 파티션은 일시적으로 사용할 수 없게 되므로, 카프카는 리더를 팔로워 중 하나로 이동시켜 클라이언트 요청을 처리할 수 있게된다. 이와같은 장애 처리는 컨트롤러...

(Json) @JsonNaming(SnakeCaseStrategy) 소스코드

 public static class SnakeCaseStrategy extends PropertyNamingStrategyBase     {         @Override         public String translate(String input)         {             if (input == null) return input; // garbage in, garbage out             int length = input.length();             StringBuilder result = new StringBuilder(length * 2);             int resultLength = 0;             boolean wasPrevTranslated = false;             for (int i = 0; i < length; i++)             {                 char c = input.charAt(i);                 if (i > 0 || c != '_') // skip first starting underscore         ...

(운영체제) 페이지

<페이지 캐시> 리눅스는 파일 I/O의 성능 향상을 위해 페이지캐시라는 메모리 영역을 만들어서 사용합니다. 한 번 읽은 파일의 내용을 메모리 영역에 저장시켜 두었다가 다시 한번 접근하면 디스크에 접근하지 않고 메모리에 접근해 데이터를 불러오는 곳입니다. free 명령어로 확인해봅니다. $ free -k

(Zookeeper) 환경설정파일

conf내에 있는 zoo.cfg에 tickTime=2000 dataDir=/Users/james/zookeeper clientPort=2181 이것은 표준 자바 속성 파일이고, 다음 세개의 속성은 주키퍼를 독립모드로 실행할 때 필요한 최소한의 속성이다. tickTime은 주키퍼의 기본시간 단위 dataDir은 주키퍼가 영속적인 데이터를 저장할 로컬파일 시스템의 위치이며, clientPort는 클라이언트의 연결을 기다리는 포트이다. dataDir은 사용사 시스템에 맞게 적적힐 변경해야 한다. 다음과 같이 서버를 시작한다. zkServer.sh start 그후 서버가 시작되었는지 확인한다. echo ruok | nc localhost 2181

(MongoDB) 읽기 격리성, 일관성, 그리고 최신성

1. 트랜잭션이 보장해야 하는 ACID 원자성(Atomicity) : 한 트랜잭션 내에서 실행한 작업들은 모두 하나의 작업으로 간주한다. 모두 성공 또는 모두 실패되어야한다. 일관성(Consistency) : 모든 트랜잭션은 일관성있는 데이터베이스 상태를 유지해야 한다. DB에서 정한 무결성 조건을 항상 만족 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않아야 한다. 지속성 (Durability) : 트랜잭션을 성공적으로 마치면 그 결과가 항상 저장되어야 한다. 격리성에 대한 이슈가 존재한다. 격리성을 완전히 보장하기 위해 모든 트랜잭션을 순차적으로 실행한다면 동시성 처리 이슈가 발생한다. 반대로 동시성을 높이기 위해 여러 트랜잭션을 병렬처리하게 되면 데이터의 무결성이 깨질 수 있다. * Serializable Isolation Level 이란?  - SELECT 수행시 READ LOCK ,즉  sharded lock을 사용 - 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 sharded lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다. * 몽고DB의 격리성 Read Uncommitted 몽고디비에서는 클라이언트가 끝나지 않는 쓰기 작업(durable) 에 접근할 수 있다. Read Uncommitted는 디폴트 설정으로 mongod인스턴스와 레플리카셋 샤드 클러스터에 적용할 수 있다. Read Uncommitted And Single Document Atomicity single 쓰기 수행작업이 단건이 아닌 여러개의 documents를 수정할 때,  각각의 document에는 원자성(atomic)이 보장된다. 그러나 그 수행작업 전체에는 원자성이 보장되지 않고, 다른 수행작업이 간섭할 수 있다. 그래서 다음과 같은 특징을 ...