Home

Spring에서 Field Injection보다 Constructor Injection이 권장되는 이유

이 주제는 좀 묵은 주제이기도 하지만, 주변을 둘러봐도 그렇지만, 일단 의존성 추가나 삭제가 간결하고 코드량도 많지 않고, 문제 발생한 경우도 없어서인지 Field Injection을 많이 사용하게 됩니다.
하지만 왜 Spring Team에서 Constructor Injection을 추천하는지 리마인드 차원에서 정리해 봅니다.

Spring의 Dependency Injection

1. Constructor Injection
Spring 4.3에서 단일 생성자의 경우 @Autowired가 필요가 없다.
@Component
public class ConstructorInjection {
     private final LoginService loginService;
     private final SignupService signupService;

    @Autowired
    public ConstructorInjection(LoginService loginService, 
                SignupService signupService) {
         this.loginService = loginService;
         this.signupService = signupService;
    }
}

2. Field Injection
@Component
public  class FieldInjection {
    @Autowired
    private LoginService loginService;
    @Autowired
    private SignupService signupService;
}

3. Setter Injection
@Component
public  class SetterInjection {
     private LoginService loginService;
     private SignupService signupService;

    @Autowired
    public  void setLoginService(LoginService loginService) {
         this.loginService = loginService;
    }

    @Autowired
    public  void setSignupService(SignupService signupService) {
         this.signupService = signupService;
    }
}

왜 Constructor Injection을 권장하나?

1. 단일 책임의 원칙
생성자의 인자가 많을 경우 코드량도 많아지고, 의존관계도 많아져 단일 책임의 원칙에 위배된다. 그래서 Constructor Injection을 사용함으로써 의존관계, 복잡성을 쉽게 알수 있어 리팩토링의 단초를 제공하게 된다.

2. 테스트 용이성
DI 컨테이너에서 관리되는 클래스는 특정 DI 컨테이너에 의존하지 않고 POJO여야 한다. DI 컨테이너를 사용하지 않고도 인스턴스화 할 수 있고, 단위 테스트도 가능하며, 다른 DI 프레임 워크로 전환할 수도 있게 된다.

3. Immutability
Constructor Injection에서는 필드는 final로 선언할 수 있다. 불변 객체가 가능한데 비해 Field Injection은 final는 선언할 수 없기 때문에 객체가 변경 가능한 상태가 된다.

4. 순환 의존성
Constructor Injection에서는 멤버 객체가 순환 의존성을 가질 경우 BeanCurrentlyInCreationException이 발생해서 순환 의존성을 알 수 있게 된다.

5. 의존성 명시
의존 객체 중 필수는 Constructor Injection을 옵션인 경우는 Setter Injection을 활용할 수 있다.

Lombok을 활용한 Constructor Injection

참고로 개발 편이성은 좋아질 수 있으나, 의존관계의 복잡성을 명확하게 보여주진 못하게 된다.
@RequiredArgsConstructor는 초기화 되지 않은 final 필드를 매개 변수로 취하는 생성자를 생성하고 @NonNull이 필드는 null 체크가 실행되고 파라미터가 null인 경우는 NullPointerException을 발생시킨다.

1. Spring 4.3 이상
@RequiredArgsConstructor
@Component
public class ConstructorInjection {
    @NonNull
    private final LoginService loginService;
    @NonNull
    private final SignupService signupService;
}

2. Spring 4.3 이전
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Component
public class ConstructorInjection {
    @NonNull
    private final LoginService loginService;
    @NonNull
    private final SignupService signupService;
}

[참고 사이트]
Tags : , ,

CTO는 무슨 일을 하나?

What does a CTO do?

CTO에 대해서는 좋은 아티클을 추천한다면 개인적으로 #define CTO와 함께 What does a CTO do?를 공유하곤 하는데, 그 중 후자가 더 CTO가 해야할 역할에 대해 잘 기술해 놓은 것 같아서 원저자의 하락하에 번역해 봅니다. What does a CTO do? 아티클의 저자는 Cloudera의 Founder이자 CTO인 Amr Awadallah입니다.



이하는 번역내용을 기술합니다.

이 질문은 내가 많이 받은 질문이기도 하고, 1년 전(2012년에 Cloudera CTO가 되었을 때)만 해도 이에 대한 해답을 찾으려고 노력을 많이 했다. 그래서 온라인상에서 자료도 많이 찾았고, 많은 소프트웨어 기업의 CTO들을 찾아 논의하기도 했다.

여기에서는 내가 가진 지식과 조사 결과들을 가지고 다음 네가지 물음에 대한 답변을 요약하고자 한다.
  • 질문 1. CTO의 미션(사명)은 무엇일까?
  • 질문 2. CTO는 구체적으로 어떻게 평가되어야 할까?
  • 질문 3. CTO는 사내 업무와 대외 업무를 어떻게 시간을 배분해야 할까?
  • 질문 4. 회사의 내부 구성원들에게 CTO가 달성해야하는 구체적인 책무는 무엇일까?

덧붙여 말하자면, 내가 다음에 언급하는 것은 내부 엔지니어 조직을 관리하고 있는 CTO가 아닌, 순수한 CTO에 초점을 맞추고 있으며, 일반 기업의 CTO나 다른 도메인에서는 편향되어 있을 수 있다.

1. CTO의 미션은 무엇인가?

CTO의 임무는 다음 3가지 축으로 요약될 수 있다.

A) 회사의 장기적인 기술 전략 책임자
  • CTO는 기업의 기술 전략을 제대로 유지/관리하고, 명확하게 구체화하고, 또한 지속적으로 올바른 방향으로 발전시켜 나가야 한다.
  • CTO는 역동적으로 진화하고 있는 경쟁 영역에서 회사가 최고의 기술 성과를 지속적으로 발휘할 수 있도록 해야하는 책임이 있다.
  • CTO는 기업의 미래를 좌우하는 중요한 기술적 트렌드 정보를 세상으로부터 식별하고, 기업 내부에 적절하게 연결하며, 그리고 기술 전략과 비즈니스 전략의 적절한 균형을 유지해야 한다.

B) 기술 책임자(테크니컬 에반젤리스트)
  • CTO는 회사의 장기적인 비전을 중심으로 내부 사람들에게 영감과 흥미를 유발해야 한다. 또한 외부의 사람들에게는 세상은 결국 그렇게 될 것이며, 자신의 회사가 최선의 선택임을 확신시켜야 한다.
  • CTO는 시장의 요구를 강조해야하고, 고객을 신뢰할 수 있어야 하고, 비즈니스 가치와ROI(투자 수익률)을 폭넓은 이해당사자들에게 명확하게 이야기할 수 있어야 한다.

C) 엔지니어들의 정신적 리더
  • CTO는 엔지니어 팀을 회사의 장기적인 기술적 목표를 향해 이끌어야 한다.
  • CTO는 새로운 엔지니어가엔지니어 조직에 합류할 수 있도록 영감을 주어야 하며, 인재를 식별하고 조직에 공급할 수 있어야 한다.
  • CTO는 최고의 기술 인재를 유치하고, 이탈없이 지속적으로 보유할 수 있도록 기술 문화도 만들고 유지해야 한다.

2. CTO의 성과를 어떻게 측정할 것인가?

나는 다음 세 가지 측면이 있다고 생각한다.

A) 기술 전략의 방향성 조정.
이것은 양면성을 가지고 있다. 만약 기업이 핵심 기술 동향을 놓치면 불리해지고, 또한, 기술 전략과 비즈니스 전략의 중대한 불일치가 있는 경우에도 불리해진다. 이 점에 관해서는 썬 마이크로 시스템즈의 Greg Papadopolous의 말을 인용한다.
"CFO는 분기별 수익에 대해 일일이 책임을 갖는 것은 아니지만, 회계에 한번이라도 예상치 못한 잘못을 저질렀을 경우 해고해야 한다. 마찬가지로, CTO는 분기별 출시되는 제품에 대해 일일이 책임을 질 필요는 없지만, 만약 핵심 기술의 변곡점(예, 인터넷)을 놓치면 그를 해고해야 한다."

B) 엔지니어 문화의 건강성 체크
기술 조직에 대한 설문조사를 통해 엔지니어의 행복도나 생산성이 동시에 보장 되는지를 측정할 수 있다. 그 지수에 문제가 있는 경우, 기존의 우수한 엔지니어는 마찰을 느끼게 되는 것이고, 외부의 재능있는 엔지니어를 유치 할 수 없다.

C) 회사의 내부 고객 만족도
CFO와 유사한 CTO는 회사내의 모든 직원들에게 고객 지원 센터 같은 존재다. 따라서 적절한 측정 항목은 조직 내부의 직원 만족도가 될 수 있다. CTO 대한 직원 만족도(CTO로부터 부가가치를 얻고 있지 못하다고 느끼면)가 낮으면 그를 해고해야 할 것이다.

3. CTO는 사내 업무와 대외 업무를 어떻게 시간을 배분해야 할까?

이 문제에 대한 완벽한 답은 없지만, 옳은 대답은  둘다 조금씩 존재한다. 외부의 지식 없이는 CTO는 조직 내에서 좋은 일을 할 수 없으며, 그 반대도 마찬가지이다. 둘 모두(내부/외부 업무)를 해야만 CTO는 조직의 비즈니스에 공헌할 수 있다. 또한 가족이나 여행을 생각해야 한다. CTO는 조직내에서 지속적으로 일해야하므로 당신만의 최적의 장소도 찾아야 한다. 이에 대한 답은 회사가 어떤 단계에 있는지에 따라 달라진다. 이런 배후 사정을 고민해서 오늘이라는 하루를 다음과 같이 구분했다.

A) 외부: 70%
  • 판매/고객관리: 35%
  • 마케팅/기술 전도사/산업 분석: 20%
  • 비즈니스 기획/파트너 관리: 15%

B) 사내: 30%
  • 외부에서 수집한 정보들을 내재화 해 그 지식을 적절한 내부팀에 공유한다.
  • 기술과 제품, 비즈니스를 지속적으로 연결하여 공개적으로 명시된 비전에 회사가 현재 가고 있는 방향과 일치하는지, 기술 로드맵이 회사의 비전과 일치하는지 확인한다.
  • 회사의 비전을 명확히 표현하는 White Paper나 산출물을 만들기 위해 마케팅 팀 지원을 함.
  • 엔지니어 문화, 프로세스, 인력 유지 등
  • 지적 재산권 포트폴리오 보호

4. 회사내 각 부서를 위해 CTO로서 구체적인 책무는 무엇인가?

다음 다섯 개의 부분이 CTO로서 완수해야 할 책무가 있다고 할 수 있다. CEO/기업 전략, 엔지니어링/제품 개발, 세일즈, 비즈니스 개발, 마케팅. 아래는 그 구성 요소들에 대한 핵심 역할을 기술한다.

A) CEO/기업 전략
  • 회사의 비즈니스에 결정적인 영향을 미칠 수 있는 모든 기술 전환점 예측.
  • 장기적인 관점에서 회사의 기술 전략 방향과 어떤 기술에 베팅할 것인가를 CEO(CFO/COO)에게 조언한다.
  • CEO가 회사의 기술 방향에 대해 주어진 시간에서 취할 수있는 충분한 정보와 최선의 대안들을 제시한다.
  • 회사의 장기적인 이익을 무엇보다 우선시하는 중립적인 견해를 제공함으로써 CEO를 위한 이사회가 되어야 한다. CFO와 유사하게 CTO는 중요한 비즈니스 리소스를 직접적으로 소유하고 있지 않기 때문에 이를 효율적으로 수행할 수 있는 유일한 위치이다.

B) 엔지니어링/제품 개발
  • CTO는 그날 그날의 기술적인 전략/전술에 대한 관리에 책임을 가질 필요는 없지만, 제품이나 엔지니어링 VP와 긴밀하게 협력하여 전반적인 개발 방향이 회사의 전략적 기술 비전과 부합하도록 해야한다.
  • CTO는 큰 틀에서 전략적 기술 배팅에 우선 순위를 두어야지, 일상적으로 이루어지는 실행에 대해서는 관여해서는 안된다. 일상적인 업무도 중요하지만(놓기 힘들수도 있지만), 장기적인 전략적 사고에 중점을 두는게 필요하다.
  • CTO의 주요 과제중에 하나는 제품 관리나 엔지니어링 관여하지 않고 사람이나 프로젝트, 아이디어들을 어떻게 지원해 주는가를 고민해 성공시키는게 중요하다. 그래서 CTO는 강력한 영향력을 행사할 수 있는 교육을 받아야 하고 어떤 아이디어에 집중할 것인지를 엄격하게 선택하는 것이 필요하다.
  • 엔지니어링 VP를 대변하는(편에 서는) 임원이 되어야 하며, 그가 발표를 하는데 도와주어야 하고 다양한 팀이 직면하고 있는 문제들에 대해 브레인스토밍을 할 수 있도록 도와주어야 한다. CTO와 엔지니어링 VP는 긴밀한 협력 관계여야하며, 매우 중요한 관계이므로, 이를 위해 노력하는데 상당한 시간을 할애해야 한다.
  • 인재 모집이나 유지 노력도 해야 한다.(학계와의 관계도 포함된다.)
  • 전체 조직의 역량 중복이나 낭비를 제거하기 위해 지속적으로 최적화를 하고 부서간의 내실을 다지게 한다.
  • 개발 조직간의 협조나 올바른 방향을 유도하고, 필요한 경우, 기술적 파편화나 어떤 아키텍처를 채용해야 하는가에 대한 분쟁 등을 조정해야 한다.
  • 정기적으로 해커톤을 개최하고 초기 단계에서부터 책임자가 되어 혁신을 조성, 발전 시켜야 한다.
  • 제품 라인 전반에 걸쳐 마스터 아키텍트 역할을 해야한다. 하지만, Cloudera에서는 내가 별로 할 수 있는게 많지 않았지만, 다행히 Cloudera는 나보다 우수한 아키텍트가 많이 있고, 내가 할 수 있었던 것은 그들이 그것에 집중할 수 있도록 도와 줄 뿐이다.(주로 갈등이 클 경우에만 참여했고 그것도 Cloudera에서는 드문 일이었다.)

C) 세일즈
  • 고객과 효과적으로 친밀한 관계를 가지려면 세일즈 조직과 협력하라.
  • 고객과 효과적으로 친밀한 관계를 가지려면 세일즈 조직과 협력하라.
  • 고객과 효과적으로 친밀한 관계를 가지려면 세일즈 조직과 협력하라.
  • 중요하기 때문에 세 번 강조했지만, 전략적 고객과 대등하게 좋은 관계를 구축해서 세일즈 조직을 지원하라.
  • 고객과의 미팅에 참여하여 장기적인 기술 로드맵을 명확하게 하고 자신의 회사가 업계를 선도한고 있다는 확신을 고객에게 심어주자.
  • 시장의 권위자와 이야기해 보고, 고객의 요구를 경청하고, 그 안의 문제를 빠르게 간파하자. 그리고 회사의 제품에 대해 좋은 조언을 제공하자.
  • 이것도 매우 중요하다.  자신이 모른다고 생각하면 질문에 대해 더 자세히 이야기할 수 있는 적합한 사람을 연결해 준다. 좋은 CTO는 그 자리에서 아는체하거나 약을 팔지 않는다.

D) 비즈니스 개발과 파트너십
  • 전략적 파트너 계약을 통해 비즈니스 개발팀을 지원하고 이들 조직의 동료들과 좋은 관계를 유지한다.
  • 파트너 기술 및 구매 목표에 대한 기술적 실사를 통해 해당 기술이 회사의 플랫폼, 제품 및 문화에 적합한지 확인한다.
  • CTO는 동일한 분야의 기술 스타트업들을 추적하고 그들의 가능성에 대해 수집한 정보를 기반으로 순위를 매겨 가지고 있어야 한다. CTO는 가능한 인수 목표, 그 회사가 놓치고 있는 전문성이 무엇인지, 어떤 회사가 부수적인 영역에서 최고의 성과를 내고 있는지? 경쟁자가 그 회사를 해칠 수 있는 것은 무엇인지? 어떤 회사가 최고의 기술팀을 보유하고 있는지? 나는 이 부분에 대해서 공동 창업자인 Jeff Hammerbacher에 많이 의지하고 있다.
  • 기술 커뮤니티에 회사를 대표하여 파트너와 함께 회사의 존재를 알려야 한다.
  • 새로운 파트너의 기술이 자사의 기술 로드맵에 중요한 영향을 줄 수 있는지를 예측해야 한다.(예를 들어, 새로운 유형의 스토리지나 컴퓨터 디바이스 등)
  • 시장의 끊임없는 변화로 인한 장기적인 경쟁 추세(우위)가 어떻게 되는지를 예측한다.

E) 마케팅
  • 이 회사의 기술에 대한 공인으로서의 역할을 한다.
  • 컨퍼런스, 강연회, 언론/미디어/애널리스트의 활동을 통해 회사의 비전과 기술의 방향을 전파하자.
  • 자신에게 중요한 업계의 애널리스트와 좋은 관계를 유지하자.
  • 회사의 제품을 중심으로 대규모의 활동이 왕성한 커뮤니티(예 : 밋업, 해커톤, 업계 컨퍼런스 등)를 구축하고 있는 마케팅 팀을 지원하자.
  • 트위터, 블로그 쓰기, 아티클이나 백서 등을 통해 소셜을 연계한 마케팅을 한다.

요약하면, 위대한 CTO는 한발 뺀 시선으로 숲 전체를 바라보고 고객과 직원 모두의 목소리를 낼 수 있어 기업에게 중요한 존재이다. 당신이 신입 CTO이든, 베테랑 CTO 든 나는 이 아티클이 도움이 되기를 바란다. 여기에서 소개하고 있는 의견에 동의하지 않거나 간과되고 있는 CTO의 중요한 역할이 있으면 언제든지 알려 달라. 건투를 빈다.


프로그래밍에서 인지 편향

Cognitive Biases in Programming

프로그래밍을 하면서 인간은 수많은 생각과 판단을 마주하게 되는데, 그에 못지 않은 인지 편향이 개발자들로 하여금 최선의 결정을 방해해서 결국 버그를 만들게 하고, 생산성을 저해하며 자신감을 떨어끄리기도 하고 일정을 지연시키기도 합니다. 개발자들이 알아야두면 도움이 될 것 같아, "Cognitive Biases in Programming"에 대한 아티클을 번역해 봅니다.

개발자로서써, 우리는 생산성을 방해하는 다양한 문제에 대해 잘 알고 있다. 하지만, 우리는 큰 관점에서 생각하는 것을 놓치는 경우가 종종 있다.

어떤 것은 인지하기 힘든 미세한 것일수도, 어떤건 큰 영향을 주는 것일수도, 여러분이 잘 처리 할 수 있는 것일수도, 잘 못할 수도 있는 것들이 존재한다. 이러한 모든 요소가 하나로 결합되어 일종의 내부 피드백 루프를 형성하여 생산성 저하, 버그 및 큰 좌절로 이어질 수 있다.

이들 한, 두가지의 영향을 최소화 할 수 있다면 그 주기(나쁜 사이클)를 깨고 나머지 것들을 무력화시킬수도 있다. 여기에서는 프로그래밍할 때 여러분들이 알아야 할 5가지 인지 편향에 대해 이야기해 본다.


과도한 가치 폄하(Hyperbolic Discounting)

나중의 더 큰 보수 대신에 지금 당장의 이익을 우선시 하는 것.
여러분중에 테스트 코드 작성을 연기 한 적이 있나요? Vim 사용중에, 화살표 키를 사용하여 커서를 이동시킨적이 있나요? 축하해요. 여러분은 과도한 가치 폄하를 보여준 것이에요. 당장의 이득에 눈이 멀어 화살표 키를 사용한다는 것은 올바른 구문을 찾기 위해서 정확한 라인으로 이동하는 과정에는 큰 고통(긴 시간)을 초래한다. 당장 익숙하지 않는 HJKL을 익힌다면 원하는 곳으로 빨리 갈 수 있어 미래의 이익은 훨씬 높아진다. 결과적으로, 당신은 많은 시간을 절약하게 된다.

이케아 효과 (IKEA Effect)

문제에 대한 자신의 해결책은 과대 평가하는 반면, 다른 솔루션을 과소 평가하는 것.
이케아 효과는 소비자가 직접 조립해서 만든 제품을 훨씬 고 평가(더 많은 가치를 줄 것이라는)하는 경향이 있는데, 이것 또한 인지 편향이다. 우리는 문제에 대한 자신의 해결책을 과대 평가하는 경향이 있고, 반면에 다른 해결책은 과소 평가하는 경향이 있다. 만약, 당신이 멋지고 독창적인 도구가 아닌 그저 그런 사내 도구를 사용하여 회사에 일한 적이 있다면, 내가 무슨 말을 하고 싶은 것인지 알 것이다.

어설픈 최적화 (Premature Optimization)

필요한 것을 이해하기도 전에 최적화하는 것.
자명하다. 엔진을 고치지 않고 낡은 자동차를 빨리 달라게 하는데에 공기 역학적 스포일러 날개를 추가하는 것은 전혀 도움이 되지 않는다. 가장 좋은 예로 실험에 목표를 둔 코드에 성능적으로 완벽한 코드를 작성하는 것이다.

계획 오류 (Planning Fallacy)

작업을 완료하는 데 필요한 시간을 낙관적으로 예상하는 것
계획 오류(Planning Fallacy)는 대부분의 사람에 관련된 이야기다. 그것이 우리든, 프로젝트 매니저든, 제품을 사용하는 고객이든 실제로 언제 작업이 끝날지에 대해 낙관적인 경향이 있다. 이것은 아래 격언이 잘 설명해 준다 : 코드의 첫 90%가 개발 시간의 첫 90%를 차지하고, 코드의 나머지 10%가 또 다른 90%의 개발 시간을 차지한다. 즉, 총 180%를 소요하게 되는 의미로, 당초 예상한 기간보다 훨씬 초과하는 경향을 표현한 것이다. (90 대 90의 법칙)

최신 편향(Recency Bias)

과거에 일어난 일보다 최근의 사건에 높은 가치를 두는 것. 최신 경험을 더 가치있다고 생각하는 것.
최신 편향(Recency Bias)은 문제를 해결해야 할 때 자주 마주치곤 한다. 우리는 비슷한 문제를 해결했기 때문에 그 해결책을 사용하자. 명심하자. 동일한 디자인 패턴을 반복해서 사용하는 자신을 발견하지 않았냐? 그렇다면, 당신은 같은 시각으로 다른 문제를 들여다보고 있는지도 모른다. 우리는 바이어스(편견)를 완전히 제거할 수는 없다. 그러나, 편견이 우리에게 어떻게 영향을 미치고 있는지를 앎으로써 그것이 야기하는 문제를 완화시킬 수는 있다.

프로그래머의 악몽이란?

Quora에서 재밌는 글을 발견해서 여기에 남깁니다. 심심힐 때 한번 보고 리프레쉬 하세요. ^^
  • 당신의 코드나 혹은 버그가 우발적으로 누군가를 죽이거나 해할 수 있다.
  • Internet Explorer(웹 개발자의 경우)
  • 요구사항이 다시 변경되었다.(잦은 요구 사항 변경)
  • GitHub의 병합이 충돌한다.
  • 의도한 것과 다른 디렉토리를 rm -rf *를 입력해 버렸다.
  • Stack Overflow 다운!
  • Stack Overflow에서 자신이 답을 찾고 싶은 것과 똑같은 질문을 발견했는데, 1년 전에 질문인데도 여전히 대답이 쓰여져 있지 않다.(xkcd : Wisdom of the Ancients)
  • Stack Overflow에서 할 수 있는 질문 개수의 한계에 다달았다.
  • 실제 제품에 버그가 발생했는데 로컬에서 재현이나 발생하지 않는다.
  • 버그 발생 확률은 낮지만, 무시할 수준은 아니다.
  • 버그의 원인이 고부하시에만 일어나는 경쟁 조건(race condition)에 해당된다.
  • 버그의 원인을 모른다.
  • 버그의 원인이 되는 코드를 쓴 것은 내가 아니지만, 수정해야하는 책임은 있다. 코드를 작성한 사람들은 더 이상 회사에 없다.
  • 버그를 야기한 이슈가 99% 신뢰하는 라이브러리에서 확인하게 된다. 시간적으로 마지막에 본다는 점이다.
  • 하드웨어 버그인데, 모든 사람들이 소프트웨어 버그라고 지목한다.
  • 다수의 사람들이 디버깅하려고, 몇년에 걸쳐 노력했지만, 아무도 성공하지 않았다.
  • 버그가 논리적 오류인데, 상당히 오랜시간 실행되고 나서야 재현된다.
  • 디버깅에 자신의 아무것도 모르는 분야에서의 경험이 필요하다.
  • 버그 수정 일정에 여유가 없다.
  • 밥줄이 걸려있어, 버그를 무시할 수 없다.
  • 세미콜론 키가 손상되어서 작동되지 않는다.
  • 훌륭하게 잘하고 있는 프로젝트에 코멘트가 없다는 것을 1년 후에 발견하고 수정하면서 외친다. "나는 도대체 왜 이런 일 했어?" "정말이 내가 짠 코드인가?" 자신의 집에서 미아가 된 기분이다.
  • 문서가 없는 라이브러리.
  • ==대신 =를 사용했다.
  • 자만. 준비 부족. 싸구려 견적. 공감이 아니라 분노.
  • 내 코드가 동작은 하지만, 이유를 모른다.
  • 커뮤니케이션 부족 : 프로그래머는 자신이 만드는 것에 대해, 그것이 어떻게 사용되는지를 이해해야 한다. 상황을 정립할 필요가 있다. 이유는 무엇을 만드는 데에는 100가지 이상의 결정이 존재하게 된다. 컨텍스트를 이해하고 있으면 판단이 내릴 수 있다.
  • 오버 커뮤니케이션 : 회의, 회의, 회의.. 회의는 프로그래머를 죽여버린다.
  • 뭔가를 명확히하는데 하루가 걸린다. 그런데 "클라이언트"가 다른 타임존에 있다.
  • 문서가 없는것보다 더 나쁜것은 쓸모없는 문서이다.
  • 들여 쓰기가 엉망이고, 불합리한 구조 때문에 디버깅을 하 수 없다.
  • 보스가 옛날 버전의 프로그램을 테스트하고 있다.

Twitter는 어떻게 1초에 3,000개의 이미지를 처리하고 있나?

How Twitter Handles 3,000 Images Per Second

Twitter는 서비스에서 어떻게 이미지를 처리했는지에 대해 잘 정리된 글이 있어서 원 저자의 허락하에 제 블로그에 포스팅합니다. 원 글은 How Twitter Handles 3,000 Images Per Second" 입니다. 이 글을 통해 퍼블릭 서비스를 만들때 사이트내에서 미디어를 어떻게 처리하면 좋을지에 대한 좋은 힌트를 얻을 수 있으면 더더욱 좋을 거 같습니다. 아래부터는 번역 내용입니다.

현재 Twitter는 초당 3,000장의 이미지(약 200GB)가 만들어지고, 저장되고 있다. 더 잘 할 수도 있었지만, 2015년에 Twitter는 이러한 이미지 파일 등 미디어 파일의 저장 방법을 개선한 덕분에 600만 달러를 절약할 수 있었다.

항상 그랬던 것은 아니다. 2012년 Twitter는 주로 텍스트 기반이었다. 이것은 마치 호그와트(해리포터에 나오는 마법학교)의 벽에 멋지게 움직이는 그림이 없는 것과 같았다. 2016년, 지금은 Twitter가 미디어 중심으로 전환하고 있다. Twitter는 미리보기, 멀티 포토, gif, vine, 인라인 비디오 등에서 필요한 사진(이미지)을 지원할 수 있는 새로운 미디어 플랫폼 개발을 통해 변화해 왔다.

Twitter의 소프트웨어 개발 엔지니어인 Henna KermaniMobile @Scale London에서 3,000 images per second"라는 이야기를 통해 미디어 플랫폼으로의 변화를 말했으며, 그 이야기는 주로 이미지의 파이프 라인에 초점을 맞추고 있었지만, 세부 사항의 대부분은 다른 미디어(동영상이나 음성)에도 적용되어 있다고 말했다.

그 이야기중에서 가장 흥미로웠던 교훈을 몇가지 들어 보자.
  • 문제를 해결하려 가장 간단한 방법을 사용하면 항상 망치게 된다. 이미지가 있는 트윗을 하게 하는 것이 고객을 락인시키는 필수적인 기능이었는데, 이것을 잘 못했다. 이 기능으로 인해 확장성도 떨어졌고, 네크워크 성능 저하도 왔으며, 이런 것들로 인해 트위터가 새로운 기능을 추가하는 것을 어렵게 만들었다.
  • 디커플링. 그래서 트윗과 미디어 업로드를 분리했다. 이를 통해 각 프로세스를 독립적으로 최적화하는 것이 가능해져, 보다 유연하게 운용을 할 수 있었다.
  • 미디어 데이터의 자체가 아니라 참조 처리로 처리 방식을 바꿨다. 시스템에서 큰 데이터 덩어리를 이동시키지 마라. 대역폭을 소비하고, 데이터를 다루는 모든 서비스에서 성능 문제를 일으킬 수 있다. 대신에 미디어 데이터를 저장하고 그 참조를 조작하는 편이 좋다.
  • 업로드를 독립시켜 도중에 실패해도 계속할 수 있도록 해, 미디어 업로드 실패율이 크게 줄었다.
  • 실험과 조사. Twitter는 실험과 그 결과를 조사하여 20일이라는 기간이 이미지 변형본(썸네일, 작은 이미지, 큰 이미지 등)을 만드는데 들어가는 저장과 저장소 크기 산정에서 가장 균형적이면서 효율적인 곳을 찾았다. 이것은 트윗에서 20일이 지나면 이미지는 액세스 될 가능성이 떨어지기 때문에 변형본 이미지는 삭제할 수 있게 된다. 그리고 삭제하면 필요한 서버의 절반인, 하루에 약 4TB의 데이터 스토리지를 절약할 수 있게 된다. 그리고 1년 동안 수백만 달러를 절약할 수도 있다.
  • 온디맨드. (트윗에서 20일 이후) 이전에 변형본 이미지는 삭제할 수 있다. 왜냐하면 사전에 이미지 생성하고 유지하기보다는 필요할 때 즉석에서 이미지를 생성하는 편이 더 낫다. 이처럼 필요에 따라 서비스를 제공함으로써 처리의 유연성이 증가하고, 일을 더 영리하게 수행할 수 있고, 더 일원적으로 관리할 수 있도록 해 준다.
  • Progressive JPEG은 진정한 표준 이미지 포멧의 승자다. 이것은 프런트 엔드와 백엔드 지원이 좋고, 저속 네트워크에서 성능이 좋다.
트위터가 미디어 지향으로 변화하면서 많은 좋은 일들이 일어났다. 그래서 그들이 어떻게 대처해왔는지 배워보자.

옛날 방식 - 2012년 Twitter

1. 쓰기 절차
  • 사용자가 앱으로 트윗을 작성하고 경우에 따라서는 이미지를 첨부한다.
    • 클라이언트는 트윗을 모놀리틱 서버에 게시한다. 이미지는 다른 모든 트윗 메타 데이터와 함께 한꺼번에 업로드 된다. 그리고 일련의 프로세스 과정에 관여하는 모든게 단일 서비스에 의해 돌려진다.
    • 이 엔드 포인트는 올드하게 설계되어 있어 많은 문제의 원인이 되고 있었다.
  • 문제 1: 네트워크 대역폭의 대량 낭비
    • 트윗 작성과 미디어 업로드는 하나의 작업으로 단단히 결합되어 있었다.
    • 업로드는 완전히 성공하든 완전히 실패하든 일회성이었다. 네트워크의 약간의 문제나 일시적인 오류 등 실패의 이유가 무엇이었던 간에, 다시 시작하려면 미디어의 업로드를 포함한 업로드 과정을 다시 할 필요가 있었다. 업로드가 95%까지 완료한 상태에서도 뭔가 문제가 있으면 또 업로드를 처음부터 다시 시작해야 했다.
  • 문제 2: 큰 치수의 미디어에 대한 충분한 확장성을 제공하지 못했다.
    • 이 방법은 동영상처럼 큰 사이즈의 미디어를 위해 충분한 확장성을 제공하지 못했다. 큰 사이즈는 특히 브라질, 인도, 인도네시아 등 네트워크가 느리고 불안정한 신흥 시장에서 실패율이 증가했다. 이 지역은 트윗 업로드 성공률을 정말 높이고 싶은 곳이다.
  • 문제 3: 내부 대역폭의 비효율적 사용
    • 엔드 포인트는 TFE(Twitter 프런트 엔드)에 연결하고, TFE가 사용자 인증과 라우팅을 처리한다. 그런후, 사용자는 Image Service로 이동한다.
    • Image Service는 다양한 크기(소형, 중형, 대형, 썸네일 등)의 이미지 인스턴스를 생성하는 Variant Generator와 통신을 한다. 이들은 사진이나 동영상과 같은 대용량 데이터에 최적화된 키-값(key-value) 저장 장치인 BlobStore에 저장된다. 이미지는 반영구적으로 거기에 저장되어 계속 유지된다.
    • 트윗의 생성과 유지 과정에 관련된 서비스는 그 밖에도 많이 있다. 왜냐하면 엔드 포인트가 모놀리틱이었기 때문에, 미디어 데이터와 트윗 메타 데이터를 묶어, 그 정리는 전체 서비스를 통해 처리되었다. 이 큰 데이터 부하는 직접 이미지를 처리하지 않아도 되는 서비스에 처리를 맡겼고, 이 서비스는 미디어 파이프 라인의 일부가 아니었지만, 큰 데이터 처리의 최적화에 사용되었다. 이 방법은 내부 대역폭을 매우 비효율적으로 만들었다.
  • 문제 4: 비대해진 스토리지 공간
    • 더 이상 요청이 없는, 한 달 혹은 일년 이상 장시간이 지난 트윗 이미지는 BlobStore 저장소에 영구적으로 저장되어 공간을 차지하고 있었다. 때때로 트윗이 삭제되었을 때에도 이미지는 BlobStore에 남아 있었다. 가비지 컬렉션이 없었던 것이다.
2. 읽기 절차
  • 사용자는 트윗과 관련 이미지를 본다. 그 이미지는 어디에서 오는 것일까?
  • 클라이언트는 CDN에서 클라이언트에 맞는 변형본 이미지를 요구한다. CDN은 이미지가 있는 TFE에 이미지에 대해 요청해야 한다. 결국, 특정 크기의 이미지 URL을 BlobStore에서 직접 찾게 된다.
  • 문제 5 부 : 새로운 변화에 도입이 불가능
    • 디자인이 별로 유연하지 않다. 새로운 변화, 즉 다양한 크기의 이미지를 추가하기 위해서는, BlobStore의 모든 이미지에 대해 새로운 이미지 크기로 다시 채우는 작업이 필요하다. 주문형 변형 기능이 없었다.
    • 유연성 때문에 Twitter가 새로운 기능을 클라이언트에 추가하는 것이 어려워졌다.

새로운 방식 - 2016년 Twitter



1. 쓰기 절차
트윗에서 미디어 업로드를 분리했다.
  • 업로드 기능은 일급 객체(First-class object)로 만들어졌다. 업로드 엔드 포인트가 생성되고 원래 미디어를 BlobStore에 넣는 것이 유일하게 하는 일이다.
  • 이것이 업로드의 처리 방법에 유연성을 준다.
  • 클라이언트는 BlobStore에 이미지를 넣는 기능을 하는 Image Service와 통신하는 TFE로 상호 통신을 해 메타 데이터 저장소에 데이터를 넣는다. 그것이 전부다. 관련된 숨겨진 서비스는 더 이상 없다. 어느 누구도 미디어를 해결하지 않고서는, 어느 누구도 데이터를 처리할 수 없다.
  • 미디어 식별자인 mediaId는 Image Service로부터 전달 받는다. 클라이언트가 트윗 또는 DM을 만들거나 프로필 사진을 업데이트하고 싶을때, mediaId는 미디어를 제공하기보다는 미디어를 참조하게 처리하는데 사용된다.
  • 그냥 업로드 된지 얼마 안된 미디어를 붙인 트윗을 작성하고 싶다고 하자. 그 흐름은 아래와 같다 :
    • 클라이언트는 mediaId을 post로 전달하면서 엔드 포인트를 업데이트 한다. 그리고 Twitter Front End에 도달한다. TFE는 생성된 엔터티에 적합한 서비스로 경로를 정한다. DM이나 프로필용 서비스는 여러 가지가 있다. 모든 서비스가 Image Service와 통신을 한다. Image Server가 얼굴 검출 및 아동 포르노 검색 등의 기능을 처리 후 처리 큐에 던져진다. 그것이 끝나면 Image Service가 이미지 처리용인 ImageBird나 동영상용인 VideoBird와 통신을 한다. ImageBird가 다잉한 크기로 이미지를 변형본을 만든다. VideoBird는 일부분에 대해 트랜스 코딩을 한다. 생성된 미디어가 무엇이든 BlobStore에 저장된다.
    • 미디어가 전달되는 것이 아니어서 불필요한 대역폭이 줄어들었다.
세그먼트화 되어 재시도가 가능한 업로드.
  • 지하철을 걸어서 10분 후에 나오는 경우, 업로드 프로세스가 앞서 중단된 부분부터 시작된다. 이것은 사용자에게 완전히 매끄럽게 처리된다.
  • 클라이언트는 업로드 API를 사용하여 업로드 세션을 초기화 한다. 백엔드는 업로드 세션 전체에서 사용하는 세션을 식별하기 위한 식별자 mediaId를 준다.
  • 이미지는 여러 세그먼트로 분할된다. 여기에서는 3개의 세그먼트로 하자. API를 사용하여 세그먼트가 추가되어 각각의 추가 명령을 호출하면 세그먼트 인덱스를 돌려준다. 모든 추가 세그먼트는 같은 mediaId를 가진다. 업로드가 완료되면 최종 업로드가 완료되고, 해당 미디어는 사용할 준비가 된다.
  • 이 방법은 네트워크의 문제에 대해 더 탄력성을 가질 수 있다. 각각의 세그먼트가 다시 시도 될 수 있다. 네트워크가 어떤 이유로 다운되면 중단하고 네트워크 상태가 정상으로 돌아왔을 때 중단 된 세그먼트를 선택할 수 있다.
  • 간단한 방법으로 막대한 이익을 얻는다. 50KB 이상의 파일에서 이미지 업로드 결함이 브라질에서 33%, 인도에서는 30%, 인도네시아에서는 19%로 감소했다.
2. 읽기 절차
MinaBird라는 CDN Origin Server를 도입했다.
  • MinaBird는 ImageBird과 VideoBird와 통신 할 수 있기 때문에 이미지 크기나 동영상 형식의 변화가 없는 경우 즉시 생성하는 것이 가능하다.
  • MinaBird는 클라이언트 요청의 처리 방법에 있어서, 더 유연하고, 더 다이나믹하다. DMCA 삭제(미국의 디지털 밀레니엄 저작권법에 따라 저작권 침해 인터넷 콘텐츠를 삭제하는 것)가 있었다고 해도, 예를 들어 미디어의 특정 부분에 대한 액세스를 차단하거나 액세스를 다시 활성화시키는 것이 매우 쉽게 할 수 있다.
  • 즉시 이미지의 변형이나 트랜스 코딩을 할 수 있는 것만으로도, Twitter는 스토리지에 대해 더욱 잘 대처할 수 있게 되었다.
    • 그때 그때 필요에 따라(스크린의 변화에 따른) 이미지 변형본을 생성한다는 것은 BlobStore에 전체 변형본을 저장할 필요는 없다는 것이다.
    • 원본 이미지는 삭제될 때까지 유지된다. 변형본은 20일 밖에 저장되지 않는다. 미디어 플랫폼 팀은 수 많은 연구와 조사를 통해 최선의 만료 기간을 찾았다. 요청된 모든 사진 중 절반은 최대 15일내의 것들이다. 그 이상의 기간 이미지를 유지한다는 것은 수확 체감 현상을 가져온다. 그 어느 누구도 오래된 미디어를 요청하지 않는다. 15일이 지나면 롱테일이 된다.
    • TTL(유효 기간)도 없고, 만료 기간도 없다면 미디어 스토리지는 매일 6TB씩 증가해 간다. 필요에 따라 변형본을 만드는 느슨한(Lazy) 방식을 적용하면 하루 스토리지 증가분은 1.5TB 된다. 20일 TTL 방식은 느슨한(Lazy) 방법보다 더 스토리지를 사용하지 않기 때문에 스토리지 비용은 거의 없지만, 계산이라는 관점에서 보면 크다. 느슨한(Lazy) 방식으로 읽을 전체 변형본을 계산하는 것은 데이터 센터 당 150개의 ImageBird 머신이 필요한 반면, 20일 TTL을 적용하면 75개 정도만 필요로 한다. 그래서 20일 TTL 적용 접근법은 스토리지와 계산의 균형점이 되는 최적인 것이다.
    • 스토리지와 계산 관점에서 절약이 비용을 줄여주고 있기 때문에, Twitter의 2015년에는 20일 TTL 도입으로 600만 달러를 절약했다.
클라이언트의 개선(Android)
  • Google이 만든 이미지 포맷, WebP를 사용한 실험을 6개월간 하였다.
    • 이미지는 PNG나 JPEG을 대응했을때보다 평균 25% 작았다.
    • 작은 이미지 크기를 사용하여 네트워크 스트레스를 감소시켜 신흥 시장에서 특히 사용자 참여의 증가가 보고 되었다.
    • iOS에 대해서는 지원하지 않았다.
    • Android 4.0+만 지원했다.
    • 플랫폼의 지원 부족으로 WebP 지원 비용이 늘어났다.
  • Twitter도 시도했지만, 또 다른 옵션은 Progressive JPEG이다. 이것은 연속적인 스캔을 통해 렌더링을 한다. 첫번째 스캔은 렌더링이 고르지 않을수도 있지만, 연속적으로 스캔해 나가면 렌더링이 개선된다.
    • 더 나은 성능.
    • 백엔드에서 지원하기 쉽다.
    • 기존의 JPEG보다 60% 인코딩이 느리다. 인코딩은 1번 밖에 일어나지 않고 처리는 여러번 발생하기 때문에 이것은 큰 문제가 되지는 않는다.
    • 투명 부분을 지원하지 않기 때문에 투명 PNG가 남게 되지만, 그 밖의 모든 것은 Progressive JPEG이 커버한다.
    • 클라이언트 측에서는 Facebook의 Fresco 라이브러리가 지원을 제공받고 있다. Fresco 대해 좋게 말하고 싶은 것들이 많이 있다. 2G에 연결한 결과는 꽤 좋았다. 첫번째 PJPEG 스캔은 겨우 10kb 밖에 필요로하지 않았기 때문에 로딩 시간도 길지 않았다. 기본 파이프 라인은 PJPEG 파일이 이미지로 인식해 표시되기까지 아무런 표시 없이 대기 타임이 있다.
    • 트윗의 상세 뷰에서 로드에 대한 지속적인 실험 결과 50 로드 시간당 9% 감소했다. 95 로드 시간당 27% 감소했다. 액세스 실패율은 74%나 감소했다. 연결 속도가 느린 사용자에게 정말 큰 만족이었다.

용어 정의

  • 일급 객체(first class object) : 크리스토퍼 스트래치(Christopher Strachey)라는 영국 컴퓨터 과학자가 만들어낸 용어로, 변수나 데이터 구조 안에 담을 수 있고, 파라미터로 전달할 수 있으며, 반환값(return value)으로 전달할 수 있고, 할당에 사용된 이름과 관계없이 고유하게 식별이 가능하고, 동적으로 프로퍼티 할당이 가능하면 일급 객체라 한다.

설정 기반의 플러그인 시스템(Java)



Java에는 OSGi라는 동적 모듈 플러그인 시스템을 구현할 수 있도록 가능케하는 오픈 소스가 있지만 다루기 복잡한 면이 있어, Guice, Airlift, ServiceLoader를 활용해 간단하면서 요긴한 플러그인 시스템을 만들어 본다. 기본적인 제약사항은 단일 프로세스에, 플러그인 추가나 제거시 프로세스를 재시작해야 하며, 정의된 플러그인만 가능하다는 점을 일러둔다.

필요한 오픈 소스

1. Guice(구글에서 만든 JSR330 (Dependency Injection for Java)의 레퍼런스)
  • Dependency Injection(의존성 주입).

2. Airlift(페이스북에서 만듬)
  • Distribute Service Framework.
  • Distribute Service Framework이지만, 여기서는 Bootstrap(설정 정보와 함께 플러그인 모듈 로딩), Dependency Injection(의존성 주입), Configuration 기능 등을 사용함.

3. Java 표준 API java.util.ServiceLoader
  • 클래스 로더를 플러그인 시스템화할 수 있도록 만들어 줌.
  • JDK 1.6이상에서 제공.

설정 기반의 플러그인 구현

1. 설정 기반의 플러그인 추상 클래스 정의 : PluginModule
public abstract class PluginModule implements ConfigurationAwareModule {
  private ConfigurationFactory configurationFactory;
  private Binder binder;

  @Override
  public synchronized void setConfigurationFactory(ConfigurationFactory
    configurationFactory) {
    this.configurationFactory = checkNotNull(configurationFactory,
      "configurationFactory is null");
  }

  @Override
  public void configure(Binder binder) {
    checkState(this.binder == null, "re-entry not allowed");
    this.binder = checkNotNull(binder, "binder is null");
    try {
      setup(binder);
    } finally {
      this.binder = null;
    }
  }

  // 설정 파일과 Config 클래스 매핑 처리
  protected synchronized  T buildConfigObject(Class configClass, String prefix) {
    configBinder(binder).bindConfig(configClass, prefix != null ?
      Names.named(prefix) : null, prefix);
    try {
      Method method = configurationFactory.getClass().getDeclaredMethod("build",
        Class.class, String.class, ConfigDefaults.class);
      method.setAccessible(true);
      Object invoke = method.invoke(configurationFactory,
        configClass, prefix, ConfigDefaults.noDefaults());
      Field instance = invoke.getClass().getDeclaredField("instance");
      instance.setAccessible(true);
      return (T) instance.get(invoke);
    } catch (NoSuchMethodException | IllegalAccessException
    | InvocationTargetException | NoSuchFieldException e) {
      throw new IllegalStateException("configuration error. ", e);
    }
  }

  // 클래스 바인딩
  protected abstract void setup(Binder binder);

  @NotNull
  public abstract String name();

  public abstract String description();
}

buildConfigObject 함수는 설정 파일을 설정 파일 클래스와 매핑시켜주고, setup 재정의를 통해 실제 구현 클래스들을 바인딩한다.

2. 확장 플러그인 구현 클래스 : MysqlModule
@AutoService(PluginModule.class)
@ConditionalModule(config = "plugin.adapter.mysql", value = "true")
public class MysqlModule extends PluginModule {
  @Override
  protected void setup(Binder binder) {
    JDBCConfig config = buildConfigObject(JDBCConfig.class, "plugin.adapter.mysql");
    binder.bind(JDBCPoolDataSource.class)
   .annotatedWith(Names.named("plugin.adapter.mysql"))
        .toInstance(JDBCPoolDataSource.getOrCreateDataSource(config));
    binder.bind(MessageService.class).to(MessageServiceImpl.class)
   .in(Scopes.SINGLETON);
  }

  @Override
  public String name() {
    return "MySQL Plugin Module";
  }

  @Override
  public String description() {
    return "MySQL Plugi Module";
  }
}

MySQL DB를 사용할 경우 설정(config.properties)에 "plugin.adapter.mysql=true"가 지정되면 MysqlModule 로딩되어 MessageService는 MySQL 커넥션 풀이 셋팅되어 해당 데이터 소스를 활용할 수 있게 된다.

3. 기타
- 데이터 처리 : MessageDao
public abstract class MessageDao implements GetHandle {
  private final Logger log = Logger.get(MessageDao.class);

  public List getChatMessages(Message message) {
    try (Handle handle = getHandle()) {
      return handle
        .createQuery(
        "SELECT mq_samsung_chat_id as id, from_subs_id, to_subs_id, mq_topic_id,
        payload, read_yn, bookmark_yn, read_date, send_date\n"
          + "FROM mq_samsung_chat\n" + "WHERE to_subs_id = :toSubsId LIMIT 5")
        .bind("toSubsId", message.getToSubsId()).map(new MessageMapper()).list();
    } catch (Exception e) {
      log.error(e, "getChatMessages caught exception");
      return null;
    }
  }
}
 

데이터 핸들링 부분은 JDBI를 통해 처리했다.

- 커넥션 풀은 HikariDataSource를 사용했으며, JDBCPoolDataSource 소스를 보면 확인할 수 있다.

- SystemRegistryGenerator
config.properties 파일 자동 생성해 준다.

4. 실제 사용
- Main 클래스
public class Main {
  private static final Logger log = Logger.get(Main.class);

  public static Set getModules() {
    ImmutableSet.Builder builder = ImmutableSet.builder();
    ServiceLoader modules = ServiceLoader.load(PluginModule.class);
    for (Module module : modules) {
      if (!(module instanceof PluginModule)) {
        throw new IllegalStateException(
            format("Module 은 PluginModule 의 하위 클래스여야 함. %s",
            module.getClass().getName()));
      }
      log.info("Module = " + module.getClass().getName());
      PluginModule pluginModule = (PluginModule) module;
      builder.add(pluginModule);
    }
    return builder.build();
  }

  public static void main(String[] args) throws Throwable {
    if (args.length > 0) {
      System.setProperty("config", args[0]);
    }
    Bootstrap app = new Bootstrap(getModules());
    app.requireExplicitBindings(false);
    Injector injector = app.strictConfig().initialize();
    List messages = injector.getInstance(MessageService.class).getChatMessages(
        new Message(null, null, "test", null, null, null, null, null, null));
    log.debug("messages=" + messages.toString());
  }
}

getModules 함수는 플로그인 구현 클래스들을 로딩하고, main에서 실제 간단한 플러그인 시스템을 사용하는 부분이 정의되어 있다.

- 전제 소스 흐름 정의
Guice를 사용한 이유는 DI 패턴을 통해 행태와 의존성을 분리하는데 있다.
위 소스에서 MessageService가 생성자에서 MySQL dataSource를 선택하는 것을 MessageService가 선택하는 것이 아니고 생성자의 인수로 전달된다. bind를 통해 MessageService의 생성자에 @Inject를 붙이면, Guice는 JDBCPoolDataSource 데이터 소스를 찾아달라고 한다. 그래서 mysql 설정이 되어 있다면 MySQLModule에서 bind 된 JDBCPoolDataSource의 인수를 MessageService의 생성자로 전달해 준다.

그리고 MessageService의 인스턴스를 얻어 getChatMessages 함수를 호출해 MySQL의 데이터베이스에서 데이터를 가지고 온다.

- 실행 환경
  • Main class : com.mimul.plugin.module.Main
  • vm option : -Dlog.levels-file=src/main/resources/log.properties
  • arguments : src/main/resources/config.properties

- 실행 결과
Module = com.mimul.plugin.module.MysqlModule

Bootstrap  Loading configuration
Bootstrap  Initializing logging
HikariDataSource  generic-jdbc-query-executor - is starting.
Bootstrap  PROPERTY                                          RUNTIME
Bootstrap  plugin.adapter.mysql.connection.max-idle-timeout  null
Bootstrap  plugin.adapter.mysql.connection.max-life-time     null
Bootstrap  plugin.adapter.mysql.data-source                  null
Bootstrap  plugin.adapter.mysql.driver_class_name            com.mysql.jdbc.Driver
Bootstrap  plugin.adapter.mysql.max_connection               5
Bootstrap  plugin.adapter.mysql.password                     testadmin
Bootstrap  plugin.adapter.mysql.test_query                   select 1
Bootstrap  plugin.adapter.mysql.url    jdbc:mysql://localhost:3306/test
Bootstrap  plugin.adapter.mysql.username                     test

Main  messages=[Message(id=1, fromSubsId=clientId-23090, toSubsId=1...)]

Main 클래스를 실행하면, 위와 같이 MySQL DB의 데이터를 5건 가져온다.

5. 전체 소스
- PluginSystem.
이 플러그인 모듈은 Netty 기반으로 REST API를 구성하는데 활용할 예정이며, 또한, 프로세스를 내리지 않고도 추가, 제거 가능하게 보완할 것입니다. 어느 정도 완료되면 소스를 공개하겠습니다. 그리고 REST API를 결합한 모듈은 저희 Data Visualization 제품인 U2에도 적용되어 있습니다.

데이터에 현혹되지 않고, 데이터를 잘 활용할수 있는 14가지 룰


요즘 데이터에 관련된 부분을 생각하고 고민하다보면서 데이터를 바라보는 시각에 도움되는 글이 있어서 원저자의 허락하에 번역해 봅니다. 원저는 14 rules for data-driven, not data-deluded, marketing"입니다.
이 글을 쓴 SCOTT BRINKER는 년도별 "Marketing Technology Landscape Supergraphic"으로도 유명합니다.

1. 데이터 기반보다는 고객 기반

데이터 기반 마케팅은 분명 좋다. 그러나 (데이터 기반) 마케팅의 목적은 고객을 유치하고 그들에게 더 나은 사용자 경험을 제공하여 그들을 유지시키는 것이다. 결국엔 데이터 기반보다 고객 기반 마케팅이 더 낫다. 고객과 데이터 기반 마케팅은 상호 배타적이진 않다. 다르게 말하면, 데이터 기반은 고객 기반을 위한 수단에 지나지 않는다. 데이터로 인해 미궁의 속으로 들어갔을 땐 생각을 멈추고 "고객에게 어떻게 좋은가?"를 물어봐라. 데이터보다는 고객의 목소리가 더 중요한 법이니깐.

2. 모든 데이터는 동일하게 만들어지지 않는다.

데이터는 논쟁을 종료시킨다는 말이 있다. 적어도 절대적 진실적(정확성)이었을때만 그렇다는 이야기다. 하지만, 데이터는 마음만 먹으면 어떤 주장에도 뒷바침할 수 있는 자료를 찾게 해준다. 다만, 중요한 것은 데이터의 정확성과 관련성의 차이다. "In A Big Data World, Marketers Know Shockingly Little About Us"를 생각해 보자.
좀더 나은 교훈은: 가장 정확하고 관련성이 높은 데이터가 논쟁을 종식시킨다. 하지만, 논쟁이 끝난 다음엔 결단이 기다린다. 즉, 데이터 분석은 단지 결단을 위한 조그만 수단일뿐이다.

3. 데이터는 단순한 역사에 불과하다.

데이터는 일어난 것을 우리에게 말해준다. 일어날 것에 대해서는 말해주지 않는다. 그렇다. 하지만, 우리는 데이터의 역사로부터 배워야 한다. 그리고 과거 데이터로부터 정확한 미래를 예측가능해야 한다. 그러나 세계는 끊임없이 변하고, 데이터가 나타내는 역사의 상황은 오늘, 내일의 환경과도 엄연히 다르다. 중요한 건, 블랙스완(과거의 경험에 의한 판단이 행동의 기준이 되어서는 안된다는 것)의 경고처럼, 그 방법이 될 순 있어서 항상 될 수 있는 건 아니다.

4. 데이터는 항상 불완전하다.

물론 구체적인 하나의 데이터셋은 완전할 순 있다. 지난 3년동안 분기별 판매액을 나에게 줘라는 것은 완전한 데이터 셋이다. 하지만, 진공상태에서 존재하는 것은 아무것도 없다. 당신이 데이터를 가지고 의사결정을 할 때 데이터가 의사 결정에 관련된 유일한 것은 있을 수 없다. 의사 결정에 관련된 더 많은 데이터가 있어야 한다. 하지만, 모든 것을 손에 넣을 순 없다. 이것을 인정하면 우리가 가지고 있는 데이터로 그림은 그릴수 있지만 기껏해야 인상파 화가의 그림 정도이다. 종종 그것은 현대 미술일 수도 있다. 즉, 모든 데이터를 손에 넣을 수 없기 때문에 때로는 주어진 데이터속에서 의사 결정을 할 수 있는 용기가 필요하다.
그리고 나의 데이터에서 의사 결정할 카드가 적다는 것은 인식할 필요가 있다. 그럼 불완전한 데이터에서 많은 시간을 투자해 의사 결정에 필요할 카드를 많이 만들 수 있게 된다.

5. 데이터는 객관적이다. 그러나, 그 집합이나 해석은 주관적이다.

데이터는 객관적이라는 위험한 환상을 우리에게 준다. 객관적으로 모든 사람은 스프레드시트의 같은 데이터를 바라본다. 내가 순 추천 고객 지수 7을 보여줬다면, 내가 보여준 7에 대해서는 이견이 없다. 그러나, 무슨 데이터를 수집했고, 언제 어떻게 수집했는지, 누구로부터 수집한지에 대한 데이터에 대한 주관적인 선택이 포함되어 있다. 다른 한편으로는 우리가 어떻게 해석했는지도 주관적이다.
"The Hidden Biases in Big Data"라는 기사에서 많은 좋은 사례를 보여준다. "data fundamentalism(데이터 원리주의)"라는 화려한 문구로 소개되곤 한다. 데이터는 항상 객관적이라는 환상은 버리는 것이 낫다.

6. 하나의 데이터셋은 무한한 스토리를 만들수 있다.

마케터들은 스토리텔러이다. 그것은 일반적으로 좋은 일이다. 데이터는 스토리를 더욱 설득력있게 만든다. 하지만, 우리는 데이터를 주관적으로 해석할 수 있기 때문에 우리는 주위가 원하는 거의 모든 이야기를 만들수 있기도 하다. 인정하지만, 몇몇 스토리는 다른 것들보다 더 신뢰한다. 이것은 퍼지 라인이며 귀납의 문제(반복되는 경험의 패턴에 따른다는 사실에 촛점을 맞추고, 과거로부터 미래를 추론(Reasoning) 하는 것으로 규정되지만, 넓은 의미에서는, 관찰된 것을 기반으로 해서 아직 관찰되지 않은 것에 대한 결론에 이르는 것을 포함된다. 귀납법을 위한 틀로서 확률이론을 사용하는 베이즈 정리(Bayes' Theorem) 등이 있다.)이다.
그리고 이는 수세기 동안 과학자나 철학자들에게 성가시게 해왔다. 말할 필요도 없이 쉬운 답은 없다. 그러나 관점을 유지하는데 도움이 된다: 데이터 주위에서 나온 어떤 이야기는 그것에 대해 이야기할 수 있는 유일한 이야기는 결코 없다.

7. 전략은 선택의 문제이고, 좋은 데이터는 그 선택을 도와준다.

좋은 전략은 선택을 하기 위한 프레임워크이다. 그리고 좋은 데이터는 좋은 선택을 지원해주는 정보이다. 우리의 전략을 추구하는데 있어서 선택에 도움이 되지 못하는 데이터는 방해의 산물이다.

8. 실험은 인과관계를 발견하는 최고의 수단이다.

상관 관계가 곧 인과 관계로 이어지지 않는다. 데이터 과학자라면 누구나 알고 있는 일이지만, 그러나 마케터들은 더 많은 고객이 우리와 함께 더 많은 사업을 수행하게 되는 인과 관계를 알고 싶어한다. 데이터를 통해 있음직한 상관 관계가 발견되었을 경우 우리는 어떻게 해야할까? 우리는 통제된 실험을 실행한다. 실질적으로 가능한 만큼의 모든 변수를 가지고, 증명 또는 우리의 가설을 반증할 수 있는 대안을 테스트한다. Google은 매년 10,000건의 실험을 수행한다. 그것은 우리가 만들수 있는 가장 강력한 데이터이다. 이것은 Big Data보다 Big Test를 더 크게해야 하는이유이기도 하다.

9. 대시보드 뿐만 아니라, 앞 유리도 보자.

정량적으로 우리는 운전하는 동안 속도계 등의 대시보드(데이터)를 봐야하고, 정성적으로 앞 유리의 시야(경험)도 봐야한다. 물론 둘다 해야한다. 또한 실제로 운전할 때 정량(데이터)과 정성(경험)의 균형을 자연스럽게 유지해야 한다. 우리는 마케팅에서도 똑같은 균형을 위해 노력해야 한다.

10. 데이터의 정확성과 관련성은 시간과 함께 쇠퇴된다.

특히나 마케팅 영역에서는 데이터의 유통기한은 짧다. 내가 새로운 자동차를 검색하는 그 주에 나에게 자동차 광고를 타겟팅하는 것은 의미가 있지만, 6개월 후에 자동차 마케팅 대상에서 나를 식별해준 데이터는 단지 과거의 화석에 지나지 않는다. 내가 자동차 마케팅 대상에 포함된 것을 안다는 것은 약간의 가치가 있을지도 모른다.
하지만 나는 여전히 가치가 없다고 생각하고 있다. 가치가 없는것보다 더 나쁘다. 왜냐하면, 나에게 나쁜 의사 결정을 유도하기 때문이다. 정확성과 관련성은 데이터를 가치있게 만들지만, 속성은 시간이 지남에따라 변한다.

11. 데이터는 탐색(exploration, why?)나 확인(confirmation, what?) 작업에 유용할 수 있다.

데이터는 탐색과 확인시에는 서로 다르게 접근해야 한다. 탐색시에는 새로운 가설에 영감을 얻을 수 있는 패턴, 인사이트나 아이디어, 발견할 것들을 찾는다. 확인시에는 어떤 일이 일어날지 안일어날지, 혹은 어느 정도 일어날지에 대해 검증하는 것이다. 그러나 통제된 실험에서 테스트한 가설이 확인되지 않았다면, 확인은 무엇이 일어났고 왜 안됐는지 말해주어야 한다. 같은 데이터로 하나의 환경에서 확인에 사용될 수도 있고 또 다른 환경에서 탐색에도 사용될 수 있다. 다만 당신이 하고 있는 것에 대해서는 알아야 한다.

12. 하나의 시계를 가진 사람은 지금의 시간을 알 수 있지만, 두 시계를 가진 사람은 알 수 없다.

이 법칙은 마케터들이 알아야할 7가지 기술 법칙들 중의 하나인 Segal의 법칙(정보량이 너무 많으면 정보들이 돌아가는 것을 모르게 된다)으로 알려져 있다. 세상엔 혼돈의 데이터가 많이 있다. 준비된 두개의 다른 웹 분석 패키지들로부터 지표를 얻고자하는 사람은 정확성을 입증할 수 있다. 이 다른 두 툴은 동일 현상을 다르게 측정한다. 왜 두 툴은 서로 다른 가치있는 통찰력을 보여주는지에 대한 이해 - 즉, 중요한 차이에 대한 이해력을 갖추는데 투자하는 것은 가치가 있을수도 있다. 하지만, 매번 모순을 추적하기에는 수확 체감이 있다. 많은 경우 완벽하게 정확한 데이터는 필요없다. 하지만 충분히 정확한 데이터는 좋은 의사 결정을 낳게 한다.

13. 모델은 현실이 아니다.

데이터는 주장을 표현하기 위한 것이고 현실성은 없다. 기껏해야 현실은 반영하지만, 데이터는 변형되기도 쉽다. 과학자이면서 철할자인 Alfred Korzybski가 "지도는 영토가 아니다"라는 말을 했다. 재해석해보면, 물리적 세계(영토)를 반영한 가상적인 공간이 지도인것처럼, 만드는 사람의 심리적 상태나 개인적인 지식의 편차에 의해 지도의 모형은 변형될 수 있다는 의미로 받아들여질 수 있다. 지도나 데이터처럼 실제적인 것이 아님에도 불구하고 사람들은 사용하길 원한다.

위대한 통계학자 George E. P. Box는 "모든 모델들은 잘못된 것이지만, 일부 유용한 것도 있다."고 말했다. 그러나, 데이터가 나타내는 것에 대해 정확성에 대한 긍정적인 의구심을 유지하기 위해 신중을 기해야 한다. 실제로 우리들은 데이터가 현실과 다르다는 것을 알리는 의미로, 임계치를 벗어나는 등의 신호에 대해 알람 주기를 원할 것이다. 그리고 "지도와 지형이 다를 경우 지형을 우선시해라."라는 스위스 군대의 격언을 업급하면서 현실(고객)의 목소리가 중요시하고 있다.

14. 데이터 시각화는 명확하게 하기도 하지만, 혼선을 주기도 하고, 집중을 분산시키기도 한다.

차트, 그래프, 인포그래픽 등의 데이터 시각화는 강력하지만, 양날의 검이기도 하다. 시각화는 지금까지 우리 인간에게 데이터에서 패턴을 발견하는데 가장 효과적인 방법이다. 불행하게도 의도적이든, 우연이든 패턴은 실제로 정확하지 않은 것을 보여줄 수 있다. 데이터 시각화는 과학이며 그 자체가 예술이다. 다양한 각도와 인사이트력이 필요하다.

비주얼에 대한 문학적 스킬을 배울려면 Stephen Few, Kaiser Fung, Edward Tufte, Nathan Yau, Fernanda Viégas and Martin Wattenberg의 글을 읽으면 좋다. 그들은 여러분들의 데이터 인사이트에 많은 도움을 줄 것이다.
Tags : ,