2019년 12월 23일 월요일

[Python] Bar 차트 그리기.

matplotlib을 이용해 작성한 Bar 차트 그리기 코드.
데이터 목록에 대한 라벨과 값을 같이 표시해준다.

import numpy as np
import matplotlib.pyplot as plt

def draw_bar_chart(values, labels, subject, draw_mean=True, fig_size=(10, 6)):
    x = np.arange(len(values))
    y = values

    figure = plt.figure(figsize=fig_size)   
    barlist = plt.bar(x, y, 0.7, align='center')

    mean = np.mean(y)
    if draw_mean == True:
        plt.axhline(mean, color='coral', linestyle='dashed', linewidth=1)

    handles = []
    for index in np.arange(len(barlist)):
        if barlist[index].get_height() > mean:
            color = '#ec8482' # red like
        else:
            color = '#a4d8ee' # blue like
        barlist[index].set_color(color)
        handles.append(plt.Rectangle((0,0), 1, 1, color=color))

    plt.title(subject)
    plt.legend(handles, labels)
    plt.xticks(x, labels)

    for bar in barlist:
        height = bar.get_height()
        val = int(height)
        plt.text(bar.get_x() + bar.get_width() / 2.,
                 height + height*0.01,
                 val, ha='center', va='bottom')
    plt.show()

draw_bar_chart([1,2,4,6,7,7], ['v1','v2','v3','v4','v5','v6'], 'sample')

[펌] 데이터 분석으로 미래를 예측할 수 있을까

https://brunch.co.kr/@amangkim/35

아래는 개인적으로 추린 요약. 원문을 읽는게 당연히 좋다.

  "데이터를 다루는 사람은 많은 양의 데이터 수집을 통해 보다 정확한 미래를 예측할 수 있다고 믿는다. 하지만 데이터는 어찌됐던 과거의 산물이다. 데이터의 양이 많아진들 그 또한 과거의 것이기 때문에 미래를 직접 대변할 수는 없을 것이다. 많은 데이터 과학자들은 데이터를 이용해 미래를 예측하거나 영향력을 분석한다. 여기엔 큰 가정이 깔리는데 그 가정은 '과거의 사건들이 현재나 미래에도 재현된다.'라는 것이다. 모든 통계학의 예측 모델은 바로 이 재현성을 기반으로 한다. 그러나 미래는 재현되지 않는다. 쉽게 적으면 2017년 1월 1일은 1999년 1월 1일과 다른 것을 떠올리면 될 것이다. 비슷하게 보일 뿐 같은게 아니다. 통계를 통한 미래 예측은 과거를 기반으로 미래를 Simulation하는 것만이 가능할 뿐, 실질적인 미래를 예측할 수는 없을 것이다."

글쓴이는 예측과 패턴을 구분짓고 있다. 이 둘을 구분하는 기준은 시간의 영향력, 즉 재현성인데 시간의 영향력(시간에 따라 결과가 크게 달라진다면)이 크면 예측이 되고, 시간의 영향력이 없거나 작으면 패턴의 문제가 되는 것이다. 시간의 영향력이 작다는 것은 조건만 맞으면 같거나 아주 비슷한 결과가 재현 된다는 것을 뜻한다. 이건 충분히 가능하다는게 글쓴이의 주장이다. 즉, 데이터 분석의 목표로 잡아야 할 것은 미래의 예측이 아니라 과거 데이터로부터 패턴을 찾아내는 것이다. 한계점을 받아들이고 데이터를 기반으로 추측된 값이 갖는 의미와 속성을 파악하는 것은 틀림없이 유용한 도구가 된다.

무언가를 시도하기에 앞서 잊지 말아야 할 것은 데이터 과학의 기반이 되는 통계학, 확률론이 가지는 속성, 확률론의 모집합이 되는 수학이 갖는 속성을 되도록 바르게 파악하고 있어야 한다.

2019년 12월 22일 일요일

개발 분야와 관계없이 알아야 하는 기술 10가지

개발 분야와 관계없이 알아야 하는 기술 10가지 - 이기곤@수아랩

한빛 데브그라운드 주니어 2019년 발표 내용 정리.

1. 문자열 인코딩: 인코딩이란 컴퓨터가 문자를 이해할 수 있게 만든 규칙.
 - ASCII: ANSI에서 표준화한 7비트 부호체계. 영문 키보드로 입력할 수 있는 모든 기호들이 할당되어 있음. UTF-8과 호환, UTF-16과는 비호환.
 - EUC-KR: 완성형 한글 인코딩 방식 중 하나로 시작은 '가', 끝은 '힝'. 지원 글자 수는 2,350자로 현대에 쓰이는 한글 11,172자에 비해 부족. (Legacy, 결제 모듈 등에서 아직 쓰임.)
 - Unicode: 전 세계의 모든 문자를 다루도록 설계된 표준 문자 전산 처리 방식. Unicode 인코딩 방식엔 UTF-8, UTF-16, UTF-32가 있고, 일반적인 상황에선 UTF-8을 Unicode라 봐도 무방.
 - UTF-8: 사실상 표준으로 글자당 1~4 바이트를 쓰는 가변 길이 인코딩 방식. 1바이트 영역은 ASCII 코드와 하위 호환성을 갖음.
 - UTF-16: 2바이트 고정 인코딩 방식. 자바, 윈도우즈 API에서 쓰이는데 멀티바이트라고도 함. 호환성 목적으로 지금도 쓰임.
 - UTF-32: 4바이트 고정. 데이터 낭비가 심해서 사실상 쓰지 않지만 가변 길이를 고려하지 않아도 되는 장점 또한 존재해 프로그램 내부적으로 쓰이는 경우도 있음.

​2. Base64: 바이너리 데이터(이미지, 텍스트, 동영상)를 아스키 기반 문자열로 인코딩하는 방법. (Base64가 암호화는 아님.)
 - 문자 코드에 영향을 받지 않도록 공통 ASCII 영역의 문자만을 이용해 인코딩 함.
 - 64진법. 전자 메일이나 웹 등의 환경에서 바이너리 데이터를 전송하는데 쓰임. (RFC 1421, RFC 2045)
 - 바이너리 -> 텍스트 변환 간 일반적으로 33% 데이터 용량이 증가.

3. JSON: 구조화 텍스트 기반 데이터 규격. 정수, 실수, 텍스트를 규격화해서 실어 나르는 용도.
 - 장점: 범용적, 읽기 편함. 디버깅 용이. (JSON 자체가 JavaScript Object Notation인 만큼 JavaScript 언어와 궁합이 아주 좋다.)
 - 단점: 고용량, 처리 비용이 높음. (일반적인 사용 사례에선 무시해도 되는 수준이나 Latency가 중요한 게임, 금융 도메인에선 사용에 제약.)
 - JSON 규격 변경에서 오는 결함 가능성을 염두하고 사용해야 함.
 - Latency가 중요한 경우엔 Google Protocol Buffer 또는 Apache Thrift 사용이 권장됨.

​4. 다국어 처리: 프로그램 수정없이 여러 언어를 지원하는 기법.
 - 리소스 파일 방식, 언어 설정 방식, 프로그램 방식(하드코딩)으로 대응할 수 있음.
 - 일반적으론 Key:Value 기준으로 타깃 언어를 획득하는 방안.
 - Python엔 gettext 패키지, 안드로이드, C# 등의 언어는 프레임워크 레벨에서 지원됨. 표준은 i18N

​5. 날짜와 시간
 - 데이터를 주고 받는 상호간 타임존이 서로 다르면 문제가 발생함. 타임존 기준은 UTC, 한국은 UTC+9. 하나의 시스템은 하나의 타임존을 쓰도록 하는 것이 권장됨.
 - Apache Zookeeper 같은 분산 코디네이션 툴을 쓰면 특정 시스템의 타임존 변화를 감지할 수도 있음. 또는 주기적으로 상호간 타임존을 확인하고 맞추는 프로토콜을 반영하는 것도 고려할 수 있음.
 - 시간엔 단조 시간과 실제 시간(벽 시계 시간, wall time)이 있음.
 - 단조 시간? 운영체제나 CPU가 계산하는 시간으로 운영체제 시작 이후 종료까지 절대 바뀌지 않는 시간을 말함. 단조 시간은 컴퓨터마다 독립적.

​6. 정규표현식 (Regex): 주어진 문자열 속에서 특정 패턴을 찾고자 할 때 쓰는 기술.
 - 비밀번호 패턴에 숫자 1개, 영어 소문자 1개, 영어 대문자 1개 포함 조건 같은걸 적용한다면 정규 표현식으로 걸러낼 수 있음.
 - 정규표현식은 regex101.com과 같은 사이트에서 미리 검증해볼 수 있음.

​7. UUID: 범용 고유 식별자. 네트워크 상에서 서로 모르는 개체를 식별하기 위한 기술.
 - 4개의 하이픈과 32개의 16진수 문자로 구성. 하이픈을 빼면 저장하는데 16바이트가 필요함.
 - UUID v4는 중앙 관리 시스템에서 고유 ID를 할당받는 방식이 아니라 개발 주체가 스스로 식별자 이름을 짓는 방식임.
 - 중앙의 서버 없이 Object, 작업, 요청 등에 ID를 메기고 싶은 경우 UUID를 쓰면 된다.
 - 340,282,366,920,938,463,463,374,607,431,768,211,456개의 경우의 수를 갖기 때문에 사실상 중복은 없다고 인정됨.
 - UUID에도 충돌은 있을 수 있으므로 신뢰성이 중요한 시스템에선 UUID Pool(UUID 십만개, 백만개를 먼저 생성한 뒤 그 안에서만 쓰도록)을 고려해야 함.

​8. Random: 게임 규칙, 랜덤 박스, 암호화키, UUID 등을 만드는데 쓰임.
 - 쓰임에 따라 유사난수와 완전 난수를 구분해야 함. 우리가 쓰고 있는 대부분의 난수는 유사 난수임.
 - 유사난수는 난수를 흉내낸 난수로, SEED 값을 주면 SEED 값 기준으로 일련의 숫자를 만들어낸다. 완전한 난수에 비해 연산속도가 빠름.
 - 게임에서 유사난수 같은걸 쓰면 특정 시간대의 아이템 드랍률을 미리 알 수도 있으므로 문제가 된다.
 - 완전 난수는 암호학적으로 안전. 유사 난수에 비해 3~4배 느림. 노이즈를 이용해 난수를 만들어내므로 예측 불가. 인증키 생성, 게임 아이템 생성 등에 쓰인다.

​9. 해쉬함수: 임의의 값을 고정길이 값으로 변환하는 알고리즘.
 - 특정 해시 알고리즘은 입력값, 입력길이와 무관하게 항상 같은 길이의 문자열을 반환한다.
 - 해시 맵 같은데 쓰인다. 해시 충돌이 있긴 하지만 가능성은 매우 낮으며 접근 비용이 O(1)이므로 매력적.
 - 민감한 데이터의 변조를 막는데 쓸 수 있다. 암호를 해시화해서 저장하는데 보안 향상을 위해 Salt가 추가됨.
 - SHA-256 이상을 쓰는것이 안전하지만 MurmurHash의 경우 안전하진 않아도 성능이 매우 좋아 Redis같은 곳에서 쓰인다.

​10. HTTP(S): 웹의 기반 기술. 텍스트 기반의 통신.
 - HTTP의 주요 성질은 Stateless. 상대가 살아있는지 요청하기 전엔 알 수 없다.
 - HTTP는 기본적으로 1개의 요청당 1개의 소켓을 사용. 요청 전 연결하고 데이터를 주고받은 후 연결을 끊음.
 - Stateless 성질을 보완하기 위해 Cookie, Session 등을 쓴다. (또는 웹소켓, HTTP/2를 쓰면 연결 중단 없이 데이터 송수신 가능)
 - Header에 주요 정보가 담긴다. (User-Agent, Content-Type, Accept ...)
 - HTTPS = HTTP + TLS, HTTPS의 암호화 수준은 TLS 버전에 따른다. 서버의 경우 인증된 제3자가 발급한 인증서가 필요.
 - 분산 환경에선 스티키 세션(처음 연결된 서버와만 통신되게끔 하는 개념), CORS(서로 다른 도메인간 요청을 허용해 주는 것.)에 대해 알아둘 필요 있음.

2019년 12월 18일 수요일

Base 64 인코딩 (RFC 1421, RFC 2045)

https://tools.ietf.org/html/rfc2045#page-24

바이너리 데이터를 문자 코드에 영향 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 문자열로 바꾸는 인코딩 방식. 64진법으로, 이는 2의 제곱수들에 기반한 진법들 중에서 화면에 표시되는 ASCII 문자들을 써서 표현할 수 있는 가장 큰 진법이다. 다음 제곱수인 128진법에는 128개의 기호가 필요한데 화면에 표시되는 ASCII 문자들은 128개가 되지 않는다. 이러한 까닭으로 Base 64 인코딩은 전자 메일이나 웹 등의 환경에서 바이너리 데이터를 전송하는데 쓰인다.

일반적으로 Base 64 인코딩은 62개의 'A-Z, a-z, 0-9'를 쓰며 62번째엔 '+', 63번째엔 '/'를 쓴다. 그리고 패딩이나 문자열의 끝을 처리하기 위한 문자로 '='를 쓴다. 이렇게 했을 때 데이터 전송량이 약 33% 정도 늘어나는 단점이 있지만 딱히 대안이 없다.

ASCII로 인코딩해서 바로 전송하는게 더 낫지 않느냐는 의문이 생길 수 있는데 시스템 별로 ASCII를 처리하는 방식에 차이가 있고 ASCII는 여러 제어 문자를 포함하고 있기 때문에 이기종 간 데이터 교환으로부터 안전(적합)하지 않다.

2019년 11월 18일 월요일

중국어로 기록된 파일이 깨져서 표시될 때


중국 고객사에서 로그 파일을 보내왔는데 받아서 열어보니 깨져서 알아볼 수가 없다. 이런 식이다.

橙깊극伎槨왕鹿豚冀警속늪죗

인코딩을 유니코드나 UTF-8로 변환해도 표시가 안되는 것을 보면 윈도우에 중국어 언어 패키지를 따로 설치해야 하는 것 같다. 번거로운 과정이다.

다행스럽게도 구글 번역기에서 문서 번역 기능을 제공하고 있다. 아래 주소로 들어가서 문서 파일을 업로드하면 원하는 소스에서 타겟으로 말끔하게 변환해준다. 구글에서 지원해주는 확장자는 .doc, .docx, .odf, .pdf, .ppt, .pptx, .ps, .rtf, .txt, .xls, .xlsx로 거의 모든 문서 형식을 지원하고 있다.

https://translate.google.co.kr/#view=home&op=docs&sl=en&tl=ko


2019년 11월 11일 월요일

Java 정렬, Collections.sort()

Java에서 정렬은 java.util.Collections의 static 메서드 sort()를 이용하는 것이 가장 편리하다.
아래의 두가지 sort() 메서드가 제공된다.

  • static <T extends Comparable<? super T>> void sort(List<T> list)
  • static <T> void sort(List<T> list, Comparator<? super T> c)

첫 번째 메서드는 정렬의 대상이 Comparable 인터페이스를 구현하고 있어야 한다. (참고: Java 제네릭에서는 매개변수화된 타입에 제약조건을 줄 때 extends와 implements를 퉁쳐서 extends 하나의 키워드를 쓴다.) Comparable 인터페이스를 구현하는 것은 어렵지 않은데 int 값을 반환하는 compareTo 메서드 하나만 구현하면 된다.

두 번째 메서드는 Comparator 인터페이스를 구현하고, 이를 인자로 넣어주는 방식이다. 사용자 타입이라든가 기본적으로 Comparable을 구현하고 있는 String과 같은 타입에 대해 다른 방식의 정렬 기준을 지정하고 싶을 때 쓰면 된다. Comparable과 유사하게 int 값을 반환하는 compare 메서드를 구현해야 한다.

반환 값은 C언어의 정렬 방식과 같이 두 비교 객체가 같으면 0, this가 인자보다 크면 1(양의 정수), this가 인자보다 작으면 -1(음의 정수)를 지정해주면 된다.

2019년 10월 13일 일요일

INEOS 1:59 챌린지 마라톤, 엘리우드 킵초게 2시간 최초 돌파.

영국 화학업체 이네오스(INEOS)가 마라톤 최초 2시간 돌파를 위해 마련한 비공식 마라톤 경기가 어제 오스트리아 빈에서 열렸다. - INEOS 1:59 챌린지 마라톤

러너는 남자마라톤 세계기록(2시간 1분 39초) 보유자인 케냐의 엘리우드 킵초게(35). 최적의 조건에서 뛰기 위해 상세 시간 없이 경기 날짜만 정해두었고, 습도, 온도가 최적이 되었을 때 경기가 시작되었다.

페이스메이커는 무려 41명이 동원되었다고, 킵초게 앞에 달리는 차는 형광색 빛을 쏘며 속도 조절을 도왔다. 2년전 첫 번째 도전에선 실패했다고 하는데 이번엔 멋지게 성공하였다. 500미터를 남겨둔 지점에서 페이스 메이커와 간격을 조금씩 벌려 나가며 피니쉬 라인을 지나는 모습이 감동적이다.

https://www.youtube.com/watch?v=k-XgKRJUEgQ



2019년 9월 26일 목요일

폴 스미스와 유발 하라리의 조언.

폴 스미스

1. 가정하지 마라. (당연하다는 것을 당연하다고 받아들이지 마라.)
2. 모든 것에서 영감을 찾을 수 있다.
3. 표절하지 마라. (영감과 표절은 다르다.)
4. 고민해라.

"Never assume.
Keep your eyes open.
Don't copy.
Get inspired."

원글: https://brunch.co.kr/@voiz/22


유발 하라리

• 오직 모든 것이 변한다는 사실만 유효하다. (예전보다 더 빠르게.)
• 특이점(Singularity, 과학 기술이 폭발적 성장단계로 도약해, 인간 본연의 생물학적 조건을 뛰어넘는 새로운 문명을 낳는 시점)이 온다.
• 변화에 대응하는 능력을 길러야 한다. 새로운 것을 배우고, 익숙하지 않은 상황에서도 균형을 유지하는 능력을 길러야 한다. 때론 가장 잘 아는 영역을 포기해야 하며 전혀 알지 못하는 분야를 편안하게 받아들여야 한다.
• 우리는 특정 정보를 아는 것보다 ㅡ
 1) 정보가 합리적인지를 판단할 수 있는 능력
 2) 무엇이 중요하고 무엇이 그렇지 않은지를 알 수 있는 능력
 3) 수많은 작은 정보를 모아 세상에 대한 폭넓은 관점을 가지는 능력
이 더 중요해진다.

오늘날 우리가 익혀야 할 것은 4C.
1. Critical Thinking
2. Communiation
3. Collaboration
4. Creativity

원글: https://ppss.kr/archives/175097


2019년 9월 17일 화요일

JPA 코드 없이 적는 기본 개념

JPA는 Java의 객체를 table/relationship에 직접 매핑해보자는 시도에서 출발한다. 이런걸 ORM이라고 한다. JPA 이전에 ORM은 이러한 프레임워크를 나타내는데 쓰이는 일반적인 용어였다.

JPA를 쓰면 애플리케이션의 클래스를 데이터베이스의 테이블에 매핑할 수 있다. (쿼리를 작성해 날리는 방식인 JPQL도 엔티티 간 매핑을 알고있는 상태에서 수행되기 때문에 SQL과 차이가 있다.) 객체 모델을 중심으로 사고할 수 있는 것이다.

JPA는 스펙이고, JPA 스펙을 구현하기 위해선 ORM 구현체(Provider)가 필요하다. 여기서 주로 쓰이는 구현체가 바로 그 Hibernate다. (Hibernate가 JPA를 위해 만들어진 것은 아니다. 특정 부분에선 JPA가 제공하는 기능 외의 기능들을 Hibernate가 제공하기도 한다.) 그리고 Hibernate 대신에 다른 JPA 구현체를 사용할 수도 있다.
 → 서블릿과 JSP가 웹 개발을 위한 자바 스펙인 것처럼, JPA는 ORM을 위한 자바 스펙인 것이다. 이와 유사하게 톰캣, 제티가 서블릿/JSP 스펙을 구현한 서버인 것처럼 Hibernate가 JPA 스펙을 구현하고 있다.

출발은 엔티티다.
데이터베이스 테이블에 저장하고 싶은 클래스 타입에 @Entity를 붙여주면, 클래스 타입에 맞는 테이블이 데이터베이스에 생성되는 방식이다. 인자없는 기본 생성자는 습관적으로 만들어주는 것이 좋다. 마지막으로 객체마다 ID를 설정해 주어야 한다. ID에 해당하는 필드엔 @Id, @GeneratedValue 어노테이션을 붙여준다.

일반적으론 JPA 조차도 쌩으로 쓰진 않는다. Spring Data JPA 라이브러리를 통하게 되는데 Spring Data JPA는 JPA를 더 쓰기 편하게 만들어 놓은 라이브러리다. JPA의 EntityManager를 직접 다루지 않고도 Spring Data JPA의 Repository를 통해 더 쉽게 데이터를 다룰 수 있게 된다.

특별한 관계를 갖지 않는 엔티티의 CRUD는 JpaRepository<T, ID>를 상속받고 @Repository 어노테이션을 붙이는 것만으로 끝이난다. 더 할게 없다. (조금만 살펴보면 JpaRepository는 PagingAndSortingRepository를 상속받고, PagingAndSortingRepository는 다시 CrudRepository를 상속받는 것을 알 수 있다.)

관계 설정도 어노테이션 방식으로 처리된다.
@OneToMany, @ManyToOne, @ManyToMany
관계 설정의 방식, mappedBy 속성의 사용 방식에 따라 조인 테이블의 생성 유무 등 테이블이 생성되는 방식이 조금씩 바뀐다.

• 자주 쓰이는 설정값
/src/main/resources/application.properties
# 세션 메트릭을 보여준다.
spring.jpa.properties.hibernate.generate_statistics=true

# 쿼리를 표시한다.
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

• 사용상 주의점
JPA가 만능은 아니다. 특정 작업에선 SQL과 비교했을 때 성능 저하가 생길 수 있다. 통계, 집계, 배치와 같은 작업은 JPA보다 SQL을 쓰는 것이 낫다고 알려져 있다.

2019년 9월 16일 월요일

C# Event vs Delegate, 이벤트와 델리게이트 차이점.

이벤트는 델리게이트와 동일하게 +, -의 조합 연산자를 지원하고, 실제 동작도 거의 같아 보이지만 이벤트엔 컴파일러가 적용하는 몇가지 사양이 있고, 속성에 대한 두가지 접근자가 추가된다는 차이점이 있다.

디자인 상 가장 큰 차이점은 이벤트는 인터페이스 내부에 선언할 수 있지만 델리게이트는 선언할 수 없다는 점이다. 이는 프로퍼티를 인터페이스 내부에 선언할 수 있지만 필드는 선언할 수 없는 C#의 디자인과 관련이 있다. 어떤 얘기인가 하면,

public event EventHandler SomeEvent;

코드는 다음과 같이 변환된다.

private EventHandler _SomeEvent;

public event SomeEvent {
    add { _SomeEvent += new EventHandler(value); }
    remove { _SomeEvent -= new EventHandler(value); }
}

위 코드 변환의 영향으로 이벤트는 클래스 내부에서만 호출할 수 있게 된다. 반면 델리게이트는 public인 경우 클래스 외부에서도 제약없이 호출할 수 있다.

차이점이 있다 하더라도 이 둘은 거의 같다. 알다시피 EventHandler도 델리게이트다. (.Net에서 이벤트는 event 키워드만 붙이면 어떤 형식의 delegate도 허용하긴 하지만 표준은 아래의 형식을 따르는게 맞다.)

public delegate void EventHandler(object sender, EventArgs e);
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

개인적인 생각으론 통지 구조를 인터페이스에 드러내거나 publish 동작을 클래스 내부에 감추는 목적으로 이벤트와 델리게이트를 구분해서 쓰면 될 것 같다.

2019년 9월 4일 수요일

2019.09.04 AWS 웨비나 세션 4 정리(?)

.. 세션 3은 잘 못들었는데 세션 2와 유사한 내용의 발표였음. RedShift 솔루션 위주로 설명이 진행됨.

■ 세션 4. Amazon의 머신러닝 솔루션: Fraud Detection & Predictive Maintenance

• AWS ML Stack
- 전세계 모든 데이터 과학자와 개발자가 손쉽게 활용할 수 있는 편리한 인공지능 & 머신러닝 환경을 제공
- AI Services + ML Services + ML Frameworks & Infrastructure.
- AWS 클라우드 환경에서 제공되는 여러 서비스와 연계해 시스템을 구축할 수 있다. 타사 솔루션 대비 비용을 효율적으로 가져갈 수 있다고 함.

• Fraud Detection using Machine Learning (SageMaker 기반 솔루션)
# 잠재적 사기 행위의 탐지 및 검토 활동을 플래그 하는 아키텍처 구축 방법
# KB 국민은행의 인공지능을 활용한 금융사기 방지 시범과제 사례.
- Lambda를 이용해 프로세싱 파이프라인을 만들 수 있고, QuickSight를 이용해 예측된 결과를 시각화할 수 있다. (Lambda가 트리거 역할.)

• Predictive Maintenance using Machine Learning
# 장비의 이상 동작, 멈춤, 고장에 관한 잠재적 징후를 탐지하고 예방 및 조치에 권장하는 작업을 제공하는 아키텍처 구축 방법
- 센싱 데이터 기반으로 설비의 예기치 않은 고장을 탐지, 예측.
- RUL(remaining useful life) 예측.
- Fraud Detection 솔루션과 마찬가지로 CloudWatch Events로, 트리거는 Lambda가..
# 'turbofan degradation dataset'이 기본으로 제공됨. 데이터 성격이 맞으면 이걸 그냥 쓰면되고 성격이 다르면 이 모델을 시작점으로 해서 customization이 가능하다.
- 현재 Apache MXNet Gluon dataset 기반으로 만들어져 있다고 하는데 MXNet Gluon이 뭔진 잘 모르겠다. 업무 절차는 SageMaker notebook instance로 모델 트레이닝을 한 뒤에 SageMaker MXNet 모델로 모델을 생성한 다음에 S3 버킷에 놓고 돌리는 방식.

후반부엔 데모까지 있었으나 전반적으로 잘 와닿진 않았다. 실제로 써봐야 알겠는데, 난이도가 좀 있어 보인다.

2019년 9월 3일 화요일

2019.09.04 AWS 웨비나 세션 1, 2 정리

들을 때마다 느끼는데 AWS 소속 아키텍트 분들은 설명 참 잘하신다. 스케일 예측이 안되거나 덩치가 너무 커져버린 경우엔 매니지드 클라우드 서비스는 거의 필수적인게 되버린 것 같다. 다행스럽게도(?) 우리 회산 아직 해당 사항 음슴.

■ 세션 1. 클라우드 기반 데이터 분석 및 인공 지능을 위한 비즈니스 혁신


... 초반 30분 못들음.

• Amazon Athena
- 표준 SQL을 사용해 Amazon S3에 저장된 데이터를 간편하게 분석할 수 있는 대화식 쿼리 서비스. 서버리스 서비스라 관리할 인프라가 없고, 실행 쿼리에 대한 비용만 지불하면 된다.

• Amazon Kinesis
- 실시간 데이터를 Data Lake로 전달하는 역할. 슈퍼셀에선 Kinesis를 이용해 일 450억건의 게임 사용자 실시간 데이터를 수집한다. Netflix는 1000개 이상의 Kinesis 샤드를 통해 일 수십억건의 VPC Flow Logs를 분석하고 있다.

• 하이퍼 커넥트 AWS 이용 사례
- Redshift를 활용 데이터 수집 및 집계 (9시간 -> 3시간 단축)
- EMR을 이용해 데이터 전처리 (4시간 -> 18분 단축)
- Elasticsearch를 통한 실시간 분석 (다양한 시각화 구현)

• 머신 러닝 모델 구축에 대한 어려움
- 훈련 데이터 수집 및 저장
- 최적화된 기계학습 알고리즘 선택
- 데이터 훈련을 위한 인프라 설정
- 훈련 및 학습 모델 튜닝 (반복)
- 최적화한 모델 배포
- 모델 기반 서비스 확장 및 운영
-> Amazon SageMaker, 완전 관리형 ML 서비스. 생성, 훈련, 그리고 배포까지 원클릭 형태로 머신 러닝 모델을 상품에 반영할 수 있다. #국내 MATHPRESSO 사례.

• 비즈니스 의사 결정을 위한 분석 빌딩 블럭
# 수집
Kinesis: 스트리밍 데이터
Direct Connect: 데이터 센터와 연결
Snowball: 기존 저장된 벌크 데이터 로드
Database Migration Service: Oracle 등의 데이터 임포트

# 저장/처리
Glue: 데이터 카탈로그와 ETL
Amazon S3: 안전하고, 비용 효율적 스토리지

# 분석
Redshift: 데이터 웨어하우스
EMR: 비정형 데이터 처리, Apache Spark
Athena: ad-hoc, 서버리스 쿼리
QuickSight: 시각화, BI
SageMaker: 기계 학습 플랫폼



■ 세션 2. 글로벌 기업들의 효과적인 데이터 분석을 위한 Data Lake 구축 및 분석 사례


• 데이터 분석 트렌드
# Data = The world's most valuable resource
- 데이터의 양과 형식이 생각했던 것 이상으로 늘어나고 있다. 따라서 이를 받혀주는 데이터 분석 기술이 필요함.
(Hadoop, Elasticsearch, Presto, Spark, ...)
- 더 다양해진 데이터 소비자, 더 복잡해진 데이터 요구사항
(Secure, Realtime, Flexible, Scalable, ...)
- 레거시 환경의 Data silos 문제, 모든 데이터 소스를 한 번에 볼 수 있는 단일 데이터 뷰가 없다. (데이터 소스 별로 분리되어 있고, 이를 결합할 수 없는 환경.)
# Data Lake는 중앙 집중식 데이터 저장소, 다양한 스키마와 구조의 데이터를 대상으로 수집, 저장, 변환, 분석이 용이해야 한다. Single View로 접근하는 차세대 데이터 플랫폼. (Amazon S3 기반)
- Data Lake - Amazon S3 설계
 Tier-1 원본 데이터
 Tier-2 분석용 데이터
 Tier-3 특정한 목적을 갖는 데이터 (Optional, Use Case(또는 ML, AI)에 적합한 구성, 도메인 레벨로 데이터마트 분리)

• 전통적인 방식의 분석 시스템
- 관계형 DB에 저장된 정형 데이터
- 확장이 어려운 구조, 대규모 선비용 투자
- 분석의 복잡성, 비실시간성, 새로운 기술과의 접목 한계
-> Data Lake를 통해 전통적인 DW를 확장.
 다양한 유형의 정형/비정형 데이터 저장, 낮은 비용으로 저장과 분석이 가능.

• Amazon Redshift
여러 개의 컴퓨팅 노드의 병렬 실행으로 페타 바이트 급 데이터를 빠르게 분석할 수 있다고 함. 

• Data Lakes로 데이터를 이동
- AWS는 다양한 선택안을 제공하고 있다. Direct Connect (전용 네트워크 연결). Snowball로 엑사 바이트 급의 데이터를 이전. 등등.
- 또한 실시간 소스(모바일, IoT 기기)로 부터 발생하는 스트리밍 데이터를 저장할 수 있는 방법도 제공. (Kinesis, ...)
- 데이터 웨어하우징, 대화형 SQL 쿼리, 빅데이터 처리, 실시간 분석, 대시보드와 시각화, 기계학습등 다양한 요구에 부합하는 서비스가 이미 AWS에 존재.

• 데이터에 대한 도전 과제
- 혼란스러운 여러 버전의 데이터
- 데이터에 대한 제한된 가시성
- 데이터를 찾기위해 낭비되는 시간
- 누락된 통찰력에 의한 의사결정 저하
-> 통합 저장 -> 시각화 분석 -> 트렌드 분석 -> 클러스터 분석 -> 예측 분석.
# 최종 목표는 예측 분석 및 머신러닝.
# 다우 존스, 에픽 게임즈 사례.

• Data Lake 구축 단계
- S3 버킷 생성
- 데이터 수집
- 데이터 프로세싱 및 카탈로그화
- 보안 및 컴플라이언스 정책 설정
- 데이터 활용 및 분석
-> AWS Lake Formation을 이용하면 Data Lake를 신속하게 구축할 수 있다고 함.

... 뒤에 10여분 못들음;

2019년 8월 19일 월요일

Java 로거(jdk logger) 정리.

Java에선 파사드 패턴으로 구현된 slf4j를 쓰는게 성능이나 유지 보수 측면에서 이점이 있지만 경우에 따라선 jdk logger가 더 나은 선택일 수 있다. jdk logger를 쓸 경우 외부 라이브러리를 참조하지 않아도 되고, 설정도 단순해 사용하기 편하다. log4j 대비 느리다곤 하나 엄청 성능에 민감한 애플리케이션이 아닌 경우엔 차이가 잘 드러나지도 않는다. 그리고 jdk logger는 표준이면서 구조적인 측면에 대해서도 배울 부분이 있기 때문에 알아둘 필요는 있다고 생각한다.

jdk logger는 java.util.logging 패키지에 구현되어 있다. Logger.getLogger(String name) 메서드를 통해 로거 인스턴스를 가져오는 방식이고, 최소 로그 레벨과 핸들러를 지정하게 된다. jdk logger가 제공하는 로그 레벨은 다음과 같다. 이 중 severe, warning, info 정도의 레벨로 로그의 위험도를 구분하는 것이 일반적일 것이다.

• SEVERE: a serious failure
• WARNING: a potential problem
• INFO: reasonably significant informational message
• CONFIG: hardward configuration
• FINE, FINER, FINEST: tracing information

핸들러는 여러 개 지정할 수 있다. 일반적으로 ConsoleHandler와 FileHandler가 주로 사용된다.

• ConsoleHandler: for writing to System.err
• FileHandler: for writing to either a single log file, or a set of rotating log files
• StreamHandler: for writing to an OutputStream
• SocketHandler: for writing to a TCP port
• MemoryHandler: for writing to memory buffers.

로거 인스턴스에 getHandlers() 메서드를 호출하면 로거에 연결된 핸들러 정보를 배열로 받아올 수 있다. 핸들러 정보를 가져와 필요하지 않은 핸들러라면 제외시킬 수 있을 것이다.

// 파일 핸들러를 제거한다면,
Logger logger = Logger.getLogger(name);
Handler[] handlers = logger.getHandlers();

for (Handler h : handlers) {
    if (h instanceof FileHandler) {
        if (!doFileLog) {
            h.flush();
            h.close();
            logger.removeHandler(h);
        }
    }
}

또는 필요한 핸들러는 언제든지 추가할 수 있다. 파일 핸들러는 rotating을 지원하는데 로그 파일의 용량과 최대 개수를 생성자에 지정할 수 있다.

FileHandler fileHandler = new FileHandler(logFileName, ROTATE_SIZE, ROTATE_COUNT, true);
logger.addHandler(fileHandler);

파일 핸들러와 같은 것들은 애플리케이션이 종료될 때 같이 닫혀야 한다. Runtime의 addShutdownHook에 파일 핸들러를 닫아주는 쓰레드를 지정해주면 될 것이다.

final class FileHandlerCloser implements Runnable {
    private final FileHandler fileHandler;

    private FileHandlerClose(FileHandler fileHandler) {
        this.fileHandler = fileHandler;
    }

    public static void addHandler(FileHandler fileHandler) {
        Thread closer = new Thread(new FileHandlerCloser(fileHandler), fileHandler.toString());
        Runtime.getRuntime().addShutdownHook(closer);
    }

    public void run() {
        fileHandler.flush();
        fileHandler.close();
    }
}

마지막으로 각 핸들러에겐 기록할 로그 형식을 지정해주어야 한다. 추상 클래스 Formatter를 구현하면 된다. 구현할 메서드는 하나다.

public String format(LogRecord record);

인자로 전달되는 record 객체로부터 로그를 남긴 클래스 이름과 메서드 이름을 알 수 있다. 라인 번호까지 알고 싶으면 스택 트레이스에서 정보를 빼오면 된다. 그리고나서 record에 담긴 정보를 문자열로 내보내면 될 것이다.

int lineNumber = 0;
StackTraceElement[] stackTraceElement = Thread.currentThread().getStackTrace();
for (int i = 0; i < stackTraceElement.length; i++) {
    if (stackTraceElement[i].getClassName().compareTo(record.getSourceClassName()) == 0
            && stackTraceElement[i].getMethodName().compareTo(record.getSourceMethodName()) == 0) {
            lineNumber = stackTraceElement[i].getLineNumber();
            break;
    }
}

// [Time] [Level] [ClassName:MethodName:LineNumber] [Message]
StringBuilder builder = new StringBuilder(1000);
builder.append(new SimpleDateFormat("[yyyy/MM/dd/HH:mm:ss]").format(new Date(record.getMillis())));
builder.append(" [").append(record.getLevel()).append("]\t");
builder.append("[").append(record.getSourceClassName()).append(":");
builder.append(record.getSourceMethodName()).append(":");
builder.append(lineNumber + "]\t[");
builder.append(formatMessage(record)).append("]\r\n");

return builder.toString();

jdk logger의 전체 구조는 아래 이미지와 같다. 근사하다.


이미지 출처는



2019년 8월 14일 수요일

리버스 프록시 (Reverse Proxy)

리버스 프록시는 사용자의 요청을 받아서 반대편(reverse) 네트워크에 있는 인터넷 서버에 요청을 전달하는 일을 한다. 리버스 프록시 서버는 단순히 요청을 전달하기만 할 뿐 실제 요청의 처리는 뒷단에 위치한 웹 서버들이 맡아서 하게 된다.

하나의 리버스 프록시 서버가 여러 웹 서버로 요청을 전달하도록 구성할수도 있는데 이 경우 소프트웨어 기반의 로드밸런싱 환경을 구축할 수 있다. HAProxy, NginX, Apache 등의 웹 서버에서 이러한 기능을 제공한다.

최근 서비스의 규모가 커지면서, 분산 시스템으로 서비스가 구성되는 경우가 많은데 프록시 서버를 이용하면 분산 시스템을 뒤에 숨기는 방식으로 시스템을 단순화 할 수 있다.

NAT 사용하는 이유

NAT(Network Address Translation)

NAT은 IP 패킷의 TCP/UDP 소스 및 목적지의 IP 주소와 포트 숫자 등을 재기록하면서 라우터를 통해 네트워크 트래픽을 주고 받는 기술이다. NAT은 IPv4의 주소 부족 문제를 해결해주는데 사설 네트워크에 속한 여러 개의 호스트가 하나의 공인 IP 주소를 사용해 인터넷에 접속할 수 있게 해준다.

NAT은 주로 공인되지 않은 네트워크 주소를 사용하는 망에서 외부의 공인망(예를 들면 인터넷)과의 통신을 위해 네트워크 주소를 변환해 준다. 별도로 공공망과 연결되는 사용자들의 고유한 사설망을 침입자들로부터 보호할 수 있다는 점에서 또다른 이점이 있다.

인터넷망과 연결되는 장비인 라우터에 NAT을 설정하면 라우터는 자신에게 할당된 공인 IP 주소만 외부로 알려지게 하고, 내부 사설 IP 주소는 감출 수 있다. (필요시에만 서로 변환) 외부 침입자는 사설망의 내부 IP 주소를 알아야 공격이 가능한데 불가능해지므로 내부 네트워크를 보호할 수 있게 된다.

2019년 8월 12일 월요일

Java Application 메모리 사용량 구하기, totalMemory(), freeMemory(), maxMemory()

모든 Java 애플리케이션에는 실행되는 환경과 인터페이스 할 수 있는 단일(single) Runtime 인스턴스가 제공된다. Runtime 인스턴스는 사용자가 직접 만들 수는 없고 getRuntime() 메서드에 의해서만 얻을 수 있다. 그리고 Runtime 인스턴스를 이용해 현재 애플리케이션의 메모리 사용 현황을 확인할 수 있다.

• Runtime.getRuntime().maxMemory() : 애플리케이션이 사용할 수 있는 총 메모리 양, -Xmx 값과 같다. 어떤 Java 애플리케이션이든 이 값을 초과하는 메모리는 사용할 수 없다. 실제 사용하는 메모리가 이 값에 근접하면 GC가 자주 발생하게 되므로 성능에 안좋은 영향을 끼치게 된다.
• Runtime.getRuntime().totalMemory() : 현재 애플리케이션에 할당된 총 메모리 양. (새로운 객체에 쓰일 메모리 영역 포함.)
• Runtime.getRuntime().freeMemory() : 새로 생성될 객체를 위해 현재 사용 가능한 메모리 공간. (available size in total memory.)

위 세가지 메서드로 실제 사용 중인 메모리, 앞으로 사용 가능한 메모리 등을 확인할 수 있다.

• 실제 사용 중인 메모리 (used memory) : totalMemory - freeMemory
• 할당되지 않았으나 앞으로 사용할 수 있는 메모리 (unallocated memory) : maxMemory - totalMemory
• 앞으로 사용 가능한 메모리 (total free memory) : maxMemory - totalMemory + freeMemory


https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html

2019년 8월 6일 화요일

getResource() vs getClassLoader().getResource()

getResource(), getResourceAsStream()는 지정된 이름의 리소스를 찾는 API다. 주어진 클래스와 관련된 리소스를 검색하는 규칙은 클래스의 클래스 로더 구현을 따른다. 만약 객체가 부트스트랩 클래스 로더에 의해 로드되었다면 이 메서드는 작업을 ClassLoader.getsystemResourceAsStream(java.lang.String)으로 위임시킨다.


getClass().getResource() vs getClass().getClassLoader().getResource()

getClass().getResource()는 호출한 클래스 패키지로 기준한 상대적인(relative) 리소스 경로로 리소스를 탐색하게 되고, getClass().getClassLoader().getResource()는 항상 절대적인(absolute) 리소스 경로로 리소스를 탐색하게 된다. 예외 규칙으로 getClass().getResource()로 전달하는 리소스 이름이 '/'로 시작하면 getClassLoader() 방식과 같이 절대 리소스 경로로 처리된다.

아래의 결과는 같다.
foo.bar.Baz.class.getResource("xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("foo/bar/xyz.txt");
아래도 같다. getClass().getResource()로 전달된 리소스 이름이 '/'로 시작하기 때문에 절대 리소스 경로로 처리된다.
foo.bar.Baz.class.getResource("/data/xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("data/xyz.txt");
[원문] https://stackoverflow.com/questions/6608795/what-is-the-difference-between-class-getresource-and-classloader-getresource


getResource() vs getResourceAsStream()

내부 동작 방식은 같은데 getResource()는 URL 객체를 반환하고, getResourceAsStream()은 InputStream 객체를 반환한다.


new FileInputStream 등으로 파일을 직접 읽지 않고 getResource(), getResourceAsStream()을 사용하는 이유

getResource(), getResourceAsStream()을 쓰면 jar로 패키징된 내부 파일을 직접 읽을 수 있다.


[참고] 자바 클래스 로더 (Java Class Loader)
https://blog.naver.com/seokcrew/221608650295

2019년 8월 5일 월요일

Java - 문자열 풀링(String Pooling, String.intern())

String Pooling은 메모리를 아끼는 작업으로써 완전히 같은 값을 가진 문자열이 서로 다른 메모리 주소에 저장되지 않도록 해준다. 이는 Java에서 String이 불변(immutable) 객체이기 때문에 가능한 장치다. JDK는 String.intern() 메서드를 통해 이러한 기능을 제공한다. Java에서 생성자가 아닌 리터럴(" ") 형식으로 선언된 String 객체는 알아서(?) Pooling 되는데 아마 String.intern()이 내부적으로 호출되는 구조를 갖을 것이다.

Java 6 버전에서는 String.intern()을 권장하지 않았다. String Pool이 PermGen 메모리 영역에 저장되었기 때문인데, PermGen 영역은 GC 대상이 아닐 뿐더러 고정된 크기(기본값 32~96MB)를 갖고 실행된다. String Pool이 커지면 OutOfMemory 에러가 발생할 수 있는 것이다. 충분히 큰 사이즈를 초기에 지정해 사용하는 방법이 있었지만 (-XX:MaxPermSize=256m) 신중한 사용을 필요로 했다.

Java 7 버전부터 String Pool은 힙 영역에 저장된다. 참조되지 않는 문자열은 GC에 의해 회수되기 때문에 메모리를 보다 효율적으로 사용할 수 있게 되었다. String Pool은 고정된 크기의 해시 맵으로 구현되는데 -XX:StringTableSize=N 설정으로 맵의 크기를 변경할 수 있다. 참고로 Java 7u40 부턴 기본 60013의 크기로 동작한다. 왜 60000이 아니고 60013이냐, 맵의 크기는 소수(prime number)로 지정하는 것이 성능에 도움이 된다고 한다.

통계 기본 용어 (표준 편차, 분산, 표준 점수, ...)


  • 표준 편차(standard deviation) : 자료의 산포도를 나타내는 수치로, 분산을 제곱근한 것. 표준편차가 작을수록 평균값에서 변량들의 거리가 가깝다. 모집단의 표준편차는 시그마로, 표본의 표준편차는 S로 나타낸다. 제곱해서 값이 부풀려진 분산을 제곱근해서 다시 원래 크기로 만들어 준다.
  • 68-95-99.7 규칙 : 통계학에서 정규 분포를 나타내는 규칙으로 3시그마 규칙이라고도 한다. 경험적인 값으로 평균에서 양쪽으로 3표준편차의 범위에 거의 모든 값(99.7%)이 들어가는 것을 나타낸다. 평균에서 양쪽으로 1표준편차 범위 내에 약 68% 값이, 2표준편차 범위 내에 약 95% 값이, 3표준편차 범위 내에 약 99.7% 내에 존재한다.
  • 정규 분포(normal distribution) : 수집된 자료의 분포를 근사하는데 쓰인다. 정규 분포는 평균과 표준편차를 기준으로 모양이 결정되는데 평균이 0이고 표준편차가 1인 정규분포를 표준 정규 분포라고 한다. 정규분포곡선은 좌우가 대칭이며 하나의 꼭지를 갖는다.
  • 편차(deviation) : 관측값에서 평균 또는 중앙값을 뺀 것.
  • 분산(variance) : 관측값에서 평균을 뺀 값을 제곱하고, 그것을 모두 더한 후 전체 개수로 나눠 구한 값. 차이값의 제곱의 평균이다. 편차를 모두 더하면 0이 나오기 때문에 제곱해서 더한다.
  • 표준 점수(standard score, z-value, z score) : 데이터가 표준편차 상에 어떤 위치(평균으로부터 얼마나 떨어져 있는지)를 차지하는지를 나타내는 수치. z score가 0이면 정확히 평균에 해당한다. (number - average) / standard_deviation)
[출처: 위키백과]

WPF 쓰레딩 모델, Invoke, BeginInvoke

일반적으로 WPF 응용 프로그램은 렌더링 처리와 UI 관리를 위한 두개의 쓰레드로 시작한다. UI 쓰레드는 작업 항목을 Dispatcher라는 오브젝트 내부에 대기시킨다. Dispatcher는 우선 순위에 따라 작업 항목을 선택하고 각각을 실행한다. 모든 UI 쓰레드에는 하나 이상의 Dispatcher가 있어야 하며 각 Dispatcher는 정확히 하나의 쓰레드에 의해서만 동작해야 한다.

응답성이 높은 애플리케이션을 구축하는 방법은 가능한 작업 항목의 실행 시간을 적게 유지하여 Dispatcher의 처리량(throughput)을 극대화 하는 것이다. 따라서 일반적으로 큰 작업은 별도의 쓰레드에서 처리하게 된다. 그리고 큰 작업이 완료되면 결과를 UI 쓰레드에 보고하여 표시하는 식이다.

WPF에서 대부분의 클래스는 DispatcherObject에서 파생된다. 생성시 DispatcherObject는 현재 실행 중인 쓰레드에 연결된 Dispatcher에 대한 참조를 저장한다. 프로그램을 실행하는 동안 DispatcherObject는 VerifyAccess 메서드를 호출할 수 있다. VerifyAccess 메서드는 현재 쓰레드와 연관된 Dispatcher와 생성시에 저장된 Dispatcher 참조 간의 비교 검사를 수행한다. 일치하지 않으면 VerifyAccess는 예외를 throw한다. VerifyAccess는 DispatcherObject에 속한 모든 메서드의 시작 부분에서 호출되도록 고안되었다.

...
하나의 쓰레드만 UI를 변경할 수 있다고 하였다. 그렇다면 백그라운드 쓰레드와 사용자 간 상호작용 하는 방법은 무엇인가? WPF에서 백그라운드 쓰레드는 UI 쓰레드에 작업을 수행하도록 요청할 수 있게 구성되어 있다. 백그라운드 쓰레드는 작업 항목을 UI 쓰레드의 Dispatcher에 등록할 수 있다. Dispatcher 클래스는 백그라운드 쓰레드가 작업 항목을 등록할 수 있게 'Invoke'와 'BeginInvoke' 메서드를 제공한다. Invoke는 동기 호출 방식이고 BeginInvoke는 비동기 호출 방식이다. 두 메서드 모두 실행을 위해 대리자를 스케줄에 등록시킨다.

아래와 같은 코드에서, Dispatcher는 startStopButton 컨트롤이 idle일 때만 delegate를 실행하게 된다. 우선순위가 SystemIdle로 지정되었으니 아래의 코드는 CheckNextNumber 호출보다 UI의 응답이 중요하다는 의미를 갖게 된다. Dispatcher는 Dispatcher 큐에 등록된 작업 항목을 우선 순위에 따라 정렬하여 순차적으로 처리한다. 제공되는 우선 순위 레벨은 10가지 이다. 이러한 우선 순위는 DispatcherPriority 열거 형에서 유지되고 관리된다.


public delegate void NextPrimeDelegate();

public void CheckNextNumber()
{
...
    if (continueCalculating)
    {
        startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, new NextPrimeDelegate(this.CheckNextNumber));
    }
}

자세한 내용은 아래 링크를.
https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model

2019년 8월 1일 목요일

싸이킷런의 주요 설계 원칙 - estimator, transformer, predictor

핸즈온 머신러닝을 읽고 있는데 이 책 정말 좋다. 싸이킷런의 주요 설계 원칙이라고 101쪽에 적혀있는 내용을 옮겨본다.


싸이킷런의 주요 설계 원칙


1. 일관성 : 모든 객체가 일관되고 단순한 인터페이스를 공유한다.

  • 추정기(estimator) : 추정은 fit() 메서드에 의해 수행되고 하나의 데이터셋만 파라미터로 전달받는다. fit()에서 쓰이는 하이퍼파라미터는 생성자의 매개변수로 전달받는다.
  • 변환기(transformer) : transform() 메서드가 하나의 데이터셋을 전달받아 변환 작업을 한다. 그리고 fit()과 transform()을 연달아 호출하는 것과 동일한 결과를 갖는 fit_transform() 메서드를 가지고 있다.
  • 예측기(predictor) : predict()는 새로운 데이터셋을 전달받아 예측값을 반환한다. 그리고 테스트 세트를 사용해 예측 품질을 측정하는 score() 메서드를 갖는다.


2. 검사 기능 : 하이퍼 파라미터는 인스턴스 변수로 직접 접근할 수 있다. 학습된 모델 파라미터도 접미사로 '_'를 붙여서 공개 인스턴스 변수로 제공된다.

3. 조합성 : 기존 구성요소를 최대한 재사용한다. 예를 들어 여러 개의 변환기를 연결한 다음 마지막 추정기 하나를 배치한 Pipeline 추정기를 쉽게 만들 수 있다.

4. 합리적인 기본값 : 일단 동작하게끔 해준다. 대부분의 매개변수에 합리적인 기본값을 지정해두었다.

2019년 7월 29일 월요일

DI와 스프링

DI는 외부 인스턴스 혹은 특정 container가 두 object 간의 종속성을 (대신) 주입해주어 서로에 대한 종속성을 낮추게 해준다. DI를 사용함으로써 어떤 클래스가 특정 클래스에 의존성을 갖을 때 구체적인 구현에 대해 모르게 할 수 있다. (구현에 대해 갖는 종속성을 제거.) 이렇게 하면 모듈화에도 좋고, DI를 적용하지 않았을 때보다 독립적인 단위 테스트를 하기 쉬워진다. 독립적인 단위 테스트를 하기 쉬워진다는 말은 결합도(coupling)가 낮아지는 것을 의미한다. 알다시피 낮은 결합도(=모듈의 높은 응집도)는 좋은 소프트웨어 설계의 목표 지점 중 하나다.

DI가 구현에 대한 의존성을 제거하는 유일한 방법은 아니다. 이 작업을 수행하는 데 사용할 수 있는 또 다른 패턴으로 Service Locator가 있다. Service Locator 패턴을 따르는 경우엔 애플리케이션이 service locator에게 필요한 서비스를 메시지로 직접 요청하는 형태를 띄게 된다. 반면 DI의 경우엔 명시적인 요청 없이 서비스가 주입된다. 이런 형태를 일반적으로 'Inversion of Control' 이라 한다.

아래는 마틴 파울러가 15년전 작성한 글인데 정말 좋다.
https://martinfowler.com/articles/injection.html

DI 구현방식

DI엔 constructor injection, setter injection, field injection의 세가지 방식이 있다. 그리고 이 중에선 일반적으로 constructor injection 방식이 가장 권장된다. Constructor injection 방식을 쓰면 생성자만 보고도 의존 관계와 복잡성을 쉽게 알 수 있게 되기 때문에 작성하는 코드가 올바른 방향으로 가고 있는지 알 수 있게 된다. 그리고 final 키워드를 이용해 객체의 상태를 변하지 않게 만들 수 있다. 이는 setter, field injection 방식에서는 가질 수 없는 형태다.
일반적으로 객체를 생성할 땐 가능하면 하나의 방식을 갖는 것이 좋고, 의존 관계가 있다면 setter method 보다 매개 변수가 있는 생성자를 통해 드러내는 것이 좋다고 알려져 있다. (객체를 구성하는 방법이 여러개라면 Factory Methods를 쓰고..) 이와 비슷한 맥락에서 DI도 constructor injection 방식을 권장하는게 아닌가 한다.

반대로 setter injection의 경우엔 특정 의존 관계를 인스턴스의 생성 시점과 구분해서 지정하거나 또는 지정하지 않거나, 교체할 수 있는 형식을 가질 수 있기 때문에 setter injection 방식도 쓰임새가 있다.

[코드 예시: https://multifrontgarden.tistory.com/214]


DI와 스프링

잘 알려진 DI 구현체를 꼽으면 스프링을 예로 들 수 있다. 스프링에선 스프링 DI 컨테이너가 관리하는 객체를 빈(bean)이라고 표현한다. 여기서 빈이 특별한 개발 사양을 갖는 것은 아니고 빈은 단지 스프링 컨테이너에 의해 관리를 받는다는 의미로 쓰인다. 그리고 스프링 컨테이너는 이 빈들을 관리한다는 의미로 빈 팩토리(bean factory)라 불린다. 빈 팩토리는 단어 그대로(factory=공장) 빈을 생성(instantiating)하고, 설정(configuring)하고, 관계를 갖는 빈들을 묶어주는(assembling) 역할을 맡는다. 여기에 비즈니스 애플리케이션을 지원하는데 필요한 기능 몇가지가 추가되면 바로 그(?) ApplicationContext가 된다.

TCP 제어 플래그 (Control Flag)

TCP 통신에서 TCP 헤더의 컨트롤 플래그는 연결 상태를 나타내는데 쓰인다. 이 값에 대해 잘 알고 있으면 관련된 문제 발생시 꽤나 도움이 된다. 플래그에는 SYN, ACK, FIN, RST, PSH, URG 가 있다. 각 플래그는 1비트씩 소비한다.



  • Synchronization (SYN) : 두 호스트 간 연결을 맺기 위한 3-way handshake 프로세스의 첫 번째 단계에서 쓰인다.
  • Acknowledgement (ACK) : 상대방으로부터 패킷을 받았다는 것을 알려주는데 쓰인다. SYN 세그먼트 전송 이후(TCP 연결 시작 후) 모든 세그먼트에는 항상 이 비트가 1로 셋팅된다. 1로 셋팅되면 'Acknowledgement number' 필드에 유효한 값이 적혀있다는 의미를 갖는다.
  • Finish (FIN) : 커넥션 종료 요청으로 더 이상 보낼 데이터가 없을 시에 sender가 마지막 패킷으로 보내게 된다. 정상적인 종료에 해당하므로 데이터 유실은 없고, receiver가 꼭 연결을 끊어야 할 의무는 없다. 연결을 종료하고 싶다는 뜻을 상대에게 보내기 위한 플래그다.
  • Reset (RST) : TCP 연결에 문제가 있다고 판단될 때 연결을 종료하는데 쓰인다. 정상적인 연결과 종료를 할 수 없는 경우에도 사용된다. 비정상적인 중단으로 데이터 유실이 생길 수 있고, 양쪽 모두에 영향을 준다. RST는 반-개방 또는 연결 문제 등의 상황 처리를 위한 초기화용 제어 비트다.
  • PUSH (PSH) : 전송 계층은 일반적으로 네트워크의 효율을 위해 충분한 양의 데이터가 모인 다음에 데이터를 전달한다. 그러나 이러한 장치는 채팅과 같이 빠른 응답을 필요로 하는 경우엔 적합하지 않을 수 있다. PSH 플래그를 1로 설정하면 세그먼트는 버퍼링되지 않고 즉각 전송된다. 수신측도 PSH 플래그가 1로 설정된 세그먼트는 바로바로 애플리케이션 계층으로 전달한다. (in-sequence)
  • Urgent (URG) : 긴급성을 갖는 데이터로 간주되어 URG가 1로 세팅된 세그먼트는 전송 큐의 제일 앞으로 보내지게 된다. (out-of-sequence)
https://www.geeksforgeeks.org/computer-network-tcp-flags/

2019년 7월 28일 일요일

JMX란

JMX = Java Management Extensions.

JMX는 JDK 1.5부터 포함된 사양이라 한다. JMX는 실행 중인 애플리케이션의 상태를 모니터링 하고, 설정을 변경할 수 있게 해주는 API라고 이해하면 될 듯 하다. 이해하기 쉬운 단순한 규격을 가지고 있어서 어렵지 않게 런타임 상태의 애플리케이션을 관리할 수 있다.

외부에선 JMX Connectors로 MBean 서버에 접근하면 되지만 JConsole과 같은 미리 구현된 모니터링 도구가 있기 때문에 직접 모든 것을 만들 필요는 없다.

JMX를 통해 리소스 관리를 하려면 MBeans라는 Managed Beans를 생성해야 하고, 생성한 MBean을 MBean Server에 등록해야 한다. MBean 서버는 등록된 MBeans를 관리하는 agent 역할을 수행하게 된다. 당연히 MBean 서버는 구현한 애플리케이션 내부에서 띄운다.

MBean을 구현하는덴 룰이 있는데 interface와 구현체를 쌍으로 만들어야 하고 MBean interface의 이름은 'MBean'으로 끝나야 한다. 노출하는 변수를 모니터링만 할 것이라면 setter는 생략해도 된다.

다음은 예제 코드.
public interface MonitoringBean {
    long getStorageSize();
}

public class Monitoring implements MonitoringBean {
    @Override
    public long getStorageCount() {
        return ...;
    }
}

// boot up MBean server
Monitoring monitoring = new Monitoring();

// 팩토리 클래스로 부터 MBeanServer 인스턴스를 생성한다.
MBeanServer server = ManagementFactory.getPlatformMBeanServer();

// ObjectName은 domain, key의 구성을 갖는다.
// domain은 이름 충돌이 발생하지 않도록 자바 패키지 이름을 쓰는 것이 관례.
// key는 key=value 쌍을 콤마로 구분해 여러 개를 지정할 수 있다.
ObjectName jmxObjectName = new ObjectName("com.okminseok.example:type=basic,name=mymon");
server.registerMBean(monitoring, jmxObjectName);

! 소스 참고: https://github.com/eugenp/tutorials/blob/master/core-java-modules/core-java-perf/src/main/java/com/baeldung/jmx/JMXTutorialMainlauncher.java

! 애플리케이션의 실행 파라미터.
-Dcom.sun.management.jmxremote.port = 8686 // 8686이 기본 포트.
-Dcom.sun.management.jmxremote.ssl = false // 설정 편의를 위한 ssl off
-Dcom.sun.management.jmxremote.authenticate = false // 설정 편의를 위한 인증 off
=> https://www.ibm.com/support/knowledgecenter/ko/SSHS8R_8.0.0/com.ibm.worklight.installconfig.doc/install_config/t_optional_config_app_server_tomcat.html


2019년 7월 25일 목요일

npm 기본 사용법

Node Package Manager, npm의 사용법을 간단히 정리해보았다.

자주 쓰게되는 명령들


# npm init: package.json 파일을 만들어 준다. package.json은 프로젝트가 참조하는 의존 라이브러리의 버전 정보를 기록해두는 파일이다. JavaScript 생태계에서 Java의 maven, gradle과 같은 역할을 하는 파일이다.
npm init

# npm install/uninstall: 패키지 설치와 삭제. -g 옵션을 붙이면 사용하는 PC 환경 전체에 영향을 준다. 붙이지 않으면 명령을 날린 하위 폴더에 적용된다.
npm install 모듈
npm uninstall 모듈

# 배포 시 참조하게 되는 라이브러리 정보를 package.json에 추가한다. (dependencies에 추가)
npm install 모듈 --save

# 개발 단계에서만 참조하는 라이브러리 정보를 package.json에 추가한다. (devdependencies에 추가)
npm install 모듈 --save-dev

# npm이 npm을 업데이트 해준다.
npm install -g npm

# npm ls: 설치된 패키지 정보를 확인할 수 있다. -depth=0 옵션을 주면 참조의 참조 패키지는 제외하고 표시해준다. -g 옵션은 이전과 동일한 의미.
npm ls
npm ls -g
npm ls -depth=0

# npm cache: clean 명령은 캐쉬 폴더를 비운다. verify는 캐쉬 폴더에서 필요하지 않은 정보를 날리거나 캐쉬 데이터가 올바른지 확인하는 명령이다.
npm cache clean [<path>]
npm cache verify

! package-lock.json


npm 특정 버전부터 package-lock.json이라는 파일이 추가로 생성된다. package-lock.json 파일은 pakcage.json 파일이 업데이트 되는 시점에 참조하는 모듈들의 자세한 의존성 트리 정보(참조의 참조, 참조의 참조의 참조~ 모듈의 버전 정보)를 기록해두기 위한 장치다. package.json 보다 더 정확한 정보를 담고 있기 때문에 특정 개발자와 협업하는 경우 미세한 버전 틀어짐 없이 정확히 같은 버전을 바라보며 개발을 할 수 있다.


! 버전 표기시 ^(캐럿)과 ~(틸드).


캐럿은 참조 모듈들이 메이저, 마이너, 패치의 SemVer 규약을 잘 따르고 있다고 가정하고 모듈의 버전을 하위 호환성이 보장되는 버전까지 올릴 수 있게 해준다. 반면에 틸드는 참조 모듈의 버전 업그레이드에 대해 비교적 보수적인 version range를 두고 접근한다. outsider님 블로그에 올라온 글을 읽으면 이해가 빠르다. (https://blog.outsider.ne.kr/1041)
~(틸드)
~0.0.1 : >=0.0.1 <0.1.0
~0.1.1 : >=0.1.1 <0.2.0
~0.1 : >=0.1.0 <0.2.0
~0 : >=0.0 <1.0
^(캐럿)
^1.0.2 : >=1.0.2 <2.0
^1.0 : >=1.0.0 <2.0
^1 : >=1.0.0 <2.0

2019년 7월 24일 수요일

Angular 히스토리

Angular History


상세

* 2010년: AngularJS의 탄생. Misko Hevery라는 사람이 Google에서 Side Project로 시작. 이후 몇몇의 동료 개발자가 참여하여 오픈 소스로 공개하였다. 하나의 코드 셋으로 웹과 모바일 환경에 바로 적용할 수 있었기 때문에 많은 호응을 받았다.

* 2014-2015년: 다시 쓰다. AngularJS가 호평을 받았지만 웹 환경은 변화했고, 더 나은 성능을 보여주는 경쟁 프레임워크가 등장하게 되었다. AngularJS는 벽에 부딪혔고, Google의 핵심 팀은 기존 AngularJS의 디자인에 얽매이지 않고 프레임워크를 다시 작성하게 되었다. 새로 만드는 Angular의 목표는 여전히 대규모, 크로스 플랫폼 애플리케이션을 구축할 때 생기는 어려운 문제점들을 해결하기 위한 것이다.
  • AngularJS vs Angular
  1. AngularJS는 기본적으로 양방향 데이터 바인딩(2-way data binding)을 채택하고 있는데 여기서 오는 성능 문제가 있었다. → Angular는 단방향 데이터 바인딩, 양방향 데이터 바인딩을 개발자가 선택할 수 있다.
  2. AngularJS의 컨트롤러, 스코프($scope) 개념은 컴포넌트와 Directive로 대체되었다.
  3. Angular는 TypeScript를 채택한다. RxJS 사용시 리액티브 프로그래밍을 지원한다.

* 2016년: 혼란스러운 시기. AngularJS 프로그램을 새로운 Angular로 이식하는 명확한 마이그레이션 전략은 없다. AngularJS 기반 프로젝트를 운영하는 개발자에겐 안좋은 소식이었다. → 우려와 다르게 AngularJS는 지금도 지원이 이루어지고 있다.

* 2017-2018년: 새로운 버전의 Angular가 자리를 잡은 시기. 기존의 AngularJS와 비교해서 더 나은 빌드 크기, 안정적인 API와 향상된 성능을 제공할 수 있게 되었다. React와 다르게 Angular는 대규모 응용 프로그램을 빌드할 때 필요한 모든 솔루션을 제공한다. React와 같은 라이브러리는 누락된 부분을 채우기 위해 다른 외부 프로젝트를 필요로 한다. Angular가 React보다 낫다는 말은 아니다.

[Django] Cookie

오랜만에 장고..

Django에서 조건에 따라 컨텐츠를 일부 바꿔야 하는 일이 생겼다. 그리고 조건에 의해 한번 정해진 값은 애플리케이션을 이용하는 동안 유지될 필요가 있었다. (참고로 사내에서만 사용하는 프로그램이라 동작이 우선인 상황.)

고민하지 않고 쿠키에 값을 실어 처리하기로 했다. 찾아보니 HttpRequest에는 딕셔너리 타입으로 COOKIES 속성이 제공되고, HttpResponse에는 set_cookie 메서드가 제공된다.

HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False, samesite=None)
 [link: https://docs.djangoproject.com/en/2.2/ref/request-response/]


쿠키를 세팅하는 방법은 그리 어렵지 않다. 호출되는 view 함수에서 render의 반환 값을 바로 리턴하지 않고 값을 지정해주면 된다. 그리고 페이지에 처음 진입할 때에도 화면을 올바르게 렌더링 해야하기 때문에 render()를 호출하기 전에 request 객체에 쿠키 값을 지정해 주었다.
request.COOKIES['app_type'] = 'v1'
response = render(request, 'template.html', context)
response.set_cookie(key='app_type', value='v1')
return response

템플릿에선 request의 COOKIES 값을 읽어서 처리하면 된다.
{% if request.COOKIES.app_type and request.COOKIES.app == 'v1' %}
...

+ 마지막으로 나의 경우 쿠키 값을 읽어서 로그인, 로그아웃 이후의 리다이렉트를 고려해야 했는데 그냥 settings.py의 LOGIN_REDIRECT_URL에 별도의 view 함수를 정해주고, 해당 view 함수에서 쿠키 값을 읽어 분기되도록 하였다.

2019년 7월 17일 수요일

WPF의 DIU(Device-Independent Units)

WPF는 DIU(Device-Independent Units) 좌표 단위를 사용한다. DIU의 값은 1/96 Inch이고 WPF는 항상 1/96 Inch에 해당하는 값을 좌표 단위로 사용한다. 따라서 Width와 Height 값이 96인 WPF Rectangle은 프린터로 출력했을 때 가로 세로가 1 Inch 크기의 사각형으로 출력된다.

재미있는건 Microsoft Windows에서도 특별한 설정을 하지 않으면 기본 DPI 값은 96이라고 한다. DPI는 Dots per Inch의 약자로 1 Inch 당 점이 몇개 들어가지는지를 나타내는 단위이다. 기본 설정인 경우 WPF의 DIU 단위와 Windows의 DPI 단위가 같아서 1DIU = 1pixel 관계가 된다. 물론 Windows에서 DPI 설정을 바꿀 수 있다.

요약해 정리하면,
1. WPF는 1/96 Inch에 해당하는 값을 좌표 단위로 사용한다. 이를 장치 독립적 픽셀 값이라고 표현한다.
2. WPF의 DIU 값과 Windows의 기본 DPI 값이 같기 때문에 1 DIU는 1 pixel로 표시된다. 단, Windows의 DPI 설정이 바뀌면 1 DIU는 1보다 작거나 큰 pixel 값을 갖게 되어 화면이 번져 보이게 된다.

http://www.charlespetzold.com/blog/2005/11/250723.html
In WPF, you always draw in units of 96 DPI. For example, if you want to create a one-inch square Rectangle object, you make it 96 units wide and 96 units high. If the program runs on a video display set at 96 DPI, the object will be drawn 96 pixels square. In the most common case device-independent units map directly to pixels. If the program runs on a video display set at 120 DPI, the object will be drawn as 120 pixels square. That's a fairly clean 3 units-to-4 pixels mapping.

2019년 7월 14일 일요일

스트리트 패션 다큐멘터리 [PERCENTAGE%]


https://youtu.be/YFhuMiYS72E

돈주고 봐야할 수준의 다큐멘터리가 유투브에 아무론 조건 없이 공개됨.

김종선(JAYASS)님을 중심으로 하여 지난 10년의 한국 스트리트 패션 문화에 대해 다룬다.
사실 BA나 휴먼트리에 대해선 잘 모르지만 어렸을 적 이런 문화와 문화를 이끄는 인물에 대해선 막연한 동경심이 있었다.
밤 늦게 시청했음에도 한 번에 클리어.

2019년 7월 11일 목요일

대칭키 암호화, 비대칭키 암호화

암호화

1. 대칭키 암호화
→ 암호화하고 복호화 하는데 같은 키를 사용하는 방식.

대칭키 암호화 기법은 2개의 키를 사용하는 비대칭키 암호화 기법에 비해 간단한 반면 A와 B가 메시지를 주고 받기 전에 동일한 내용의 대칭키를 미리 알고 있어야 하는 문제가 있다. 이 대칭키는 쉽게 바꿀 수도 없고 사전에 공유하기 어려운 성질을 갖는다. A와 B가 대칭키 암호화 방식으로 데이터를 주고 받기 결정했다면 A 또는 B 중 한사람은 암호키를 만들어서 상대방에게 전달해야 한다. 그런데 전달 과정에서 누군가 이 암호키를 가로챈다면 그 누군가는 언제든 암호화된 메시지를 복호화 해서 내용을 확인할 수 있게 된다.


2. 비대칭키 암호화
→ 암호화하고 복호화 하는데 공개키/비밀키의 한 쌍을 사용하는 방식.

대칭키 암호화 기법에서 대칭키 교환의 문제를 해결하기 위해 고안된 기법이다. 비대칭키 암호화 기법은 공개키와 비밀키 한쌍의 암호화 키를 이용해 암복호화를 수행한다. 무슨 얘기인가 하면 공개키로 암호화된 메시지는 Pairing된 비밀키로만 복호화할 수 있고, 비밀키로 암호화된 메시지 또한 Pairing된 공개키로만 복호화할 수 있다는 말이다.
비대칭키 암호화 방식의 단점은 CPU 리소스를 대칭키 암호화 방식에 비해 많이 쓴다는 점이다. 따라서 일반적으론 비대칭키 암호화 방식을 이용해 대칭키를 서로 공유한 뒤에 이후의 암호화는 대칭키 방식을 사용한다.


3. 웹 암호화
SSL/TLS 기반의 웹 사이트에 접근하게될 때, 웹 서버는 공개키가 포함된 인증서를 사용자에게 보내주어 이 공개키를 이용해 암호화 하면 된다고 알려준다.  앞선 예와 유사한 이유로 수신한 공개키의 진위 여부를 가릴 필요가 있다.

만약 해커가 인증서와 인증서에 포함된 공개키를 조작해 전달한 것이라면 사용자의 암호화된 패킷을 자유롭게 열어볼 수 있을 것이다. SSL/TLS 기반의 통신을 지원하는 웹 서버는 웹 서버가 사용자에게 전달하는 공개키가 진짜라는 것을 보증받기 위해 신뢰할 수 있는 인증 기관 (Certificate Authority)에 공개키를 등록하게 된다.

인증서가 만들어지는 목적은 공개키의 무결성을 검증하기 위함이고, 신뢰할 수 있는 인증기관에 의해 보증된다. (*신뢰할 수 있는 인증 기관 목록은 이미 브라우저가 알고 있다.)

2019년 7월 8일 월요일

[SQL] WITH (NOLOCK)

http://www.sqler.com/bColumn/870643

SQL Server에서 잠금(LOCK)은 지극히(?) 정상적인 동작이다. 특정 데이터 영역에 INSERT나 UPDATE 작업이 일어나면 해당 영역엔 LOCK이 걸리게 된다. 데이터의 일관성을 유지하기 위한 것으로 동시에 이 영역을 참조하는 SELECT 작업은 LOCK이 해제될 때까지 대기해야 한다.

문제는 사용자 입장에서 불필요하다고 느낄 때가 있다는 것인데, 정말 단순한 데이터라 일관성은 뒤로하고 그저 빠르게 읽어오고 싶은 경우가 있기 때문이다.

이럴 때 잠금 힌트 중 NOLOCK을 사용한다. NOLOCK 힌트는 커밋되지 않은 트랜잭션이나 읽는 중 롤백된 데이터에 대한 조회를 가능하게 한다. 즉, 커밋되지 않은 읽기가 가능하므로 LOCK이 걸려있어도 대기하지 않고 데이터를 가져올 수 있다. (READUNCOMMITTED) 단, 다시 언급하지만 데이터의 일관성이 보장되진 않으므로 데이터 성격을 감안해서 써야 한다.

사용 방법은 FROM 절 뒤에 WITH (NOLOCK)을 붙여주면 된다.
ex) SELECT * FROM EMPLOYEE WITH (NOLOCK)

Apache Commons DBCP 설정

https://commons.apache.org/proper/commons-dbcp/configuration.html

  • initialSize (default: 0) : BasicDataSource 클래스 생성 후 최초로 getConnection()을 호출할 때 커넥션 풀에 채워지는 커넥션 개수
  • maxTotal (default: 8) : 동시에 사용할 수 있는 최대 커넥션 개수
  • maxIdle (default: 8) : 커넥션 풀에 반납할 때 유지될 수 있는 커넥션 개수
  • minIdle (default: 0) : 최소한으로 유지할 커넥션 개수
→ initialSize, maxTotal, maxIdle, minIdle은 동일한 값으로 통일해도 무방한데 실제로 성능에 영향을 주는 요소는 커넥션 개수를 몇개까지 쓸 것인가에 대한 것이기 때문이다. 풀의 최대 크기를 몇 개로 설정할 것인가가 관건이지 저 4개의 변수를 미세 조정하는 것은 별 의미가 없다. 그리고 일반적으론 maxTotal과 maxIdle 값은 같게 지정하는 것이 좋다. maxTotal 보다 maxIdle이 낮게 설정되면 일부 커넥션이 거의 즉시 닫혔다 열리는 현상을 볼 수 있다. 이런 현상은 비용 측면에서 낭비다.
# 초기엔 initialSize, maxIdle, minIdle 값을 maxTotal 대비 낮은 값을 지정해 적정 커넥션 수치를 모니터링한 뒤 값을 Fix하는 것이 권장된다.

  • maxWaitMills : 커넥션 풀 안의 커넥션이 고갈됐을 때 커넥션 반납을 대기하는 시간. 단위는 밀리초. 이 값이 너무 짧으면 불필요한 오류가 생기고 너무 크면 사용자가 과도하게 대기하는 증상이 발생해 좋지 않다. (10초(10000ms) 권장)
  • validationQuery : 커넥션 풀에서 연결의 유효성을 검사하는데 사용할 SQL 쿼리를 지정한다. 형식은 적어도 하나의 행을 반환하는 SQL SELECT 문이어야 한다.
     - Oracle : select 1 from dual
     - MS-SQL : select 1
     - MySQL : select 1
  • testOnBorrow (default: true) : 커넥션 풀에서 커넥션을 얻어올 때 테스트 실행
  • testOnReturn (default: false) : 커넥션 풀로 커넥션을 반환할 때 테스트 실행
  • testWhileIdle (default: false) : 유휴 객체 제거기(object evictor)에 의한 유효성 검증을 할 것인가에 대한 설정. validate에 실패하면 커넥션이 풀에서 삭제된다. testOnBorrow는 getConnection()을 호출할 때마다 테스트를 한다면 이 설정은 특정 주기로 여러 개의 커넥션을 모아서 테스트한다. (by object evictor thread.)
  • timeBetweenEvictionRunsMillis (default: -1) : object evictor 쓰레드 실행 간격. object evictor를 활성화 시키려면 양수 값을 갖어야 한다. Tomcat DBCP는 이 값을 5초 정도로 해도 괜찮지만 Commons DBCP에선 성능 저하의 위험이 있다. Commons DBCP에선 기본 설정이 아예 false인 것으로 봐도 짧은 시간을 지정하는건 무리가 있는듯. (30초 ~ 60초 권장)
  • numTestsPerEvictionRun (default: 3) : object evictor 쓰레드가 실행할 때마다 검사할 커넥션 개수. 검사 시간 동안 커넥션 풀에 락이 걸린다고 한다. 기본값 권장.
  • minEvictableIdleTimeMillis (default: 1000 * 60 * 30, 30분.) : 커넥션이 축출 대상이 되기 전에 커넥션 객체가 풀에서 유휴 상태로 있을 수 있는 최소 시간.
  • removeAbandoned (default: false) : 오랫동안 열려만 있고 close() 메서드가 호출되지 않는 커넥션을 임의로 닫는 기능을 설정. (removeAbandonedOnBorrow / removeAbandonedOnMaintenance)

2019년 7월 3일 수요일

Conquer and divide

In XP, We don't divide and conquer. We conquer and divide. First we make something that works, then we bust that up and and solve the little parts.
- Kent Beck.

애자일이나 TDD에 대한 가장 흔한 오해 중 하나는 그 방식들이 divide and conquer를 장려한다는 생각이다.
...
고객에게 가치를 전달하는 측면을 보자면 divide and conquer는 제대로 가치를 주지 못할 수 있습니다. 왜냐하면 experience를 주기보다 feature를 주기 때문입니다.
...
- 김창준님 페이스북

2019년 6월 12일 수요일

김연수 소설가의 일 中

1.
나이가 들면 성격이 바뀌지 않는다는 통념이 많지만, 마흔 살이 넘어도 나는 어떤 사람이 되고 싶고, 또 될 수 있다고 생각한다. 이건 무슨 바람이나 신념 같은 게 아니라 과학적 사실에서 나오는 말이다. 뇌과학에는 반복된 경험이 뇌의 구조를 바꾼다는 사실을 가리키는 신경가소성이라는 용어가 있다. 반복하면 할수록 뇌의 구조가 바뀌기 때문에 어떤 일을 계속 연습하면 사람이 달라진다는 사실은 20세기 후반에야 비로소 과학적으로 확인됐다. 쉽게 말하면 의식적으로 하루에 세 번 농담을 던지는 행동을 계속하면 뇌의 신경경로가 농담을 잘하는 쪽으로 변화하고 재구조화된다. 그렇게 일단 뇌가 바뀌면 사람이 달라진다. 그러니까 유머를 개발하려고 노력하고 생활에서 이를 실천하면 사십 년 뒤에 내가 농담을 잘하는 할아버지가 된다는 것은 거의 확실하다. 점점 우스워지는 사람이 있을 뿐, 날 때부터 우스운 사람은 없다.

2.
어떤 일을 할 것인가 말 것인가 누군가 고민할 때, 나는 무조건 해보라고 권하는 편이다. 외부의 사건이 이끄는 삶보다는 자신의 내면이 이끄는 삶이 훨씬 더 행복하기도 하지만, 한편으로는 심리적 변화의 곡선을 지나온 사람은 어떤 식으로든 성장한다는 걸 알기 때문이다. 아무런 일도 하지 않는다면, 상처도 없겠지만 성장도 없다. 하지만 뭔가 하게 되면 나는 어떤 식으로든 성장한다. 심지어 시도했으나 아무것도 하지 못했을 때조차도 성장한다. 그러니 일단 써보자. 다리가 불탈 때까지는 써보자. 그러고 나서 계속 쓸 것인지 말 것인지 결정하자. 마찬가지로 어떤 일이 하고 싶다면, 일단 해보자. 해보고 나면 어떤 식으로든 우리는 달라졌을 테니까. 결과가 아니라 그 변화에 집중하는 것, 여기에 핵심이 있다.

JavaScript 난독화

https://stackoverrun.com/ko/q/3910942

JavaScript Obfuscate.

/** 
* Obfuscate a plaintext string with a simple rotation algorithm similar to 
* the rot13 cipher. 
* @param {[type]} key rotation index between 0 and n 
* @param {Number} n maximum char that will be affected by the algorithm 
* @return {[type]}  obfuscated string 
*/ 
String.prototype.obfs = function(key, n = 126) { 
    var chars = this.toString().split(''); 

    for (var i = 0; i < chars.length; i++) { 
        var c = chars[i].charCodeAt(0); 
        chars[i] = String.fromCharCode((c + key) % n); 
    } 

    return chars.join(''); 
}; 

/** 
* De-obfuscate an obfuscated string with the method above. 
* @param {[type]} key rotation index between 0 and n 
* @param {Number} n same number that was used for obfuscation 
* @return {[type]}  plaintext string 
*/ 
String.prototype.defs = function(key, n = 126) { 
    return this.toString().obfs(n - key); 
}; 

사용 방법은 아래와 같다. 시간이 없어서 코드를 그대로 긁어다 붙였지만 예상대로 동작하지 않을 수 있기 때문에 사용하기 전에 충분한 테스트가 필요하다.


"abc;123!".obfs(13) // => "nopH>?@." 
"nopH>?@.".defs(13) // => "abc;123!" 

무슨 말인가 하면 아스키 문자에는 del 키나 backspace 키 같은 제어 문자가 포함되므로 난독화 과정에서 일부 문자가 소실될 수 있는 것이다. 아스키 코드 표는 아래 링크에 잘 정리되어 있다.
https://shaeod.tistory.com/228

이병철 회장의 경영 15계명

1. 행하는 자 이루고, 가는 자 닿는다.
2. 신용을 금쪽같이 지켜라.
3. 사람을 온전히 믿고 맡겨라.
4. 업의 개념을 알아라.
5. 판단은 신중하게, 결정은 신속하게.
6. 근검절약을 솔선수범하라.
7. 메모광이 되라.
8. 세심하게 일하라.
9. 신상필벌을 정확하게 지켜라.
10. 전문가의 말을 경청하라.
11. 사원들을 일류로 대접하라.
12. 부정부패를 엄히 다스려라.
13. 사원 교육은 회사의 힘을 기르는 것이다.
14. 목계의 마음을 가져라.
15. 정상에 올랐을 때 변신하라.

2019년 5월 29일 수요일

Spring 3.X 프로젝트 설정하기 (xml 방식)

Spring 버전 3의 프로젝트 설정방법에 대해 알아보기로 한다. 버전 3 이후론 Annotation을 사용해 코드 내에서 대부분의 설정을 할 수 있지만 이번 포스팅에선 xml 설정 방식을 따르기로 한다. IDE는 Eclipse 기준이고 Spring 3.X, Maven, Mybatis를 쓴다고 가정하였다.

당연히 Spring Boot를 쓰거나 프로젝트 템플릿을 내려받아 쓰는게 편할 것이다. 그렇지만 편해지기 이전에 어떻게 프로젝트 설정을 하고, 설정된 프로젝트는 어떻게 켜지는지 알 필요가 있다고 생각한다. Spring관 관련이 적지만 아래의 링크가 도움이 되었다. 아래 링크는 Spring 프레임워크 없이 쌩 Java-웹 표준 사양만 가지고 웹 프로젝트를 동작시키는 것에 대해 잘 설명해주고 있다.
https://www.in28minutes.com/first-java-web-application-with-jsp-and-servlets-in-25-steps

프로젝트를 만들어보자.


1. File > New > Maven Project

- Use default Workspace location
- maven-archetype-webapp
- Group Id: 네이밍 스키마를 적용한 고유 도메인 이름. 예: org.apache.maven, org.apache.commons
- Artifact Id: 프로젝트 이름, jar 또는 war 파일의 이름이 된다.
# Group Id와 Artifact Id는 특수문자를 사용하지 않고 가급적 소문자로 작성한다. 예: maven, commons-math


2. Java 버전, Facet 설정 [Optional]

- Properties > Java Build Path > Libraries
- Properties > Java Compiler
# pom.xml에서 지정해도 괜찮다. (maven-compiler-plugin)

- 프로젝트 경로 > .settings > org.eclipse.wst.common.project.facet.core.xml
 <installed facet="java"/>, <installed facet="jst.web"/> (Dynamic Web Module -> 3.1) 버전 설정.
- File > Restart
- Properties > Project Facets > 표시되는 버전이 xml에 지정한 것과 일치하는지 확인.


3. web.xml - 스키마 버전 세팅 [Optional]

# 버전이 높을수록 사용할 수 있는 태그의 표현 범위는 늘어나겠지만 안해줘도 크게 상관은 없는듯.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <display-name>project name</display-name>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>


4. Maven Install & edit pom.xml

- Run As > Maven install
- dependency와 build 항목에 필요한 설정을 해준다. (web에 일반적으로 쓰이는 pom.xml을 찾아 참고하면 된다.)
# dependency : spring-context, spring-webmvc, ...
# build : maven-eclipse-plugin, maven-compiler-plugin, maven-war-plugin
- Spring Tools > Add Spring Project Nature : 'Spring Elements'를 프로젝트에 추가해준다.
- Maven > Update Project : pom.xml 내용을 프로젝트에 적용해준다.


5. web.xml, servlet-context.xml 설정

- web.xml에 FrontController 역할을 하는 org.springframework.web.servlet.DispatcherServlet을 등록해준다.
# DispatcherServlet은 보통 하나만 등록하지만 하나의 프로젝트 내부에서 성격이 다른 여러 개의 서비스를 서빙해야 하는 경우 여러 개를 등록할 수 있다.
# DispatcherServlet의 contextConfigLocation 파라미터에 서블릿(스프링) 관련 context 경로를 잡아준다.
- /WEB-INF/config/spring/*-context.xml과 같이 설정할 수 있다.

<servlet>
    <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/spring/*-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/api/*</url-pattern>
</servlet-mapping>

- /WEB-INF/config/spring/servlet-context.xml을 생성한다. 파일 형식은 Spring Bean Configuration File. 네임스페이스 beans, context, mvc 체크.
- 생성된 servlet-context.xml에 component-scan을 위한 base package를 지정해 준다.
- 애플리케이션 내에서 @Controller, @Component 등 프레임워크에서 제공되는 annotation을 이용하는 것이 일반적이므로 <mvc:annotation-driven />을 선언한다.
- view가 있는 프로젝트라면 view resolver를 설정해준다.
- web.xml에서 encoding filter를 설정해준다. 로그 출력을 위한 log filter, 크로스 도메인간 요청 허용을 위한 cors filter도 필요하면 등록시켜준다.

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>


6. applicationContext.xml 설정

- applicationContext.xml을 생성한다. 형식은 Spring Bean configuration File. 네임스페이스 aop, beans, p, tx 체크.
- web.xml에서 contextConfigLocation 파라미터의 값이 applicationContext.xml을 가리키도록 한다.
- web.xml에서 listener로 ContextLoaderListener를 선언한다.


<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

- applicationContext.xml에 필요한 bean을 등록해준다. ContextLoaderListener가 applicationContext.xml에 선언된 bean을 파싱하고 singleton으로 로드시키는 역할을 하나보다. Application context와 Servlet context가 있고, 사용자는 이 둘에 대한 life cycle을 Spring Framework에 위임하게 되는 셈.

더 적으려다가 아래 링크를 참고하는게 나을 것 같다. :(
https://handcoding.tistory.com/10?category=621283

2019년 5월 15일 수요일

Python 가상 환경 사용하기 (venv)

Python 3.3 이후 버전부터는 venv 모듈을 이용해 가상 환경을 생성한다. (아래 버전에서는 virtualenv 패키지를 이용하면 된다.) venv 모듈을 통해 특정 프로젝트가 포함된 디렉터리를 Global 환경과 격리시킬 수 있고, 독립적으로 설치된 Python 패키지 집합을 운영할 수 있게 된다.

python -m venv my-env # my-env는 가상 환경 이름이다.
call my-env\Scripts\activate # activate.bat를 호출함으로써 가상 환경이 활성화 된다.
...
(my-env) pip install -r requirements.txt

왜 필요한가?

Python으로 작성된 여러 개의 프로젝트를 운영하는 경우 프로젝트 상황에 따라 같은 모듈이더라도 참조 버전이 갈리는 경우가 발생한다. 혼자서 하는 작업이라면 버전이 높은 쪽을 기준으로 소스 코드를 수정하면 되겠지만 여럿이서 하는 작업이라면 곤란해진다.

이런 문제점의 대안으로 Python은 가상 환경(Virtual environment)이라는 개념을 제공하는데, 프로젝트 별로 전용의 Environment를 설정할 수 있게 해줌으로써 버전 충돌로 인한 문제점을 해결해주고 있다. 특히 참조하는 모듈의 버전 뿐만 아니라 Python 인터프리터 버전도 설정할 수 있기 때문에 여러모로 편리하다고 할 수 있다.

https://dojang.io/mod/page/view.php?id=2470

2019년 5월 2일 목요일

국제화(I18N)과 현지화(L10N)

국제화 (Internationalization): 텍스트의 글로벌한 입출력 처리에 관한 작업. 다양한 나라의 언어 표현 방법을 모두 수용할 수 있도록 만들어주는 것을 의미한다. (=다국어 처리 / Unicode)

현지화 (Localization): 애플리케이션을 특정 Locale에 적합하게 만드는 작업.
 * Locale: 시간대, 시간 및 날짜 표기 형식, 숫자 표기 형식, 통화 기호 등.

국제화는 줄여서 I18N(Internationalization은 I로 시작해서 N으로 끝나는 18자리 단어.)
현지화는 줄여서 L10N(마찬가지로 Localization은 L로 시작해서 N으로 끝나는 10자리 단어.)

국제화는 주로 언어적인 내용과 관련이 있지만 현지화는 지역의 관습이라든가 정렬의 순서, 미적 감각 등 보다 섬세한 측면에서 문화적인 내용을 포함한다.

https://ko.wikipedia.org/wiki/%EA%B5%AD%EC%A0%9C%ED%99%94%EC%99%80_%EC%A7%80%EC%97%AD%ED%99%94

2019년 4월 28일 일요일

OEE (설비 종합 효율)


OEE = Overall equipment effectiveness


OEE는 제조 공정이 얼마나 효율적으로 수행되는지를 평가하기 위한 지표다. OEE 수준은 산업 별로 다르게 나타날 수 있으므로 절대적인 척도가 아니며 스스로의 과거 수준과 현 수준을 파악하는데 더 유용하다. OEE의 계산은 어렵지 않지만 metric에 대한 표준에 대해 주의를 기울일 필요가 있다.

OEE의 기본 측정 항목
- Availability : 계획된 생산 시간 대비 작업 수행 시간. (Uptime)
- Performance : 설계된 생산 속도 대비 실제 속도
- Quality : 양품율. First Pass Yield.

* 참고로 OEE에 Loading을 곱하면 TEEP(Total effective equipment performance)가 된다.
Loading = Scheduled Time / Calendar Time. (7일 중 5일을 일한다면. Loading은 5/7 = 71.4%)

OEE 식.
OEE = Availability * Performance * Quality
* 예시: (OEE=73.6%) = (Availability= 86.6%)*(Performance=93%)*(Quality=91.3%)
* Availability = Operating time / Scheduled time. (* 점심 시간과 같은 계획된 정지 시간은 빼야 한다.)
* Performance = (Parts produced * Ideal cycle time) / Operating time
* Quality = (Units produced - defective units) / (Units produced)

아니면 단순하게 이런 식의 계산도 가능하다.
1. 28,800초 동안 14,400개 생산. → 하나 만드는데 2초.
2. 최적 싸이클 타임이 1.5초인데 이 경우 14,400개 만드는덴 21,600초만 필요함. (7,200초의 Loss가 있었음.)
3. OEE = 21,600 / 28,800 = 75%

3개의 metric을 단순하게 곱해버리기 때문에 하나만 깨져도 결과가 좋지 않게 나오는 것을 볼 수 있다. 따라서 모델이 해당 산업군에 적합한지 부터 따져볼 필요가 있겠다.

-

* 조업시간 = 생산을 해야 되는 시간 (휴무 제외)
* 부하시간 = 설비를 가동해야 하는 시간 (계획가동정지 제외)
* 가동시간 = 설비가 실제 가동한 시간 (정지로스 제외)
* 정미가동시간 = 제품을 만든 시간
* 가치가동시간 = 양품을 만든 시간

2019년 4월 17일 수요일

AJAX와 CORS

AJAX (Asynchronous JavaScript And XML)


AJAX는 서버와 통신하기 위해 XMLHttpRequest 객체를 사용하는 것을 의미한다. 이 객체를 이용해 XML, JSON, HTML 그리고 일반 텍스트 형식 등을 포함한 다양한 포맷을 주고 받을 수 있다. 페이지 전체를 리프레쉬하지 않고서도 수행되는 "비동기성"은 AJAX의 가장 큰 특징으로 페이지의 일부분만을 업데이트 할 수 있게 해준다.

AJAX와 함께 자주 거론되는 개념이 있는데 그건 바로 CORS다. CORS의 도입 배경은 다음과 같다. HTTP 요청은 기본적으로 A 도메인에서 B 도메인으로 향하는 cross-site HTTP 요청이 가능하지만 <script></script>로 둘러싸인 스크립트에서 생성된 cross-site HTTP 요청은 보안상 이유로 호출이 제한된다. XMLHttpRequest도 same-origin 정책을 따르는데 XMLHttpRequest에 대해서도 cross-site HTTP 요청이 가능해야 한다는 요구가 생겨 W3C에 의해 CORS 권고안이 나오게 되었다.



CORS (Cross-Origin Resource Sharing)


CORS는 origin 도메인에서 실행중인 웹 애플리케이션이 추가 HTTP 헤더를 사용해 브라우저가 다른 출처 서버의 자원에 액세스할 수 있는 권한을 가지도록 하는 메커니즘이다.
→ CORS를 쓰면 AJAX로도 다른 도메인의 자원을 쓸 수 있다.

1. CORS 스펙에선 서버 데이터에 side-effects를 일으킬 수 있는 요청 메서드(특정 MIME 유형의 POST 등)에 대해 브라우저가 요청할 내용을 OPTIONS 메서드를 이용해 사전 전달(preflight) 하도록 하는데, 이는 서버로부터 지원 중인 메서드(GET, POST, ...) 목록을 내려 받은 뒤 승인(approval) 시에 실제 HTTP 요청을 전송하는 것을 말한다.
→ 사용자가 요청 프로세스를 직접 다루는 것이 아니라 브라우저가 알아서 처리한다.
2. 서버는 클라이언트에게 요청과 함께 자격 증명(credential)을 보내야하는지 여부를 알릴 수도 있다.


AJAX 요청의 흐름.

기본적으로 비동기이므로 요청 전에 서버로 보낸 요청에 대한 응답을 받았을 때 처리할 함수를 미리 지정해주어야 한다.
onreadystatechange property에 지정하면 된다.
그리고 나서 open()과 send()를 차례대로 요청하면 된다.


httpRequest.onreadystatechange = function () {
  // ...
};

// 쓸 일은 없겠으나 open의 셋째 파라미터를 false로 지정하면 동기적으로 동작한다.
// 이 경우엔 onreadystatechange는 필요하지 않다.
httpRequest.open('GET', 'http://www.example.org/some.file', true); 

// send()의 파라미터가 null인 까닭은 'GET' 방식의 호출이기 때문이다. 'POST'인 경우엔 서버로 보낼 데이터를 send() 파라미터에 지정하면 된다.
// 다만 서버가 데이터의 형식을 알 필요가 있기 때문에 request에 MIME type을 먼저 설정해야 한다.
httpRequest.send(null);
httpRequest.setRequestHEader('Content-Type', 'application/x-www-form-urlencoded');

위와 같이 서버 응답에 대한 처리는 onreadystatechange에 지정한 함수에서 이루어진다.
일반적으로 httpRequest.readyState의 값이 XMLHttpRequest.DONE(4) 이면서 httpRequest.status의 값이 200 OK인 경우 호출-응답의 프로세스가 성공한 것이다.

받은 데이터는 responseText 또는 responseXML property로 읽어오면 된다. responseXML의 경우 서버의 응답을 XMLDocument 객체로 반환하며 DOM API를 통해 이 객체를 다룰 수 있을 것이다.

기본적인 예제는 아래를 참고하자.

<button id="ajaxButton" type="button">Make a request</button>

<script>
(function() {
  var httpRequest; // httpRequest 변수는 지역 변수로 선언해야 한다. 
                   // 전역으로 사용되면 여러 요청에 의한 경쟁 상태(race condition)이 발생할 수 있다.
  document.getElementById("ajaxButton").addEventListener('click', makeRequest);

  function makeRequest() {
    httpRequest = new XMLHttpRequest();

    if(!httpRequest) {
      // Can not create instance.
      return false;
    }
    httpRequest.onreadystatechange = alertContents;
    httpRequest.open('GET', 'test.html');
    httpRequest.send();
  }

  function alertContents() {
    if (httpRequest.readyState === XMLHttpRequest.DONE) {
      if (httpRequest.status === 200) {
        alert(httpRequest.responseText);
      } else {
        // Error
      }
    }
  }
})();
</script>

https://developer.mozilla.org/ko/docs/Web/Guide/AJAX/Getting_Started
https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS#%EA%B0%9C%EC%9A%94

2019년 4월 15일 월요일

WPF - Binding Proxy

WPF에서 DataContext 속성은 할당된 요소의 자식에게 자동으로 상속되므로 편리하다. 따라서 바인딩하려는 각 요소에 대해 다시 DataContext를 지정하지 않아도 된다.

그러나 WPF에서 바인딩 대상이 Target Object와 다르거나 Visual Tree가 다른 경우 참조식을 작성할 때 불필요하게 식이 길어지는 경우가 종종 있다. (DataGrid, ItemsControl 내 엘리먼트가 ViewModel의 프로퍼티를 참조하는 경우)

이런 상황에 놓이면 사용자가 해당 요소에 DataContext를 직접 알려주어야 한다.
이러한 경우 Binding Proxy를 만들어 이용하면 코드도 간결해지고 표현도 명확해진다.

public class BindingProxy : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy));
}

Freezable을 구현하는 까닭은 WPF에서 Freezable 객체는 Visual Tree 또는 Logical Tree가 다른 경우에도 DataContext를 상속할 수 있기 때문이다. 사용법은 아래와 같은데, proxy 객체의 Data attached 프로퍼티에 바인딩 된 뷰모델을 바로 참조할 수 있다. (물론 단순한 경우엔 x:Name ~ x:Reference, RelativeSource ~ FindAncestor가 나을지도 모르겠다.)


    <DataGrid>
        <DataGrid.Resources>
            <local:BindingProxy x:Key="proxy" Data="{Binding}"/>
        </DataGrid.Resources>
        <DataGrid.Columns>
            <DataGridTextColumn Visibility="{Binding Data.MyColumnVisibility,
                                                Source={StaticResource proxy}}"/>
        </DataGrid.Columns>
    </DataGrid>


https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

2019년 4월 10일 수요일

WPF의 Dependency Property와 Attached Property.

Dependency Properties와 Attatched Properties는 WPF가 제공하는 UI 시스템이 효율적으로 운영되게 하는 장치라 봐야겠다.


Dependency Properties


DP는 일반적인 프로퍼티와 다르게 인스턴스의 값이 인스턴스에 저장되는 것이 아니고, DependencyObject에 사전(dictionary) 형태로 기록된다. 값을 읽을 때에도 DependencyObject의 GetValue() 메서드를 통한다.

DependencyObject는 특정 UI Control에 대한 기본 속성 값을 가지고 있는다. DependencyObject는 어떤 사용자가 Button을 100개 선언한다고 하여도 Button 100개에 대한 속성 각각을 따로 따로 메모리에 기록하지 않고, 기본 속성 값에서 변경되는 값만 따로 기록하는 방식을 채택해 메모리 낭비를 줄인다. 또한 Value inheritance를 지원해 선언되지 않은 속성 값은 Visual Tree 상 부모의 값을 따르게 하며 PropertyMetadata를 통해 값의 변경, 밸리데이션에 대한 통지를 해준다.

등록하는 DP 이름의 끝에 Property가 붙는 것은 관례다.


// Dependency Property
public static readonly DependencyProperty CurrentTimeProperty = 
     DependencyProperty.Register( "CurrentTime", typeof(DateTime),
     typeof(MyClockControl), new FrameworkPropertyMetadata(DateTime.Now));
 
// .NET Property wrapper
public DateTime CurrentTime
{
    get { return (DateTime)GetValue(CurrentTimeProperty); }
    set { SetValue(CurrentTimeProperty, value); }
}

new FrameworkPropertyMetadata( DateTime.Now, 
                       OnCurrentTimePropertyChanged,  // Value Changed Callback
                       OnCoerceCurrentTimeProperty ), // Coerce Value Callback (Range Check,...)
                       OnValidateCurrentTimeProperty ); // Validation Callback (Type Checking,...)



Attached Properties


Attached Properties는 DP의 특수한 형태로 프로퍼티가 선언된 곳과 다른 곳에서 쓰일 수 있도록 하는 장치다. DP 사례의 Register() 대신에 RegisterAttatched()에 의해 등록된다.

Attached Properties의 경우 예를 떠올리면 이해가 편하다.
Panel.ZIndex 또는 Grid.Row와 같은 것. 이런 프로퍼티는 Button이나 TextBox 같은 컨트롤에서 쓰일 수 있지만 실제 프로퍼티의 정의는 Panel이나 Grid에 되어 있다. 다른 인스턴스에 'attached'되는 것이다.

DP와 다르게 static 메서드 형태로 Set / Get을 정의한다. 당연한게 선언된 곳과 다른 인스턴스에서 호출되어야 하기 때문이다.


<Canvas>
    <Button Canvas.Top="20" Canvas.Left="20" Content="Click me!"/>
</Canvas>

public static readonly DependencyProperty TopProperty =
    DependencyProperty.RegisterAttached("Top", 
    typeof(double), typeof(Canvas),
    new FrameworkPropertyMetadata(0d,
        FrameworkPropertyMetadataOptions.Inherits));
 
public static void SetTop(UIElement element, double value)
{
    element.SetValue(TopProperty, value);
}
 
public static double GetTop(UIElement element)
{
    return (double)element.GetValue(TopProperty);
}


2019년 4월 2일 화요일

[Netty] 클라이언트 예제

Client 초기화시엔 read/write를 처리할 worker thread만 지정해 주면 된다. 빌더 패턴으로 부트스트래핑 하는데 인코더/디코더를 어떤걸 쓸건지, 어떤 옵션을 택할건지를 빼면 코드는 거의 비슷하다.

아래 예제에선 채널 파이프라인에 ObjectEncoderObjectDecoder를 지정해 주었다. ObjectEncoder는 Java 객체를 ByteBuf로 serialize 하는 역할을 수행하고, ObjectDecoder는 수신한 ByteBuf를 Java 객체로 deserialize 해준다.

싱글턴으로 XXXXClient를 구현하고 핸들러에 메시지를 전송할 수 있는 메서드를 열어두면 간단하게 소켓 클라이언트를 구현할 수 있다. Handler로 전달된 참조형 객체(ByteBuf)의 해제는 핸들러의 책임인 것으로 알고 있다. 읽는 쪽의 어딘가에서 ReferenceCountUtil.release() 해주어야 한다. 메시지를 쓰기 위해 필요한 ChannelHandlerContextchannelActive 시점에 셋 해주면 될 것이다.


public class XXXXClient {
    private EventLoopGroup workerGroup = new NioEventLoopGroup();
    private XXXXHandler handler = new XXXXHandler(...);

    // Singleton.
 
    public void start(...) {
        new Thread(() -> {
            try {
                Bootstrap b = new Bootstrap();
                ChannelFuture f = b.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(...)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(
                                new ObjectEncoder(),
                                new ObjectDecoder(ClassResolvers.cacheDisabled(null)),
                                handler);
                        }
                    })
                    .connect(ip, port).sync();
                f.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                // ...
            } finally {
                workerGroup.shutdownGracefully();
            }
        }).start();
    }

    public void stop() {
            workerGroup.shutdownGracefully();
    }

    public void sendMessage(Object msg) {
         handler.sendMessage(msg);
    }
}

public class XXXXHandler extends ChannelInboundHandlerAdapter {
    private ChannelHandlerContext context;

    public void channelActive(...) {
         super.channelActive(ctx);
         this.context = ctx;
    }

    public void channelRead(...) {
        if (msg instanceof YYYY) {
            ...
        }

        ReferenceCountUtil.release(msg);
    }

    public void channelReadComplete(...) {
        ctx.flush();
    }

    public void sendMessage(Object msg) {
        if (context != null && context.channel().isWritable()) {
            context.writeAndFlush(msg);
        } 
    }
}

동작은 하는데 이렇게 하는게 맞는진 잘 모르겠다. :(

이벤트-드리븐 아키텍쳐 (Event-Driven Architecture)

TCP 연결을 처리하는 방식에서 비교되는 개념으로 Thread-Based Architecture와 Event-Driven Architecture가 있다. 이 글에선 Event-Driven Architecture에 대해서 정리하고자 한다.

이벤트-드리븐 아키텍쳐의 구동방식은 일반적으로 아래의 두 가지 형태로 구분된다.


1. 이벤트 리스너 방식.
  대상 객체의 특정 이벤트 리스너에 이벤트 처리 로직을 가진 이벤트 메서드를 등록하는 방식. 객체의 이벤트가 발생할 때 이벤트 처리 스레드에서 등록된 메서드를 수행한다. 등록된 메서드가 없으면 이벤트는 씹힌다. 대부분의 UI 프레임워크가 쓰는 방식이다.


2. 이벤트 큐를 이용하는 방식.
  이벤트 발생 객체(Event Emitter)는 이벤트를 큐에 쌓고, 이벤트를 처리하는 객체(Event Loop)는 큐에 입력된 이벤트가 있을 때 이벤트를 꺼내 이벤트를 실행하는 방식. 위의 방법보다 일반적이나 구현 난이도는 높다.

  이 방법에선 구현 목적에 따라 이벤트 처리 스레드를 하나로 둘 수도 있고, 여러 개로 둘 수도 있다. trade-off가 있다.

  2.1 싱글 스레드 기반 : 이벤트를 순서대로 처리할 수 있다. 다중 코어 CPU의 자원을 효과적으로 사용할 수 없다.
  2.2 멀티 스레드 기반 : 다중 코어 CPU 자원을 효과적으로 사용할 수 있다. 이벤트 처리 순서를 보장할 수 없다. 이벤트 큐에 대한 스레드 경합이 발생한다.

→ Netty 같은 I/O 프레임워크에선 채널(하나의 연결) 별로 전용 이벤트 큐를 할당시키는 방식을 채택한다. 전체 메시지의 처리 순서를 보장할 순 없어도 특정 채널에서 발생하는 메시지의 처리 순서는 보장해준다.
(채널과 큐의 관계는 1:1은 아니고, N:1의 관계.)

두 번째 방식을 좀더 구체화한 아키텍쳐 패턴으로 Reactor Pattern이 있다.

Reactor Pattern은 이벤트에 반응하는 객체(Reactor)가 이벤트가 발생할 때 Event Handler에게 전달해주고, Event Handler는 알맞은 method를 사용해 처리하는 구조를 갖는다.

Reactor가 계속해서 이벤트를 Dispatching 할 수 있게 해줘야 하므로 Event Handler는 새로운 스레드(스레드 풀)에서 실행되는 것이 일반적이다.

Reactor Pattern은 아래 링크에서 친절하게 설명되어 있다.
http://jeewanthad.blogspot.com/2013/02/reactor-pattern-explained-part-1.html

The beauty of non blocking architecture is that we can write the server to run in a single Thread while catering all the requests from clients. Just forget about the Thread pool for a while. Naturally when concurrency is not used to design a server it should obviously be less responsive to events. In this scenario when the system runs in a single Thread the Reactor will not respond to other events until the Handler to which the event is dispatched is done with the event. Why? Because we are using one Thread to handle all the events. We naturally have to go one by one. 
We can add concurrency to our design to make the system more responsive and faster. When the Reactor dispatches the event to a Handler, it can start the Handler in a new Thread so that the Reactor can happily continue to deal with other events. This will always be a better design when performance is concerned. To limit the number of Threads in the system and to make things more organized, a Thread pool can be used.