스토리 홈

인터뷰

피드

뉴스

조회수 1583

RxJava2 함수 파헤치기!

Overview지난 글 Rxjava를 이용한 안드로이드 개발에서는 RxJava의 Android 연결 방법과 기본적인 사용법을 다뤘습니다. 이번 글에서는 RxJava의 강력하고 다양한 함수들을 살펴보고자 합니다. Android에서 복잡하게 구현되는 내용들을 단 몇 개의 함수로 처리할 수 있는 RxJava를 꼭 사용해보길 권합니다.1. just2. fromArray/fromlterable3. range/rangLong4. interval5. timer6. map7. flatMap8. concatMap9. toList10. toMap11. toMultiMap12. filter13. distinct14. take15. skip16. throttleFirst17. throttleLast18. throttleWithTimeout참고: 공통적으로 사용하는 구독(수신) 클래스는 아래와 같습니다.static class CustomSubscriber<T> extends DisposableSubscriber<T> { @Override public void onNext(T t) { System.out.println(Thread.currentThread().getName() + " onNext( " + t + " )"); } @Override public void onError(Throwable t) { System.out.println(Thread.currentThread().getName() + " onError( " + t + ")"); } @Override public void onComplete() { System.out.println(Thread.currentThread().getName() + " onComplete()"); } } 1. just파라미터를 통해 받은 데이터로 Flowable을 생성하는 연산자입니다. 최대 10까지 전달할 수 있고, 모든 데이터가 수신되면 onComplete() 수신됩니다. 기본적인 Flowable 생성자 함수로 볼 수 있으며 단순 작업에서 많이 사용합니다.public static void just() { //파라미터 값을 순차적으로 송신하는 Flowable 생성 Flowable<String> flowable = Flowable.just("A", "B", "C", "D", "E", "F"); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onNext( F ) main onComplete() 2. fromArray/fromIterablefromArray, fromIterable 함수는 파리미터로 배열 또는 Iterable(리스트 등)에 담긴 데이터를 순서대로 Flowable을 생성하는 연산자입니다. 모든 데이터를 순차적으로 송신 후 완료됩니다. 반복적인 데이터 변환 작업 같은 경우 for 문 대신 대체할 수 있습니다. 결과를 보면 main Thread 에서 작업 결과가 나오지만, flatMap 을 사용한다면 별도의 Thread로 main Thread의 부하를 막을 수 있습니다.1. fromArray public static void fromArray() { //fromArray 배열로 파라미터를 전달 받는다. Flowable<String> flowable = Flowable.fromArray("A", "B", "C", "D", "E"); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onComplete() 2. fromIterable public static void fromIterable() { List<String> list = Arrays.asList("A", "B", "C", "D", "E"); //fromIterable 리스트로 파라미터를 전달받는다. Flowable<String> flowable = Flowable.fromIterable(list); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onComplete() 파라미터와 함수는 다르지만 동일하게 처리된다. 3. range/rangLongrange 함수는 지정한 숫자부터 지정한 개수만큼 증가하는 Integer 값 데이터를 송신하는 Flowable를 생성합니다. rangLong 함수는 range와 동일하며 데이터 타입은 Long을 사용합니다. 두 함수 데이터 송신을 마치면 onComplete를 송신합니다.1. range public static void range() { //range(int start, int count) //start : 시작 값 //end : 발생하는 횟수 Flowable<Integer> flowable = Flowable.range(10, 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( 10 ) main onNext( 11 ) main onNext( 12 ) main onNext( 13 ) main onNext( 14 ) main onComplete() 2. rangLong public static void rangeLong() { //range(int start, int count) //start : 시작 값 //end : 발생하는 횟수 Flowable<Long> flowable = Flowable.rangeLong(10, 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( 10 ) main onNext( 11 ) main onNext( 12 ) main onNext( 13 ) main onNext( 14 ) main onComplete() 4. interval지정한 간격마다 0부터 시작해 Long 타입 숫자의 데이터를 송신하는 Flowable을 생성합니다. 데이터는 0, 1, 2, 4 순차적으로 증가된 데이터를 송신합니다. Android 에서는 반복적인 작업인 TimerTask를 대신해서 interval로 간단하게 처리할 수 있습니다. UI 변경이 필요한 부분에서는 interval scheduler를 AndroidSchedulers.mainThread() 를 변경해 적용할 수 있습니다.public static void interval() { //(long time, TimeUnit unit, Scheduler scheduler) //time : 발생 간격 시간 //unit : 간격 시간 단위 //scheduler : 발생 scheduler를 변경하여 사용할 수 있습니다. // ex)AndroidSchedulers.mainThread() // - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 // 1초 간격으로 데이터 요청을 송신하다. Flowable<Long> flowable = Flowable .interval(1000L, TimeUnit.MILLISECONDS).take(10); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onNext( 6 ) RxComputationThreadPool-1 onNext( 7 ) RxComputationThreadPool-1 onNext( 8 ) RxComputationThreadPool-1 onNext( 9 ) 5. timertimer 함수는 호출된 시간부터 일정한 시간 동안 대기하고 Long 타입 0을 송신 및 종료하는 flowable을 생성합니다. interval이 조건까지 반복적으로 송신한다면, timer는 한번만 송신하고 종료됩니다.public static void timer() { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd hh:mm ss"); System.out.println("현재시간 : " + simpleDateFormat.format(System.currentTimeMillis())); //(long time, TimeUnit unit, Scheduler scheduler) //time : 발생 간격 시간 //unit : 간격 시간 단위 //scheduler : 발생 scheduler를 변경하여 사용할 수 있습니다. // ex)AndroidSchedulers.mainThread() Flowable<Long> flowable = Flowable.timer(1000L, TimeUnit.MILLISECONDS); //구독을 시작한다. flowable.subscribe(value -> { System.out.println(" timer : " + simpleDateFormat.format(System.currentTimeMillis())); }, throwable -> { System.out.println(throwable); }, () -> { System.out.println(" complete"); }); } 결과 현재시간 : 2019.04.29 09:09 56 timer : 2019.04.29 09:09 57 complete 6. mapFlowable 에서 송신하는 데이터를 변환하고, 변환된 데이터를 송신하는 연산자입니다. 하나의 데이터만 송신할 수 있으며, 반드시 데이터를 송신해야 합니다. 혹여 송신되는 데이터가 null 을 포함하면 map 대신 아래의 flatMap 을사용하는 것이 좋습니다.public static void map() { Flowable<String> flowable = Flowable.just("A", "B", "C", "D", "E") //map(Function mapper) //mapper : 받은 데이터를 가공하는 함수형 인터페이스 //알파벳 값을 소문자로 변경하여 return 한다 .map(value -> value.toLowerCase()); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( a ) main onNext( b ) main onNext( c ) main onNext( d ) main onNext( e ) main onComplete() 7. flatMapflatMap은 map과 동일한 함수이지만, map과는 달리 여러 데이터가 담긴 Flowable을 반환할 수 있습니다. 또한 빈 Flowable를 리턴해 특정 데이터를 건너뛰거나 에러 Flowable를 송신할 수 있습니다.파라미터 mapper에서 새로운 Flowable의 데이터 전달이 아닌 다른 타임라인 Flowable로 작업하면 들어온 데이터 순서대로 출력을 지원하지 않습니다. 타임라인 Flowable(timer, delay, interval 등)에서는 가급적 사용을 피하거나, 순서에 지장이 없을 때 사용하는 것이 좋습니다.public static void flatMap() { Flowable<String> flowable = Flowable.range(10, 2) //flatMap(Function mapper, BiFunction combiner) //mapper : 받은 데이터로 새로운 Flowable를 생성하는 함수형 인터페이스 //combiner : mapper가 새로 생성한 Flowable 과 원본 데이터를 조합해 새로운 송신 데이트를 생성하는 함수형 인터페이스 //첫 번째 데이터를 받으면 새로운 Flowable를 생성한다. //take(3) : 3개까지만 발생한다. .flatMap(value -> Flowable.interval(100L, TimeUnit.MILLISECONDS).take(3), (value, newData) -> "value " + value + " newData " + newData); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( value 10 newData 0 ) RxComputationThreadPool-2 onNext( value 11 newData 0 ) RxComputationThreadPool-1 onNext( value 10 newData 1 ) RxComputationThreadPool-2 onNext( value 11 newData 1 ) RxComputationThreadPool-1 onNext( value 10 newData 2 ) RxComputationThreadPool-2 onNext( value 11 newData 2 ) RxComputationThreadPool-2 onComplete() 결과를 보면 각기 생성된 Flowable이 비동기식으로 송신 되기때문에 서로 다른 스레드에서 실행돼 데이터를 받는 순서대로 송신하지 않는다는 점을 주목하자 8. concatMap받은 데이터를 Flowable로 변환하고 변환된 Flowable을 하나씩 순서대로 실행해서 수신자에서 송신합니다. 다시 말해 여러 데이터를 계속 받더라도 첫 번째 데이터로 생성한 Flowable 의 처리가 끝나야 다음 데이터로 생성한 Flowable을 실행하는 것입니다.생성된 Flowable의 스레드에서 실행되더라도 데이터를 받은 순서대로 처리하는 것을 보장하지만, 처리 성능에 영향을 줄 수 있습니다.public static void concatMap() { Flowable<String> flowable = Flowable.range(10, 5) //map(Function mapper) //mapper : 받은 데이터를 가공하는 함수형 인터페이스 .concatMap(value -> Flowable.interval(100L, TimeUnit.MILLISECONDS).take(2) .map(data -> ("value : " + value + " data : " + data))); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( value : 10 data : 0 ) RxComputationThreadPool-1 onNext( value : 10 data : 1 ) RxComputationThreadPool-2 onNext( value : 11 data : 0 ) RxComputationThreadPool-2 onNext( value : 11 data : 1 ) RxComputationThreadPool-3 onNext( value : 12 data : 0 ) RxComputationThreadPool-3 onNext( value : 12 data : 1 ) RxComputationThreadPool-4 onNext( value : 13 data : 0 ) RxComputationThreadPool-4 onNext( value : 13 data : 1 ) RxComputationThreadPool-5 onNext( value : 14 data : 0 ) RxComputationThreadPool-5 onNext( value : 14 data : 1 ) RxComputationThreadPool-5 onComplete() 결과를 보면 생성된 Flowable 스레드와 데이터 순서대로 출력이 보장된다 것을 알 수 있다. 9. toListtoList는 송신할 데이터를 모두 리스트에 담아 전달합니다. 한꺼번에 데이터를 List로 가공해서 받기에 좋습니다. 하지만 많은 양의 데이터를 처리할 경우 버퍼가 생길 수 있고, 쌓은 데이터 때문에 메모리가 부족해질 수도 있습니다. 또한 수신되는 데이터는 하나이므로 Flowable이 아닌 Single 반환값을 사용합니다.public static void toList() { Single<List<String>> single = Flowable.just("A", "B", "C", "D", "E", "F") .toList(); // 구독을 시작한다. single.subscribe(new SingleObserver<List<String>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext()"); } @Override public void onSuccess(List<String> strings) { //최종 완료된 리스트를 순서대로 출력한다. for (String text : strings) { System.out.println(Thread.currentThread().getName() + " onSuccess( " + text + " )"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() main onSuccess( A ) main onSuccess( B ) main onSuccess( C ) main onSuccess( D ) main onSuccess( E ) main onSuccess( F ) 10. toMaptoMap은 송신할 데이터를 모두 키와 값의 쌍으로 Map에 담아 전달합니다. 나머지는 toList의 특징과 같습니다. 송신되는 데이터 타입은 Map에 담아서 송신하는데 동일한 key에서 value는 마지막 데이터가 덮어 씁니다. 요청되는 값보다 결과 값이 적을 수도 있습니다. List 값을 손쉽게 key, value로 분리할 수 있는 함수이기도 합니다.public static void toMap() { Single<Map<Long, String>> single = Flowable.just("1A", "2B", "3C", "1D", "2E") //toMap(Fuction keySelector, Function valueSelector, Callable mapSupplier) //keySelector : 받은 데이터로 Map에서 사용할 키를 생성하는 함수형 인터페이스 //valueSelector : 받은 데이터로 Map 넣을 값을 생성하는 함수형 인터페이스 .toMap(value -> Long.valueOf(value.substring(0, 1)), data -> data.substring(1)); //구독을 시작한다. single.subscribe(new SingleObserver<Map<Long, String>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext()"); } @Override public void onSuccess(Map<Long, String> longStringMap) { //최종 완료된 map을 순서대로 출력한다. for (long id : longStringMap.keySet()) { System.out.println(Thread.currentThread().getName() + " onSuccess( id : " + id + ", value " + longStringMap.get(id) + " )"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() main onSuccess( id : 1, value D ) main onSuccess( id : 2, value E ) main onSuccess( id : 3, value C ) 11. toMultiMap키와 컬렉션 값으로 이루어진 Map을 데이터로 변환하여 송신하는 함수입니다. 나머지 특징은 toList, toMap과 같습니다. toMap에서 중복되는 value를 관리하는 건 없었지만, value를 collection으로 관리하여 전달되는 데이터를 모두 수신할 수 있습니다.public static void toMultiMap() { Single<Map<String, Collection<Long>>> single = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) //toMultimap(Function keySelector, Function valueSelector) .toMultimap(value -> { //value가 홀수인지 짝수 인지 판단해서 key값을 리턴한다. if (value % 2 == 0) { return "짝수"; } else { return "홀수"; } }); //구독을 시작한다. single.subscribe(new SingleObserver<Map<String, Collection<Long>>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext( " + d + " )"); } @Override public void onSuccess(Map<String, Collection<Long>> stringCollectionMap) { for (String key : stringCollectionMap.keySet()) { StringBuffer stringBuffer = new StringBuffer(); for (long value : stringCollectionMap.get(key)) { stringBuffer.append(" " + value); } System.out.println(Thread.currentThread().getName() + " onSuccess( id : " + key + ", value " + stringBuffer.toString() + ")"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() RxComputationThreadPool-1 onSuccess( id : 짝수, value 0 2 4 ) RxComputationThreadPool-1 onSuccess( id : 홀수, value 1 3 ) 12. filterfilter는 받은 데이터가 조건에 맞는지 판단해 결과가 true인 값만 송신합니다. 위의 just, fromArray, interval이 반복적인 케이스였다면, filter는 if문처럼 조건문의 역할을 할 수 있습니다. 반복문 함수와 조건문 함수를 같이 사용해 몇 줄 안에 for, if와 똑같이 구현할 수 있죠.public static void filter() { Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) //짝수만 통과한다. 3개만큼 .filter(value -> value % 2 == 0).take(3); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 13. distinct이미 처리된 데이터를 다시 볼 필요가 없을 때 사용하는 함수입니다. 송신하려는 데이터가 이미 송신된 데이터와 같다면 해당 데이터는 무시합니다. 이 함수는 내부에서 HashSet으로 데이터가 같은지 확인합니다.public static void distinct() { Flowable<String> flowable = Flowable.just("A", "a", "B", "b", "A", "a", "B", "b") //distinct(Function keySelector) //keySelector : 받은 데이터와 비교할 데이터를 확인하는 함수 //모두 소문자로 변환하여 알파벳 기준으로 데이터를 판단한다. .distinct(value -> value.toLowerCase()); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onComplete() 14. take1.taketake 함수로 지정된 횟수만큼 받은 데이터를 송신합니다. 지정된 횟수에 도달하면 완료를 송신해 처리 종료합니다.2.takeUntil지정된 조건까지 데이터를 송신하는 연산자입니다. 조건이 되면 완료를 송신해 종료합니다.3.takeWhile지정된 조건이 해당할 때만 데이터를 송신하는 연산자입니다.4.takeLast데이터의 끝에서부터 지정한 조건까지 데이터를 송신하는 연산자입니다.take 함수는 한 화면에 출력되거나 칠요한 데이터만큼 리스트에서 값을 하나씩 수신할 때 사용합니다. 예를 들어 화면에 데이터가 6개가 필요하면 take를 이용해 원하는 만큼의 데이터를 가져올 수 있습니다.Flowable.take(6) 또한 이후에 나올 skip 함수를 같이 사용하면 두 번째 화면에서 필요한 데이터를 6개 가져올 수 있습니다.Flowable.skip(6).take(12) 1. take public static void take() { // 100 밀리세컨드만큼 반복하며 총 5개를 출력후 종료한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 2. takeUntil public static void takeUntil() { // 100 밀리세컨드만큼 반복하며 값이 5가 될때까지 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .takeUntil(value -> value == 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onComplete() 3. takeWhile public static void takeWhile() { // 100 밀리세컨드만큼 반복하며 값이 5가 아닐경우까지 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .takeWhile(value -> value != 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 4. takeLast public static void takeLast() { //100밀리 세컨트만큼 반복하며 5개의 출력중 뒤에 2개만 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) .takeLast(2); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 15. skip1.skip함수로 지정된 횟수만큼 받은 데이터 송신을 제외합니다. 지정된 횟수가 초과되면 나머지 데이터를 송신합니다.2.skipUntil지정된 조건까지 데이터 송신을 제외하는 연산자입니다. 조건이 되면 나머지 데이터를 송신합니다.3.skipWhile지정된 조건이 해당될 때만 데이터 송신을 제외하는 함수입니다.4.skipLast데이터의 끝에서부터 지정한 조건까지 데이터 송신을 제외하는 함수입니다.take와 반대의 기능을 갖고 있습니다. 보통 페이저나 리스트에서 paging을 처리할 때는 take와 skip을 혼용합니다.1. skip public static void skip() { //100 밀리세컨드만큼 반복하며 5번 발행하고, 처음 2개를 제외합니다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) .skip(2); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 2. skipUntil public static void skipUntil() { //300밀리 세컨드만큼 반복하며 5개를 발행하고, 1000 밀리세컨드 제외 후 송신합니다. Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) .skipUntil(Flowable.timer(1000L, TimeUnit.MILLISECONDS)) .take(5); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-2 onNext( 3 ) RxComputationThreadPool-2 onNext( 4 ) RxComputationThreadPool-2 onNext( 5 ) RxComputationThreadPool-2 onNext( 6 ) RxComputationThreadPool-2 onNext( 7 ) RxComputationThreadPool-2 onComplete() 3. skipWhile public static void skipWhile() { //300밀리세컨드만큼 반복하며 5개를 발행하고, 데이터 3이 올때까지 데이터를 제외힙니다. Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) .skipWhile(value -> value != 3) .take(5); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onNext( 6 ) RxComputationThreadPool-1 onNext( 7 ) RxComputationThreadPool-1 onComplete() 4. skipLast public static void skipLast() { //1000 밀리세컨드만큼 반복하며 5개를 발행하고 마지막 2개는 제외합니다 Flowable<Long> flowable = Flowable.interval(1000L, TimeUnit.MILLISECONDS) .take(5) .skipLast(2); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onComplete() 16. throttleFirst데이터를 송신하고 지정된 시간 동안 들어오는 요청을 무시합니다. 이 함수는 View의 Event 처리에서 많이 사용됩니다. 중복되는 처리를 막기 위해 최초 실행 후 일정 시간 동안 View의 클릭 이벤트나 API 이벤트를 막을 수 있기 때문에 비동기 처리와 화면에 직접적인 피드백이 발생했을 때 throttleFirst를 자주 사용하고 있습니다. //데이터 요청이 30 밀리초마다 5번 발생합니다. //데이터 요청 발생시 100 밀리세컨트 동안 들어오는 데이터 요청을 무시합니다. // — 0 — 1 — 2 — 3 — 4 interval 30 밀리초 마다 // — — -*- — throttleFirst 100 밀리초 무시 Flowable<Long> flowable = Flowable.interval(30L, TimeUnit.MILLISECONDS) .take(5).throttleFirst(100L, TimeUnit.MILLISECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 17. throttleLastthrottleLast 함수는 데이터를 송신하고 지정된 시간 동안 들어오는 마지막 요청을 송신합니다. 이 함수도 throttleFirst처럼 반복적인 선택 이벤트 처리에 유용하게 사용할 수 있습니다. 간단하게 장바구니 카운트 변경을 요청할 때 마지막 변경 이벤트 데이터만 처리하면 되므로 값이 선택되고 일정 시간이 지났을 때 API를 요청해 리소스 낭비를 줄일 수 있습니다.public static void throttleLast() { //데이터 요청이 1 초 마다 6번 발생합니다. //데이터 요청 발생시 2 초 동안 들어오는 마지막 요청을 송신하다. // - 0 - 1 - 2 - 3 - 4 interval 1 초 마다 // - - -* - throttleLast 2 초의 마지막 값 송신 Flowable<Long> flowable = Flowable.interval(1, TimeUnit.SECONDS) .take(5) .throttleLast(2, TimeUnit.SECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 18. throttleWithTimeoutthrottleWithTimeout 함수는 데이터를 송신하고 지정된 시간 동안 다음 데이터를 받지 못하면 현재 데이터를 송신합니다. 완료 시엔 마지막 데이터를 송신하고 종료됩니다.public static void throttleWithTimeout() { Flowable<String> flowable = Flowable.<String>create(emitter -> { emitter.onNext("A"); Thread.sleep(1000L); // 1000 밀리세컨드 슬립 // 500 밀리세컨드 동안 데이터 다음 데이터 요청이 없으므로 A 송신 emitter.onNext("B"); Thread.sleep(300L); // 300 밀리세컨드 슬립 emitter.onNext("C"); Thread.sleep(300L); // 300 밀리세컨드 슬립 emitter.onNext("D"); Thread.sleep(1000L); // 1000 밀리세컨드 슬립 // 500 밀리세컨드 동안 데이터 다음 데이터 요청이 없으므로 D 송신 emitter.onNext("E"); Thread.sleep(100L); // 100 밀리세컨드 슬립 emitter.onComplete(); //완료 요청 시 마지막 데이터 송신 후 종료 }, BackpressureStrategy.BUFFER) .throttleWithTimeout(500L, TimeUnit.MILLISECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( A ) RxComputationThreadPool-1 onNext( D ) main onNext( E ) main onComplete() ConclusionRxJava에서 많이 사용되고, 또 알고 있으면 좋은 함수들을 살펴봤습니다. 브랜디에서도 이 함수들을 응용해 그동안 다양한 기능을 구현했고, 복잡한 함수도 사용하고 있습니다. 지금까지는 Flowable로 송신과 수신이 1 : 1 로 진행되었지만, 다양한 수신자를 사용해 하나의 Flowable로도 다른 화면에서 여러 수신자를 등록하여 반복적인 작업을 할 수 있습니다. 덕분에 같은 작업을 코드 중복 없이 간단하게 구현할 수 있죠.다음 글에서는 2개 이상의 Flowable을 결합해 사용하는 방법과 Android View에서 RxJava를 응용하는 방법, 구독을 관리하는 방법 등 Android에서 유용하게 쓰는 방법들을 알아보겠습니다.글고재성 팀장 | R&D 개발MA팀[email protected]브랜디, 오직 예쁜 옷만
조회수 2711

스타트업 CTO의 일

최근 다음과 같은 고민이 깊어졌다."나는 잘하고 있을까?""내가 지금 해야만 하는 중요한 일은 무엇일까?""나의 역할은 어디까지고, 무엇을 위임해야 할까?""어떤 사람을 채용해야 할까?"팀의 구성원이 떠나기도 했고, 회사도 여러 가지 도전을 받고 있으며, 나 자신의 정체도 느끼는 것이 고민의 시작이다. 위 질문들의 공통된 뿌리는 “나의 일은 무엇인가?”라는 질문이다.'나의 일'이라는 것은 '스타트업 CTO의 일'이다. 하지만 모든 스타트업의 CTO가 하는 일이 나와 같지는 않다. 스타트업은 다양한 단계가 있고, 목표로 하고 있는 시장도 제각각이다. 가지고 있는 기술, 목표로 하는 기술도 다르고, 구성원 또한 제각각이기 때문이다. 하지만 어느 정도 공통점이 있을 거라 생각한다. 혹시 이 글을 어느 스타트업의 CTO가 읽으신다면 자신의 일과 비교를 해봐 주시길 부탁드린다.본격적으로 시작하기 전에 이 글은 내가 앞으로 겪을 경험에 따라 많이 바뀔 수 있음을 미리 알려둔다.CTO?Chief Technology Officer의 준말이다. 경영진 중의 한 명으로 회사에서 기술과 관련된 모든 일을 관리, 책임진다. 여기에서 '기술과 관련된 모든 일'이라는 모호한 것을  들여다보기 위해서는 CTO의 역할을 좀 더 나눠 볼 필요가 있다. 다음과 같이 나눠보고 각각에 대해서 살펴보자.Technical Leader - 최고의 엔지니어Technical Businessman - 기술조직과 사업조직의 가교Team Manager - 팀장Product Manager - 프로덕트 관리자Technical Leader보통 CTO라 하면 가장 먼저 떠올리게 되는 역할이다. 기술기업의 경우 핵심 기술역량을 보유하고 있거나, 서비스 기업의 경우 주도적으로 서비스를 개발/운영해본 경험이 있어야 한다. 다음과 같은 역할이 요구된다.1) 기술 비전과 로드맵회사의 기술 비전을 세우고, 그 비전을 달성하기 위한 로드맵을 정하고 실행해야 한다. 실행을 위해서 기술 조직에 비전을 전달하고 공감을 얻어 낼 수 있어야 한다.2) 아키텍트회사가 만드는 서비스 아키텍처를 만들고 발전시켜 나가야 한다. 동시에 이 서비스가 동작하는 인프라 아키텍처를 셋업하고 견고하게 만들어야 한다. 이를 위한 개발 스택들을 결정하고 적용해야 한다.3) 좋은 기술 코치팀이 기술적으로 성장할 수 있는 환경을 갖추고 코칭을 해야 한다. 팀의 구성원이 기술적 목표를 높게 유지할 수 있도록 해야 한다.4) 시니어 개발자시니어 개발자로 다음과 같은 역할을 해야 한다.· 팀이 현재 겪고 있는 가장 어려운 문제를 풀 수 있어야 한다.· 회사의 핵심 기술을 이해하고 높은 퍼포먼스로 제품을 만들어야 한다.· 개발 효율을 높일 수 있는 환경을 갖춰야 한다. (DevOps)· 문서화를 해야 한다.Technical Leader로서 위와 같은 일들을 잘 하게 되면· 고도화되더라도 효율이 떨어지지 않는 시스템· 높은 제품의 성능· 높은 기능적 완성도· 경쟁력 있는 기술과 그 기술을 갖춘 팀을 얻을 수 있다. 위 일들은 조직이 커지게 되면 팀의 시니어 개발자들이 점점 나누어 가지게 된다. (단, '기술 비전과 로드맵'을 제외하고) 다르게 말하면 반드시 위의 역할을 잘 나누어 가질 수 있는 사람을 시니어 개발자로 채용해야 한다.Technical Businessman대부분의 스타트업은 기술을 기반으로 시장의 문제를 해결(=사업)한다. CTO는 기술조직과 사업조직이 함께 잘 굴러가기 위한 가교 역할을 해야 한다. 이를 위해서는 회사의 사업에 대한 이해와 사업적인 센스가 필요하다.1) 기술적인 조언시장의 문제를 기술적으로 해결하고자 한다면, 기술조직에서 아이디어가 나와야 한다. CTO는 보통 가장 많은 아이디어를 사업조직에 제공해야 한다. 또한 회사가 새로운 일을 시작하고자 할 때, 그 일이 기술적으로 가능한 일인지, 어느 정도 크기의 일인지를 추정해야 한다. 비록 추정이 조금 부정확하더라도 추정이 있어야 사업적 판단을 할 수 있다. 그리고 회사에서 그 추정을 가장 잘 해야 하는 사람이 CTO다.2) 사업을 기술조직에 전파“나는 왜 이것을 개발해야 하는가?”에 대한 개발자의 질문에 답을 해주어야 한다.(정확히는 물어보기 전에 알려주어야 한다.) 이 일을 하는 사업적인 이유를 충분히 설명해 주어야 개발자는 동기를 얻고, 정해진 것 이상의 것을 만들어 낼 수 있다.3) 기술을 다른 조직에 전파회사가 가진 기술을 다른 조직에 전파해서 충분히 이해할 수 있도록 해야 한다. 그래야 기술이 아이디어를 만나 빛을 발하고 회사의 가치가 높아진다.Technical Businessman으로 위와 같은 일들을 잘하게 되면 회사가 가진 기술이 사업에서 효과적으로 사용된다. 그리고 사업을 위해 필요한 기술이 기술조직에서 발전하게 된다. 이 일들은 조직이 커지게 되면 역시 시니어 개발자와 프로젝트 관리자에 의해서 대체될 수 있다.Team Manager일반적인 팀장/조직장이다. 이 역할을 잘 수행하기 위해서는 커뮤니케이션 능력, 시간/리소스 관리 능력을 갖추어야 한다.1) 채용좋은 개발자를 채용해야 한다. 이를 위해서는 다음과 같은 기본적인 일을 해야 한다.· 채용 공고를 작성하고 올린다.· 면접을 진행하고 채용을 결정한다.· 좋은 사람을 소개받고 만난다.채용을 위해서는 장기적으로는 회사의 '기술 브랜드', '기업 문화'를 만들어 나가는 것이 도움이 된다. 물론 이런 것 보다 회사가 로켓처럼 날아가는 게 효과는 훨씬 더 좋다.2) 인력의 유지어렵게 뽑은 인력을 잘 유지해야 한다.  · 개인의 비전과 회사의 비전을 일치시키기 위해 노력한다. 다른 말로는 동기부여라고 한다.· 개인의 조직 내 성장을 돕는다.· 개인이 회사에서 만나게 되는 문제를 해결해 준다.물론 충분한 대우를 해주는 것도 중요하다.3) 자원의 산정과 확보프로젝트가 시작되기 전에 프로젝트에 필요한 인적, 물적 자원을 구체적으로 산정한다. (위의 Technical Leader가 하는 초기 결정을 위한 추정과는 다르다.) 대부분의 경우 어떤 사람이 어떤 일을 할 것인가를 결정짓는 일이다. 그리고 개발 혹은 운영에 필요한 추가적인 자원들을 준비한다. 장비가 될 수도 있고, 외부 서비스가 될 수도 있다.4) 일정의 계획과 관리일정을 계획하고, 관리한다. 다른 팀 혹은 외부와 의존성이 있는 경우 특히 이런 부분들을 잘 관리해서 일정이 차질 없이 진행이 될 수 있도록 한다. 프로젝트의 진행상황은 시각화하여 공개적으로 확인할 수 있도록 한다.5) 업무 프로세스 개선업무 프로세스를 개선해서 효율적으로 일할 수 있도록 해야 한다. 이를 위해서는프로젝트 관리 도구의 도입이슈 트래킹 시스템 도입스크럼/칸반등의 개발 방법론을 도입하고 운영등이 필요하다.3), 4), 5)는 우리가 일반적으로 프로젝트 관리자(PM)라 부르는 사람의 역할이기도 하다.이 일을 잘하게 되면회사에 필요한 인적 구성/역량을 갖춘 기술 조직을 유지할 수 있다.팀이 효율적으로 일할 수 있다.타 팀과 조화롭게 일할 수 있다.팀이 진행하는 프로젝트를 성공으로 이끌 수 있다.이 일들은 조직이 커지게 되면 중간 관리자, PM, HR 담당자가 생기면서 대체될 수 있다.Product Manager팀이 고객들이 원하는 제품을 전달하게 한다. 이 역할을 잘하기 위해서는 사업, 기술에 더해 UX에 대한 이해가 추가로 필요하다. (인터넷 서비스의 경우)1) 고객에 대한 이해고객을 보다 잘 알기 위한 노력을 해야 한다. 이를 위해서는 서비스에 관련된 지표들을 지속적으로 관찰해야 한다. 또는 인터뷰, 고객 대응 등을 통해 고객의 소리를 직접 듣는다.2) 고객의 대변자고객이 원하는 바를 명확하게 적은 스펙 문서를 작성해서 메이커에게 전달해야 한다. 또한 애매한 사항들이 있을 때 이를 최종적으로 결정해야 한다. 때로는 고객을 대신해서 제품에 대한 쓴소리를 해야 한다.3) 제품의 비전과 로드맵"우리는 어떤 제품을 만들어 갈 건가요?"라는 질문에 답을 할 수 있어야 한다. 즉, 제품의 비전을 구축해야 하고 이를 위한 구체적인 로드맵을 만들고 실행해야 한다. 이 비전을 조직에 전파하고 공감을 얻어야 한다.4) 우선순위의 결정사업, 고객의 측면에서 때로는 기술/디자인 부채를 없애기 위한 메이커의 입장에서 우선순위를 결정해야 한다.각 구성원 간의 이해관계가 부딪치는 부분이다. 효과적인 커뮤니케이션을 통해 슬기롭게 해결해 가야 한다.5) 제품의 퀄리티제품의 퀄리티를 책임진다. 직접 QA도 하고 디테일을 챙겨서, 구성원들이 높은 퀄리티를 목표로 할 수 있도록 해야 한다.이 역할을 잘하게 되면 좋은 제품을 만들어서 고객들의 호응을 이끌어 낼 수 있다. 또한 제품을 만드는 구성원들이 만족감을 얻을 수 있다. 이 역시 조직이 커짐에 따라 기획자, UX 디자이너가 일부 역할을 대체할 수 있으며 Product Manager를 뽑을 수도 있다.마치며스타트업의 CTO가 해야 하는 일은 이렇게 많다. 사실 스타트업 초기에는 위에서 말한 모든 것이 필요하지는 않다.(일단 컴퓨터를 사고, WIFI 설정도 하고..) 하지만 회사와 조직이 성장함에 따라 각각의 역할은 점점 중요해진다. 적당한 시기에 이 역할들을 위임하지 못하면 구멍이 생기기 시작하면서, 결국 여러 가지 중 하나도 제대로 챙기지 못하는 상황이 발생하게 된다. 이렇게 일을 정리하고 보니 지금의 내가 그런 상황이 아닌가 싶다.그럼 이제 내가 해야 하는 일은 동료들에게 적극적으로 도움을 요청해야 하는 것이다. 동료들도 내가 손을 내밀기를 기다리고 있을 거라 생각한다. :)  · 마지막으로 타이틀 이미지는 최근 프로덕트 그룹 워크샵에서 디자이너님의 타이포 세미나 때   제가 직접 그려 본 것입니다.#8퍼센트 #에잇퍼센트 #스타트업CTO #CTO #일상 #하루 #관리자
조회수 3968

리디북스 서비스 장애 복구 후기

지난 8월 26일에는 약 21분간 리디북스 서비스 전체가 중단되는 장애가 있었습니다.사실 서버 스택 일부에만 영향을 주는 장애는 눈에 잘 띄지 않지만 꽤 흔하게 발생하는 일입니다. 기기 1대당 외부적인 요인으로 인한 장애가 평균 2년에 1번 발생한다고 가정하면, 서버가 100대 있을 때는 대략 1주일에 1번꼴로 장애가 발생하는 셈입니다.이런 형태의 장애는 서버 스택의 한 곳에서만 발생하므로, 이중화 혹은 클러스터링을 통해서 극복하곤 합니다. 또한 원인이 명확하므로 해당 기술에 대한 이해도가 높다면 비교적 빠른 시간 내에 복구가 가능합니다.그러나 이번에 리디북스가 경험한 장애는 달랐습니다. 현재 리디북스는 2개의 데이터센터와 클라우드에 인프라가 분산되어 있는데, 이 중에서 1차 데이터센터의 전원 공급에 문제가 생겨 특정 서버 랙에 있는 서버 17대가 동시에 내려간 것입니다. 즉, 소프트웨어나 머신의 물리적인 장애가 아닌, 데이터센터의 장애였습니다. AWS로 비유를 하자면 가용 영역(Availability Zone)의 장애라고 할 수 있겠습니다.원인에 대해이번 장애의 근본적인 원인은 데이터센터가 전원을 정상적으로 공급해주지 못한 것입니다. 물론 데이터센터 혹은 클라우드 서비스(IaaS)는 고객사에게 전원과 네트워크를 안정적으로 제공해주어야 하는 의무가 있습니다.하지만 이들 역시 천재지변이나 사람의 실수에 대한 대비가 100% 완벽할 수는 없습니다. 따라서 이러한 점을 사전에 고려하고 인프라를 설계하지 못한 것이 2차적인 원인입니다.이번 계기를 통해 데이터센터 이중화를 계획하게 되었고, 사용 중인 클라우드 역시 지역(Region) 전체에 장애가 생길 경우에 대한 대비가 되어있지 않아, 이번 계기로 복제 계획(Geo-Replication)을 세우게 되었습니다.구체적인 상황당시 전원이 차단되어 강제 종료된 서버들은 아래와 같습니다.데이터베이스 프록시 x 2메인 리버스 프록시 x 1읽기 분산용 MySQL 슬레이브 x 1서점용 웹 서버 x 3추천 알고리즘 API 서버 x 1알림센터 API 서버 x 2메인 스토리지 서버 x 2출판 플랫폼용 데이터베이스 x 2테스트 및 배치 작업용 서버 x 3그림으로 표현해 보자면, 대략 아래와 같은 상황에서… 아래와 같은 상황이 된 셈입니다.서버 스택의 여러곳에 순간적으로 장애가 발생한 상황공인 IP가 할당된 메인 프록시 서버 중 1대가 내려갔지만, 실제로는 아래와 같이 가상 IP로 구성을 한 상태였기 때문에 대기 중인(stand-by) 프록시가 동작하여 곧 서점에 장애 공지를 띄울 수 있었습니다.[이미지 출처: DigitalOcean™]공지 이후의 움직임우리는 데이터센터의 복구 시점을 명확히 알 수 없어서 신규 구축(provisioning)을 시작함과 동시에, 서버들의 물리적인 위치 이동을 고려하고 있었습니다. 그러나 다행히 10분이 지난 시점에서 전원 문제는 해결되었고, 서버들은 순차적으로 부팅이 완료되었습니다.일부 서버들은 부팅 과정에서 예상치 못한 지연이 발생하기도 하였지만, 모든 서버의 부팅이 완료된 이후에도 서비스는 완전히 정상으로 돌아오지 않았습니다. 당시 우리가 겪었던 문제와 해결책은 아래와 같습니다.A. 읽기 분산용 MariaDB 슬레이브의 복제 지연(replication lag) 문제슬레이브 서버의 부팅이 완료되자 데이터베이스 프록시(HAProxy)는 해당 서버를 정상으로 간주하여 라우팅 대상에 포함하게 되었고, 애플리케이션 서버들은 정상적으로 커넥션을 맺기 시작하였습니다. 하지만 해당 슬레이브는 수십 분간 마스터를 따라잡지 못한 상태였기 때문에 최신 데이터가 보여지지 않는 문제(stale data)가 있었습니다. 우리는 즉시 해당 슬레이브를 제거하였고 지연이 사라진 이후에 다시 서비스에 투입하였습니다.B. 읽기 분산용 슬레이브의 웜업(warm-up) 문제복제 지연은 사라졌지만 서버의 CPU 사용량이 크게 높은 상태가 한동안 유지되었고, 응답속도는 정상적인 슬레이브에 비해서 많이 느렸습니다. 왜냐하면 캐시가 비워진 상태에서 바로 서비스에 투입되어, 캐시 미스가 휘몰아치는 현상(cache stampede)이 발생하였기 때문입니다. 따라서 간단한 쿼리도 평소보다 오래 걸렸고, 그대로 둔다면 커넥션풀이 꽉 차는 현상이 발생할 것으로 예상되었습니다.곧 우리는 HAProxy로 해당 서버의 가중치를 10%로 낮추어 인입되는 쿼리의 양을 조절하였으며 응답속도는 정상 수치로 돌아오게 되었습니다. 이후 스크립트를 작성하여 수동으로 캐시를 채워나감과 동시에 점차 가중치를 높여 처리량을 정상화하였습니다.프로덕션에서 사용하는 서버는 innodb_buffer_pool 이 100G 이상으로 매우 크게 설정되어 있으며, 재시작 시 캐시가 날아가는 현상을 해결하기 위해 innodb_blocking_buffer_pool_restore 옵션을 적용하고 있습니다. 하지만 지금처럼 메모리를 덤프하지 못하고 비정상 종료가 된 상황에서는 해당되지 않았습니다.C. 인메모리 데이터의 보존 문제알림센터는 다양한 프로모션과 개인화된 정보를 전달해주는 공간입니다. 알림센터의 특징은 데이터의 영구 보존(persistency)이 필요하지 않고, 매일 수백만 건의 개인화된 메시지가 기록된다는 것입니다. 이러한 특징은 인-메모리 데이터베이스에 적합하므로 우리는 Redis를 마스터/슬레이브로 구성하여 저장소로 사용하고 있었습니다.어떠한 이유로든 Redis를 재시작해야 할 경우가 생기면, 메모리 상의 데이터가 날아가는 것을 방지하기 위해 주기적으로 스냅샷을 남기고 있습니다만, 이번에는 로그가 마지막까지 기록되지 못한 상태에서 메모리의 데이터가 날아가 버렸습니다.다행히 알림 발송과 관련된 메타정보는 모두 MariaDB에 기록하고 있으므로, 우리는 이를 기반으로 소실된 시점부터의 알림을 순차적으로 재발송할 수 있었습니다. 물론 모든 알림이 신규 상태로 간주되어 아이콘이 잘못 노출되는 문제가 있었지만, 고객님들은 너그럽게 이해해 주신 것 같습니다. 😅그래서 앞으로는?리디북스 DevOps 멤버들은 이번 데이터센터 장애를 통해 현재 인프라의 한계점을 실감하였고, 앞으로의 개선 방향에 대해 고민하게 되었습니다.몇 가지를 정리하면 다음과 같습니다.랙 단위로 장애가 발생할 수 있음을 인지하고 대비하자.같은 기능을 하는 서버를 하나의 랙이나 같은 가용 영역에 두지 말자.2차 데이터센터는 더 이상 옵션이 아닌 필수다.낙뢰나 지진으로 인해 데이터센터에 문제가 생길 수도 있다.긴급하게 프로비저닝이 필요한 상황에 대비하자.문서화가 되어 있더라도 경험이 없다면 동일한 구성에 많은 시간이 소요된다.모든 구성요소들에 대한 Ansible 스크립트를 작성하여두자.캐시 웜업 스크립트도 작성하여 두자.백엔드 구성요소들 간의 불필요한 의존 관계를 끊자.단 한 줄의 코드라도 참조하고 있다면 이는 독립적인 것이 아니다.언제나 서비스 지향적인 설계를 추구하자.Uptime을 관리하자.최대 180일을 기점으로 무조건 리부팅을 하자.재시작 과정에서 다양한 문제와 개선점이 발견될 것이다.커널 패치, 보안 패치를 할 수 있는 것은 덤이다.아래와 같은 긍정적인 면도 발견하였습니다.장애 상황이 실시간으로 Slack 채널을 통해 전파되었음진행 상황에 대해 모두가 동일한 수준으로 이해할 수 있었다.모니터링 연동(integration) 기능 때문에라도, Slack은 유료로 구매할만한 값어치가 충분하다.같은 기능을 하는 서버들이 다른 랙에 많이 분산되어 있었다.인프라가 확장될 때마다 빈 공간에 필요한 서버를 추가했을 뿐이지만, 자연스럽게 물리적인 위치가 분산되는 효과가 있었다.이 외에도 특정 클러스터를 구성하는 노드들을 분산하여 배치시키자.서버별로 오너쉽이 부여되어 있어서 빠르게 복구가 된 점여러 명의 백엔드 개발자들이 병렬적으로 복구를 진행할 수 있었다.마지막으로넷플릭스의 엔지니어들은 무질서한 원숭이(Chaos Monkey)라는 프로그램을 만들어서 운영한다고 합니다. 이 원숭이는 서비스 인스턴스들을 무작위로 중단시키는 역할을 합니다. 다소 황당하게 들리지만, 넷플릭스에는 일부 서비스에 장애가 발생하더라도 나머지 부분은 문제없이 운영되어야 한다는 원칙이 있으므로, 이를 수시로 시뮬레이션하는 과정을 통해 복구 능력을 높여둔다는 것입니다.실제로 이렇게 급진적인 아이디어를 실천할 수 있는 회사는 매우 드물 것입니다. 하지만, 우리는 이번 계기를 통해 무질서한 원숭이의 필요성을 절감하였고, 이로 인해 서버를 주기적으로 리셋하는 정책을 만들게 되었으며 모든 단일 장애점(SPoF)에 대한 대비를 시작하게 되었습니다.장애를 단순히 피해라고만 생각한다면, 서로를 비난하고 책임을 전가하는 상황이 펼쳐질 것입니다. 하지만 고객의 불편함과 맞바꾼 매우 비싼 경험이라고 생각한다면, 보다 튼튼하고 회복탄력적인 시스템을 갖추기 위해 노력하게 될 것입니다. 그러다 보면 언젠가는 데이터센터 전체에 문제가 생겨도 버틸 수 있는 모습으로 진화할 것이라고 생각합니다.#리디북스 #장애복구 #역경돌파 #개발 #개발후기 #개발자 #서버개발 #서버
조회수 2146

스켈티인터뷰 / 스켈터랩스의 스테로이드 서종훈 님을 만나보세요:)

Editor. 스켈터랩스에서는 배경이 모두 다른 다양한 멤버들이 함께 모여 최고의 머신 인텔리전스 개발을 향해 힘껏 나아가고 있습니다. 스켈터랩스의 식구들, Skeltie를 소개하는 시간을 통해 우리의 일상과 혁신을 만들어가는 과정을 들어보세요! 스켈터랩스의 스테로이드 서종훈 님을 만나보세요:)사진1. 스켈터랩스 스테로이드 서종훈 님Q. 진부한 첫 번째 질문, 자기소개를 부탁한다.A. 스켈터랩스에서 소프트웨어 엔지니어로 일하고 있는 서종훈이다. 연세대학교 컴퓨터과학과에서 HCI(Human-computer interaction)와 컴퓨터 비전(Vision)쪽 연구로 박사 학위를 받았다. 그리고 L모 기업의 AI연구소에서 일을 하다가 최근 스켈터랩스에 입사했다.Q. 어떻게 스켈터랩스에 입사하게 되었는지 궁금하다.A. 지인을 통해서 스켈터랩스의 여러가지 프로젝트에 대해 듣게 되었다. 스켈터랩스의 Inno Lab에서 진행 중인 프로젝트가 HCI와 가장 연관성이 깊고, 재미있는 디바이스를 구현하고 있어서 눈여겨 보다가 입사를 지원했다. 물론 프로젝트의 방향성이 나의 관심 분야와 일치하는지 뿐만 아니라, 함께 일하는 사람이 어떤 사람인지 알아보는 과정도 필요했다. 다행히 스켈터랩스에 지인이 있었고, 그의 소개로 하드웨어 엔지니어팀을 이끌고 있는 재경 님을 비롯하여 다른 팀원들을 미리 만날 수 있었다. 긴 대화 끝에 회사의 조직문화나 방향성의 결이 나와 맞는다는 생각을 했다. 뛰어난 개발자가 많기 때문에 내가 계속 성장해나갈 수 있는 환경이라는 점도 입사 결심을 굳히게 된 큰 요소 중 하나다.Q. 스켈터랩스에서는 어떤 업무를 맡고 있는가. A. 스마트 거울 샘(Samm)의 제스처 인식을 담당하고 있다. 이미지 인식을 기반으로 하는 작업이기 때문에 카메라로 구현을 하는게 맞을 지, 혹은 센서를 사용하는 것이 좋을지를 테스트하며 최적의 답을 찾아내려 하고 있다. 또한 엔도어 솔루션(Endor Solution, 공정 과정에서 부품의 결함을 자동으로 검출하는 솔루션)이 더욱 뛰어난 성능을 발휘할 수 있도록 개발에 참여하고 있다. 기존의 팀원들 모두가 딥러닝 경험이 풍부하다. 반면 전통적인 비전(Vision) 쪽 경험은 상대적으로 내가 더 풍부하기 때문에, 데이터처리나 고전적인 방법을 적용한 개발을 통해 엔도어 솔루션을 탄탄하게 보완하려고 한다. 텐서플로우(Tensorflow) 기반으로 기존의 팀이 일해왔다면, 나는 OpenCV를 통해 선행 데이터를 처리한다.사진2. 영화 <마이너리티 리포트>에 등장하는 G-SpeakQ. 비전 기술에 관심을 갖게 된 특별한 계기가 있었는지 궁금하다.A. 글쎄, 계기라고 말하기는 힘들다. 그냥 자연스럽게 HCI쪽에 관심을 가지게 되었고, 그러다보니 다양한 인터페이스를 구현하는 일을 맡아왔다. 당시 HCI가 붐이었고, 아이폰이 이제 막 세상에 등장한 시기이기도 했다. 그런데 HCI 분야의 개발을 지속할수록, 사람들에게 편리한 방식으로 원하는 것을 제공할 수 있는 분야에서 비전 기술은 필수라는 생각이 들더라. 웨어러블 디바이스를 사용하지 않고서도, 개개인의 행동을 관찰하고 그에 맞게 적절한 가이드를 제시하는 것은 모두 비전을 바탕으로 한다. 스티븐 스필버그 감독의 톰 크루즈 주연 영화 <마이너리티 리포트>를 보면, 톰 크루즈가 특수장갑을 착용한 채 스크린을 제어하는 장면이 등장한다. 양손을 사용하여 자유자재로 허공에 활성화시킨 스크린을 제어하는데, 이 장면은 단지 영화 연출이 아닌 실제로 개발된 기술에서 영감을 얻은 장면이다. 기술의 명칭은 ‘G-스피크(G-Speak)’. 이 혁신적인 기술을 개발한 존 언더코플러(John Underkoffler)는 영화 자문 이후, ‘오블롱 인더스트리즈(Oblong Industries)’라는 회사를 설립했다. 사실 ‘G-스피크'를 구현하기 위한 개별 기술들은 당시에도 굉장히 많았다. 오블롱의 차별점은 이 다양한 개별 기술을 하나로 통합한다는 점이다. 오블롱의 행보를 관찰하며, 비전 기술의 활용도에 대해 일종의 확신을 강하게 품게 되었다. NUI(Natural User Interface) 기술이 보편화되면, 기존 오퍼레이션 시스템 환경은 크게 변화할 것이다. 그때 일반 소비자에게 편하게 와닿을 수 있는 새로운 인터페이스를 선도하는 회사가 시장의 선도자가 될 것이고, 비전 기술은 시장 선도자의 핵심일 것이라고 생각하고 있다.Q. 여러 프로젝트에 동시에 참여하고 있기 때문에, 각 팀마다 업무 방식이 어떻게 다른지를 경험했을 것 같다. 그 이야기를 듣고싶다.A. 기본적으로 분위기가 굉장히 다르다. 엔도어 솔루션은 기업의 사설연구소의 느낌이랄까, 굉장히 학구적인 느낌이 강하다. 딥러닝과 관련된 많은 논문을 읽고 깊이 있게 연구하고자 한다. 많은 실험도 필수적으로 병행되는데, 내부적으로는 각 논문과 실험을 통해 얻은 인사이트를 정리하고 공유하고자 노력하고 있다. 이러한 과정을 통해 기존의 다양한 모델을 조합하고 자체적인 모델 개발을 통해 최적의 결과물을 구축하려고 한다. 반면 Inno Lab의 다양한 프로젝트는 오히려 내가 기대했던 스타트업스러운 느낌이 있다. 기존에 없던 디바이스를 만들어 내기 위해 다같이 아이디에이션 과정을 진행했다. 그리고 빨리 구현하고 피드백을 취합한 후, 다시 개발에 들어가는 과정이 꽤 다이나믹하게 이뤄진다. 현재 개발 중인 샘 덕분에 주변의 신기하고 재미있는 디바이스를 검색해보고, 직접 써보고 있는데 덕분에 굉장히 얼리어답터가 된 듯한 느낌이다.사진3. 종훈 님의 일하는 모습을 몰래 촬영해보았다Q. 동시에 결이 다른 두 개의 프로젝트를 진행하기가 어려울 것 같다.A. 어렵다. 그래서 나는 아예 프로젝트마다 기한일을 설정한다. 한 분야에 몰입해서 쭉 끌고 나가는 것이 내게는 더 맞는 느낌이라, 각 프로젝트의 PM과 상의하여 샘 개발에 15일까지 참여한다면, 월 말까지는 엔도어 솔루션에 참여하는 식으로 조정한다.Q. 이전 직장과 스켈터랩스의 업무가 어떻게 다른지도 궁금하다.A. 이제 스켈터랩스에 합류한지 3개월이 좀 지났는데, 크게는 두 가지가 가장 다른 점이자 만족스러운 점인 것 같다. 첫 번째는 일단 개발 환경이다. 스켈터랩스는 개발 환경이 굉장히 빠르고 선진적이다. 개발을 워낙 잘 하시는 분들이 많기 때문에 협업하면서 배울 점도 많고 협업을 통한 시너지도 강하다. 여러가지 툴을 똑똑하고 빠르게 잘 활용하는 것도 업무 효율을 크게 향상시키는 부분이다. 구글 드라이브, 깃허브(GitHub) 뿐만 아니라, 유트랙(Youtrack)과 같은 이슈트래커(Issue Tracker)도 적극 활용한다. 클라우드 환경, 빌드 환경 등도 모두 유연하게 잘 갖춰져있다. 이전 회사가 폐쇄적으로 운영되었던 부분이 있어서 상대적으로 이런 부분을 더 만족스럽게 생각한다. 스타트업인 만큼, 신기술에 대해서 팔로우하고 적용시켜 보려는 과정이 빠르게 일어나고 있는 점도 좋다. 두 번째는 ‘함께 하고 있다'라 느낌이 강하다는 것이다. 이전에는 워낙 프로젝트의 규모도 컸기 때문에, 각자 맡은 업무의 경계선이 분명하게 그어져있었다. 그러나 스켈터랩스는 잦은 미팅을 통해 함께 기획부터 참여하기 때문에 ‘우리의 것'을 만들어낸다는 느낌을 준다.Q. 스켈터랩스에서 가장 애정하는 조직문화가 있다면?A. 맥주를 먹으면서 일할 수 있다는 것(스켈터랩스에는 맥주 디스펜서가 구비되어 있다)! 다이어트를 하고는 있지만 워낙 맥주를 좋아하는 나로서는, 개발이 잘 안풀릴 때 맥주를 먹으면서 일을 할 수 있다는 것 자체가 만족스럽다. 매주 금요일마다 함께 모여서 회사의 여러 프로젝트 진행 상황을 듣고, 구성원에 대해서 알아보는 시간인 올핸즈(All-hands)도 좋아한다. 보통 다른 회사의 경우 정보가 총체적으로 전달되지 않고, 쪼개진 정보만이 내려오는 경우가 많다. 하지만 올핸즈 덕분에 회사의 정보들이 모두에게 공유될 수 있고, 또한 참여할 수 있다고 생각한다.Q. 비슷한 질문이지만 회사 자랑을 위해 하나 더 묻고싶다. 스켈터랩스에서 가장 자랑하고 싶은 점을 꼽는다면 무엇일까.A. 두 가지를 꼽고 싶다. 먼저 자유로운 문화라는 점. 한국에서 정말 몇 안되는 실리콘밸리의 분위기를 풍기는 곳이라고 생각한다(단순히 나만의 의견이 아니라, 실제 실리콘밸리에서 근무하는 친구가 사무실에 놀러왔을 때 ‘실리콘밸리 같다'라고 표현했다). 겉으로는 허름한 창고같은 사무실이지만, 문만 열리면 다른 세계가 펼쳐지는 듯한 느낌을 받을 수 있다. 자유롭게 의견을 내고 토론을 하는 문화도 이 사무실의 분위기와 일맥상통한다. 두 번째는 개개인의 실력이 높아서 정말 배울 것이 많다는 점이다. 그게 한편으로는 스트레스기도 하다. ‘내 밑천이 바닥나면 안될텐데'라는 생각에 책과 다양한 소스를 통해 끊임없이 자발적으로 공부하게 만든다. 실제 개발자 중 몇 분은 구글에서 개발자 레벨의 최고 등급을 받은 것으로 알고있다. 개발 실력은 당연히 코드에 묻어나온다. 다른 개발자의 코드를 보면서도 많은 영감을 얻을 수 있고, 코드 리뷰에 참여하는 것 만으로도 개발 실력이 향상될 수 있다.Q. 자유로운 출퇴근 문화지만, 종훈 님은 꽤 일찍 출근하는 편으로 알고 있다. 하루 일과가 궁금하다.A. 집에서 아침 시간을 여유롭게 즐기는 편이다. 여섯시에 일어나 아침 밥을 집에서 챙겨먹고 출근하고 있다. 일찍 출근할수록, 그 날 내가 목표로 한 업무를 빨리 마치고 퇴근할 수 있기 때문에 너무 늦게는 출근하지 않으려 한다. 덕분에 규칙적으로 일곱시 쯤에는 퇴근을 마치고 운동을 한다. 주말에도 주로 운동을 즐기는 편인데, 요즘에는 토요일마다 꼬박 꼬박 딥러닝 스터디를 하고있다. 나는 전통적인 비전(Vision) 연구를 해왔기 때문에, 딥러닝 쪽은 바탕 지식이 얕은 편이다. 업무를 진행하는데 큰 어려움은 없지만, 회사 프로젝트의 좋은 결과물을 내기 위해서는 딥러닝을 썼을 때 효율적인 부분이 크다. 때문에 많은 시간을 공부에 할애하는 것 같다.Q. 스켈터랩스 헬스동호회 스켈터 스테로이드의 수장으로 알고있다. 동아리를 소개한다면?A. 동호회를 만들게 된 계기는 단순하다. 새 회사에 왔으니, 새로운 몸을 만들겠다는 마음이었다. 사실 헬스는 누군가랑 같이 하는 운동은 아니지않나. 그래도 동호회원들 덕분에 ‘오늘은 그냥 좀 운동을 쉴까’ 싶다가도 누군가가 먼저 나서면 ‘그래도 빠지지 말아야지'란 생각에 꼬박꼬박 운동을 가게된다. 일주일에 두 번이니, 부담스럽지 않은 양이기도 하다. 내가 수장인 만큼 본보기로 열심히 나가야한다는 일종의 책임감도 꾸준히 운동을 이어나가는 원동력이 되었다. 날씨가 추워지면서, 다들 몸을 만들겠다는 의지가 약해져서인지 최근에는 참여률이 떨어지고 있다. 실내에서 할 수 있는 다양한 운동 종목을 더해, 참여를 높이는 방법을 고민 중이다. Q. 운동을 꾸준히 해오고 있는데, 헬스 동호회를 통해 목표했던 성취는 이루었는지 궁금하다.A. 동호회 소개를 하며 ‘이틀 밤을 새도 지지않는 체력을 얻어갈 수 있습니다'라고 공표했는데, 변명이지만 목표가 너무 거창했던 것 같다. 이틀 밤을 새도 지지않는 체력이 갖기 위해 갈 길이 멀다. Q. 이제 인터뷰를 마무리할 단계다. 스켈터랩스가 어떤 회사가 되면 좋겠는가.A. 앞서 말했던 오블롱 인더스트리즈나 센스타임(Sensetime)처럼, 확고한 기술력으로 시장의 선두주자가 되었으면 한다. 이를 위해서는 논문도 많이 내야할 것이고, 더욱 많은 개발자와 함께 기술을 더 깊게 파고드는 과정이 지속되어야 한다. 또한 스타트업으로서 시장의 성패와 상관 없이 가치있고 재미있는 개발을 많이 하면 좋겠다. 현재로서는 Inno Lab이 이러한 성격을 띠고 있다. 그래서 일단은 프로젝트 중 하나인 샘을 성공적으로 런칭하는 것이 나의 목표다.Q. 진짜 마지막 질문. 앞선 질문과 비슷하지만, 개인적인 꿈이 있다면?A. 오래 일하고 싶다. 나이가 들어서도 시장의 흐름을 읽고, 새로운 기술에 대한 충분한 이해와 개발력을 갖춘 사람으로 오래오래 일하고 싶다. 사실 일반적으로 개발자의 수명은 길지 않다. 그래서 창업에 대한 욕심도 품고 있다. 스켈터랩스의 CEO인 테드 님을 보면서 한편으로는 기업 운영 노하우를 배워나간다는 생각도 있다. 향후에는 스켈터랩스의 경쟁사를 내가 세울 수도 있지 않을까(테드 님이 이걸 보면 뭐라하실지 걱정이긴 하다).#스켈터랩스 #사무실풍경 #업무환경 #사내복지 #기업문화 #팀원인터뷰 #팀원소개 #팀원자랑
조회수 1046

[사람이 서비스다] #4 JD, 안드로이드앱 개발 담당

셀잇은 기존 중고거래 시장에서 이용자들이 겪는 불편과 불안감을 해소하기 위해 등장한 서비스라는 자부심을 가지고 구매자와 판매자를 잇는 접점이 되고자 합니다. 이를 위해 서비스를 기획하고 실행하는 저희 구성원들에 대한 이야기를 간간히 들려드리고자 합니다. 좋은 서비스든 아이디어든 결국 사람이 하는 일이니까요-저희가 어떤 생각을 품고 어떤 마음가짐으로 살아가는지에 대해 진솔하게 풀어보고자 합니다. 이 청년들의 이야기, 한 번 들어보실래요? Interviewee: JD (제이디, 개발팀 / 안드로이드앱 개발 담당)Interviewer: Austin (오스틴, 마케터)  우선 자기소개부터 간단히 해주시죠. 흔해 빠진 소개일랑 집어치우고! 최대한 자신을 우리에게 알려봐요! 정~ 뭐라고 쓸지 모르겠으면 자기 이름으로 삼행시라도 해보세요. 우선 저에게 이런 귀찮은 일을 안겨준 브라이언에게 감사의 인사를 전하는 바입니다. 덕분에 독무대에 이어 다시 한번 불면증에 시달리게 되었어요. 그건 브라이언에게 개인적으로 앙갚음(?)을 해주시고, 본인 소개부터 해주세요. 저도 바쁘답니다. 안녕하십니까? 저는 전남 해남의 작은 시골 마을에서 2남 중 장남으로 태어나 안드로이드 개발을 하고 있는 JD라고 합니다. 원래는 게임 개발이 하고 싶어서 프로그래밍 공부를 시작하였지만 어쩌다 보니 앱을 개발하고 있네요. (뭐, 뭐지? 이 ‘신입사원의_패기.wav’ 같은 느낌은?) 그럼 현재 셀잇에서 개발자로 일하시겠군요. 그럼 본인이 하는 일 중에서 이건 나만의 스페셜티다! 하는 부분은 무엇인가요? 당연히 안드로이드 개발입니다. 우리 회사에서 저 밖에 못하는 거죠~(찡긋) (찡...찡긋?) 하하하;; 네네 그렇군요. (셀잇이 잘 되는 이유가 이거였군. 정상적인 놈이 없는...) 그게 다인가요? 개발하시다가 잘 안풀리거나 열 받을 때는 어떻게 하나요? 자세한 건 ‘영업비밀’이니까- 전 안풀리면... 음- (한참을 생각한다)잠을 잡니다. (역시 오늘도 산으로 가는건가…) 아…(포기한 듯) 얼마나 자나요? 한 20분 정도 짧게 자요. 사실 잔다기보다는 자는 척을 하면서 생각을 하는거죠. 읭? 굳이 자는 척을 해야 될 필요가 있나요? 그냥 대놓고 생각하면 안되는건가요? 안됩니다! 온전한 집중을 위해서 자는 척을 해야 해요. (정적) 인터뷰 하는 중에 월드시리즈까지 끝나버렸네요... 올해 모든 야구가 끝나버렸어요 ㅠ (후우... 내가 이걸 왜 시작했을까...) 그럼 일 얘긴 그만하고(더 할 수도 없겠어;;) 업무 외의 시간에는 주로 뭘 하시나요? 듣자하니 야구를 좋아하는 것 같은데- 야구를 봅니다. 한국 야구는 기아를 응원하고, 메이저리그는 한국 선수들이 진출한 팀들을 응원하고 있어요. 주말에는요? 주말이면 아침에 일어나서 메이저리그 두 경기 정도 보고 오후에는 한국 야구를 보면서 하루를 보냅니다. 이제 야구 시즌도 다 끝나서 다가오는 겨울이 두렵습니다ㅠ 차라리 야구선수로 전향하시는게- 만약 실력이 문제라면 사회인 야구팀이라도 해보시는건요? 그건 돈도 많이 들고, 일단 귀찮고-부상 위험도 크고, 일단 귀찮고-그냥 친구랑 캐치볼 하는 것으로 만족합니다. 그리고 일단 귀찮고- 커피나 한 잔 하실래요? 커피나 마시면서 다른 얘기로 넘어가죠~ 괜찮습니다. 저는 카페인 마시면 안되서- 아, 그럼 그냥 계속 하죠. (여자랑은 술 마시고 나랑은 커피도 안 마시냐?-_- 쳇, 근데 이해되네...) 중고에 대해서 어떻게 생각하세요? 중고를 바라보는 가치관 같은게 있으시면 말씀해 주세요. 제가 환경 문제에 관심이 많습니다. 중고거래가 보편적으로 활성화 된다면 상대적으로 공산품의 생산량이 줄어들게 되고, 이는 지구의 자연 환경에 도움이 되지 않을까 합니다. (응? 뭔가 익숙한데?) 제가 예전에 쓴 글을 보신건가요?… 네~ 꼭 중고 거래가 활성화되서 지구 환경을 지켜주세요… 그럼 마지막으로 셀잇에서 이루고 싶은 것은 무엇인가요? 주로 컴퓨터 부품들을 중고 거래를 이용해 구매했던 적이 있는데요. 항상 직거래를 했지만 정상 작동하는지 불안했던 기억이 있습니다. 집에 와서 컴퓨터에 장착해 보고서야 안심을 하곤 했었는데, 셀잇을 이용하면 최소한 이런 걱정 없이 믿고 안심하며 거래할 수 있는 서비스로 자리 잡을 수 있었으면 합니다. 아니 이건 셀잇이 나아갈 방향이고~ 저는 제이디 본인 개인의 목표에 대해서 물은거예요. 셀잇이 곧 저입니다. 화, 화이팅...! (후우...) 이런 자리가 부끄럽죠? 가슴 속에 뜨거운 뭔가가 있는게 보이지만 굳이 밝히지 않으시겠다면 앞으로 안드로이드 앱을 통해 그 뭔가를 제가 찾아보겠습니다. (빨리 끝내려 애쓴다;;) 인터뷰는 이 정도로 마치는 것으로 하고~ 셀잇에서 칭찬하고 싶은 사람 한 명만 꼽아주세요. 이유도 함께 말해주세요. 전 네이쓴을 칭찬하고 싶습니다. 특유의 친화력과 유머러스함으로 주변 사람들을 기분 좋게 만들어주는 아주 훌륭한 팀원이기 때문입니다. 로봇입니까? 네? 아닙니다. 그럼 오늘 수고하셨습니다. 아! 최근에 셀잇 앱 2.0이 배포됐는데 감회가 남다를 것 같 같은~ 어떠세요? 딱히 이렇다할 소감은 없습니다만 이용자분들이 이전보다 더 편하게 서비스를 이용할 수 있었으면 하는 바람입니다. (로봇 맞네...) 넵- 수고하셨습니다. (하아... 네이쓴이라... 다음엔 우주로 가겠구만...)#셀잇 #번개장터 #인터뷰 #팀소개 #팀인터뷰 #팀원소개 #기업문화 #조직문화 #회사문화 #사내문화
조회수 2580

[인공지능 in IT] 네가 내 마음을 알아?

지난 2018년 3월, 고용노동부는 10월부터 발효되는 '감정노동자 보호법 개정안'을 통과시켰다. 해당 개정안은 고객의 폭언이나 폭력으로부터 스트레스를 받는 감정노동자의 인권과 업무의 질을 개선시킬 사업주 조치를 의무화하는 내용을 담고 있다. 감정노동이란, 고객을 응대하며 자신의 본래 감정과는 상관없이, 업무상 정해진 감정 표현을 연기하는 것을 일상적으로 수행하는 노동을 말한다. 예로 콜센터, 백화점 안내, 텔레마케터 등이 있다.< 감정노동자 보호를 위한 5개 금융업법 개정안 주요 내용, 출처: 동아닷컴 >이제 정부는 감정노동자의 '적응 장애'와 '우울증' 등을 업무상 질병으로 인정한다. 세계보건기구(WHO)에서 정의한 건강은 '육체적, 정신적, 사회적, 영적으로 안녕한 상태'다. 즉, 감정노동자들은 육체뿐만 아니라 정신적, 사회적으로 고통받을 수 있다는 것이다. 그들은 자동으로 저장된 말을 내뱉는 음성 안내기가 아니고, 일반 사람들처럼 똑같이 울고 웃는 사람이다. 그렇지만, 아직까지 국내에서 심리상담에 대한 정서적인 장벽은 높고, 상담 받을 수 있는 인프라도 잘 갖춰지지 않다. 감정노동자들이 실질적인 상담 도움을 받기는 어렵다는 의미다.감정노동 소식 뒤, 국내 인공지능 기술 업체 중 한 곳이 심리상담 서비스를 출시했다는 기사를 접했다. 전문 심리 상담사들이 축적한 수많은 상담 시나리오 데이터를 수집하고 구축해, 개별적이고 정확한 서비스를 제공한다는 것이 취지다. 또한, 통화 목소리를 기반으로 이를 감정 데이터로 변환시켜 정신 건강에 대한 정보와 스트레스 관리 등을 위한 맞춤형 콘텐츠를 제공하는 것이 목적이다. 정확도는 알 수 없지만, 인공지능이 인간의 감정을 인지하고 생활에 도움을 줄 수 있다는 사실만으로도 큰 의미가 있다고 생각한다.사실 필자는 몇 년 전까지 매 순간 변하는 복잡한 인간의 감정은, 인간 고유의 것이라고 생각했다. 인간은 자신의 감정을 알지 못할 때도 있고, 긍정적인 감정과 부정적인 감정을 주체하지 못하기도 한다. 아직 우리 스스로 감정에 대해 확실하게 정의할 수 없고, 통제할 수 없다. 하지만, 그럼에도 불구하고, (앞서 언급한) 심리상담 서비스처럼 여러 분야에서 기계가 인간의 감정을 이해하고, 심지어 감정 표현을 돕는 연구는 거듭되고 있다.기계와 감정의 접목은 2000년대 이전부터 시작되었다. 1995년 MIT의 피카드(Rosalind Picard) 박사가 처음으로 감성컴퓨팅이라는 용어를 사용하며, 인간의 감성을 분석하고 해석하는 기술 개발을 시작했다. 감성 컴퓨팅은 인간이 느끼는 바를 인지, 해석, 처리할 수 있는 시스템을 설계하기 위한 인공지능 기술을 연구하고 개발하는 분야다. 감정 인식은 상상 이상으로 복잡하고, 아직까지 정확하게 구현하기 힘든 어려운 기술이지만, 조금씩 그 영역을 확장하며 다양한 분야에서 사용되고 있다.아무래도 사람의 감정을 드러내는 표면적인 수단 중 가장 눈에 띄는 것이 표정일 것이다. 얼굴에 드러나는 인간의 감정은 안면 근육의 움직임을 통해 여러 표정으로 나타나기 때문이다. 여기에 영상 처리 기술을 활용하면, 기계가 인간의 감정을 분류할 수 있다. 이를 기반으로 한 감정인식은 다음의 과정으로 이루어진다.먼저 영상이나 이미지 안에서 얼굴 영역을 찾는다. 일반적으로 스마트폰 카메라 앱에서 많이 볼 수 있듯, 네모 박스 형태로 얼굴을 인식한다. 그리고 해당 박스 안에서 눈, 코, 입 등 랜드마크라고 불리는 특징점들을 찾는다. 이어서 각 특징점을 바탕으로 각각의 위치나 배치를 파악하는 프로세스를 거친다. 마지막으로 학습을 거쳐 사람의 표정을 인식할 수 있다.일반적으로 감정 쪽을 연구하고 기술을 개발하는 업체 대다수는 이러한 딥러닝 방식을 적용한다. 그리고 미리 지정한 각각의 감정 메트릭에 사용자의 표정을 맵핑하는 식으로 결과값을 도출한다. 하나 주의해야 할 점은 표정과 비교하는 감정이라는 결과값을 '확률(%)'로 산출한다. 예를 들어, 눈썹을 찌푸리고 눈이 커지면서 입을 벌리고 있으면, 감정은 '화남 95%, 놀람 20%, 슬픔 5%...' 등으로 표현하는 방식이다.< EMOTION>이외에도 톤, 크기, 템포 등 감정 변화에 따라 변하는 목소리를 분석하는 음성 인식 기술이나 몸의 특정 움직임을 분석해 감정 상태를 인지하는 동작 인식 기술 등이 있다. 특히, 음성 인식은 CS(고객 응대) 영역에서 빛을 발할 수 있다. 실시간으로 고객의 감정을 분석해 소통방식을 바꾸거나, 그들의 구매 패턴을 예측하는 데 도움을 준다.최근에는 페퍼를 비롯한 가정용 휴머노이드 로봇이 여럿 출시되면서, 감정인식 기술의 적용사례를 쉽게 찾아볼 수 있다. 이들 로봇들은 인간과 대화할 때 억양이나 표정을 인식하며, 심지어 때로는 인간의 감정을 예측하고 묻기도 한다. 물론, 아직까지 우리의 머리 속에는 기계라는 생각 때문에 상호간 자연스러운 대화나 감정을 전달하기 어렵다. 하지만 문자, 음성, 시각 등 현재도 여러 영역에서 인공지능 기술은 발전을 거듭하고 있다.< 핸슨로보틱스(hansonrobotics)의 휴머노이드 로봇 소피아(Sopjia), 출처: 핸슨로보틱스 >인간의 감정이라는 것은 하나의 영적인 매개체가 아닌, 복합적인 것이다. 결국 각 영역별 인공지능 기술이 고도화될수록 감정 인식에 적용할 수 있는 기술 또한 정교해진다는 것을 의미한다. 언젠가는 기계가 인간의 말상대가 되어주고, 함께 어려운 문제에 대해 의논할 수 있는 단계까지 이르지 않을까? 감정 노동자의 마음을 어르고 달래는 로봇이 등장할지도 모를 일이다.이호진, 스켈터랩스 마케팅 매니저조원규 전 구글코리아 R&D총괄 사장을 주축으로 구글, 삼성, 카이스트 AI 랩 출신들로 구성된 인공지능 기술 기업 스켈터랩스에서 마케팅을 담당하고 있다#스켈터랩스 #기업문화 #인사이트 #경험공유 #조직문화 #인공지능기업 #기술기업
조회수 2808

WHATAP Python APM 이야기...

백엔드 서비스로 Python을 사용한다면 만나게될 상황을보다 쉽게 해결하기 위한 와탭의 Python APM, 개발하게 된 이유입니다.파이썬은 배우기 쉽고, 어디서나 실행되는 언어라고 이야기되며, 인기도 높습니다. 생각보다 많은 곳에서 배울 수 있으며, 혼자 배우기도 좋습니다. 그런데, 이 규모가 확대되어서 스타트업의 경우에 Python을 사용하여 백엔드 서비스를 개발하는 경우를 찾는 것이 어렵지 않습니다. 또는, 수학적인 알고리즘이거나 ML(머신러닝)과 같은 영역이거나 블록체인등에서 Python을 사용하여 API geteway나 broker를 사용하는 경우에 한정한 상황을 고려하고 있습니다.Python으로 백엔드 서비스를 만들 때에는 성능과 설계 부분에 대해서 많은 걱정을 하게 됩니다. 이런 상황을 만나게되는 개발자는 여러가지 문제를 만나게 됩니다. 그 문제에 와탭은 집중합니다.!와탭은 백엔드 서비스를 Python으로 개발시에 만나게 되는 상황을 가장 최우선으로 생각하게 되었습니다.Python으로 '설계', '개발'되고 '테스트'된 후에 '배포'되는 상황에서 서비스의 불완전함과 속도상의 문제, 리소스의 불협화음등을 '유지보수'하는 단계를 '성능 튜닝'이라고 정의하고, 이를 고려한 상황을 보다 단순화하는 것이라고 생각하게 되었습니다. 이를 어떻게 처리하느냐가 와탭 Python의 핵심 가치라고 생각하였습니다.----- 이 부분은 Python korea 페이스북에서 '배권한'님이 지적하신 내용을 기반으로 일부 첨언되었습니다.----- python native 개발자들에게는 불필요한 설명에 해당됩니다.파이썬은 분명, 읽기 쉽고 사용하기 쉬운 것은 장점이며, 라즈베리파이 위에서 동작되는 기민함은 정말 매력적입니다. <- 원래 문장.(* 현재에는 jvm도 동작합니다. 하지만, 작고 기민하게 다양한 IoT 디바이스에서 폭넓게 활용되는 것은 파이썬의 장점은 분명하지 않나 합니다. 이 부분에 대한 지적이 있어서 첨언합니다. )내부 구성상 비동기식으로 쓰레딩이 아니라, 단일 이벤트 루프를 사용하는 비동기식 작성은 매우 효과적입니다. <- 원래 문장.(* 이 부분도 asyncio나 gevent등에 대한 이야기이고, CPython의 언어 구현상 GIL때문에 쓰레드가 비효율적이라는 이야기를 거론하고 싶었으나, 일반적으로 파이썬에 대한 언어를 사용할때에 대부분 사용하는 이유가 단일 이벤트 루프기반의 비동기식 작성이 매우 일반적으로 사용되기 때문에, 이렇게 서술되었습니다. 하지만, 이런 설명은 백엔드로 Python을 사용하는 경우에 대부분의 프레임웍들에서 처리되고 있기 때문에 서술이 불분명하다는 지적이 있었습니다. 당연, 백엔드 서비스를 개발할때에 사용되는 wsgi interface등에 맞추어서 서술되는 경우에는 이런 설명이 무의미합니다.다만, 이렇게 서술한 이유는 Java를 기반으로 APM이 개발되어졌기 때문에 이 부분에 대한 서술이나 설명이 필요하다고 생각한 저의 과도한 설명이 되겠습니다.이 부분은 Python Native개발자들에게는 불필요한 설명이 되겠습니다. 하지만, 백엔드 서비스를 개발하면서 만나게될 환경에서는 이 부분에 대한 이해가 어느정도 필요하다고 생각되어 서술된 내용이라고 생각해주시면 감사하겠습니다. )----------------------------------------------------------------------------------------------------------------------이 방식은 복잡한 자원 경쟁이나 교착상태를 발생하지 않게 되며, 기본 코딩과 유지보수를 정말 수월하게 만들어 줍니다. 그만큼 일관성이 높은 수학 알고리즘을 구현하는데 매우 적합합니다. 하지만, 냉정하게, 비즈니스 로직이나 분기가 많은 업무 로직에 적합한 언어는 아닙니다.하지만, 수학적 알고리즘 기반의 주요 모듈 위에 데이터베이스가 일부 필요하고, 웹서비스의 형태로 가동되는 구조라면 파이썬은 매우 훌륭한 선택이 되고 있으며, 생각보다 많이 사용됩니다.그런 이유 중의 하나는 파이썬의 멀티패러다임 구성과 같은 구성에서는 자바에서처럼 굳이 프린트를 위해서 객체지향 클래스를 만들 필요 없이 간단한 함수형 스타일도 가능하게 구성이 됩니다. ( 자바 8에서는 이런 함수 기능도 추가되었습니다. )단순한 구조와 방식 때문에 파이썬 개발은 요즘처럼 ML이나 AI 등의 기술적 요소들이 많이 사용되는 환경에서는 매우 효과적입니다. 백엔드 파이썬 개발이 많이 보이게 되는 이유이기도 하죠.또한, 파이썬 개발의 단점이라고 지적되던 문제들도 현재에는 실행 속도 문제는 사실상 큰 문제가 되지 않는 상황입니다. 일례로, 파이파이(PyPY)로 실행된 파이썬 코드는 웬만한 수준의 C 코드보다 빠르게 동작합니다.굳이 더 지적하자면, 모바일 컴퓨팅과 브라우저에 따른 웹 애플리케이션 클라이언트는 굳이 파이썬으로 작성할 필요성을 느끼지 못한다고 이야기하는 정도입니다.하지만, 이런 파이썬 개발에 가장 큰 문제가 있습니다.테스팅 없이는 동작하기 어렵고,실제 동작 환경에서만 등장하는 오류의 발생파이썬의 특성상 동적 입력 형태에 따르는 더 많은 테스팅을 필요로 하고 있으며, 실제 실행시간에만 나타나는 오류를 찾는 것이 가장 큰 문제가 있습니다. 이 부분은 수많은 파이썬 개발자들을 괴롭히고 있습니다.( 단편적으로 파이썬 개발환경이 매우 고도화되어있지 않으며, 파이썬으로 백엔드 서비스를 만들 것이라고 예측하지 못한 점도 있을 것입니다. 앞으로 파이썬 개발이 더 고도화 되기를 기원합니다. )이 가장 큰 문제를 잡기 위해서와탭은 집중하였습니다.파이썬 백엔드 개발 시의 문제 해결!물론, Python도 디버깅에 대한 지원 유틸리티가 존재합니다.pdb라는 파이썬 디버깅 모듈을 통해서 Step over/Step into, 중단점(breakpoint) 설정, 콜 스택 검사, 소스 리스팅, 변수 치환 등을 할 수 있습니다.‘Phthon -m pdb 파이썬 파일. py’의 형태로 디버그 동작 화면에서 세부적인 동작을 트레이스 해보는 방식을 사용하거나, pdb모듈을 import 한 후에 pdb.set_trace()를 중단하고 싶은 부분에 넣어서 사용하는 방식도 사용됩니다. 또한, 디버그 세션을 사용하는 방식이며, PDB를 사용하여 디버깅하는 방식들도 흔하게 사용됩니다.PyCharm, PTVS, Spyder 등의 IDE를 사용해서 디버깅을 하는 방법은 전통적인 개발환경과 동일하게 사용할 수 있습니다.하지만, 이 방식들은 백엔드 서비스에는 맞지 않게 되며 개발자들은 백엔드 서비스 동작시에 디버그 추적을 위한 로그를 거는 방식을 흔하게 사용하게 됩니다. ( 너무도 전통적인 방식이죠. )정말 백엔드로 파이썬을 사용하고 있다면, 오류 추적이나 동작 메커니즘을 추적한다는 것은 매우 귀찮고 번거로운 작업이 됩니다.만들어지는 파이썬의 모든 파일에 해당 로그를 넣었다가 빼었다가, 배포의 오류를 만나는 상황까지 매우 번거로운 작업들이 끊임없이 반복되게 됩니다. 이런 상황들을 추적하기 위한 APM의 추적 기능들을 찾게 됩니다.또한, Python의 특징상 수학 알고리즘으로 구성된 API 중개인의 형태를 취할 경우에 DB에 대한 접근을 위한 ORM에서의 추적과 외부 웹 호출들이 뒤섞이게 되면서 오류 추적은 매우 짜증스러운 단계로 진화되게 됩니다.Python으로 백엔드 개발을 하게 되면만나게 되는 매우 짜증스러운 상황이죠.그래서, 와탭의 Python APM은 이 문제에 집중하기 위해서 와탭 고유의 문제 해결 방식을 그대로  아키텍처로 적용하여서 개발시에 편하고 빠르게 성능을 추적할 수 있도록 제작되었습니다. Python 백엔드 개발을 위한 최선의 방향을 제시합니다.Python개발자는 와탭의 APM을 설치하면 매우 손쉽게 웹 트랜잭션의 단계, 에러 추적, 클래스 추적, DB의 형태 및 Slow Query추적, 외부 호출 메커니즘의 구성 등을 설치 이후부터 빠르게 추적할 수 있으며, 개발자의 실수이거나 다른 외부 호출의 문제, DB와의 관계 등을 빠르게 잡아낼 수 있습니다.에러를 추적하기 위한 로그를 동작한다던지, 실환경시에 배포를 다시 한다던가 하는 귀찮은 작업을 모두 제거하는 것뿐만 아니라, 매우 통계적으로 의미 있는 와탭의 트랜잭션 추적 메커니즘을 사용할 수 있게 됩니다.파이썬을 기반으로 백엔드를 구성하는 곳이라면,와탭 APM은 매우 의미 있는 결과를 도출할 수 있습니다.와탭 Python의 세부적인 기능을 조금 더 상세하게 설명드리겠습니다.가장 먼저, 실시간 트랜잭션 모니터링!5초 주기로 트랜잭션을 수집하는 와탭의 방식은 서버의 부하를 최소화하면서 가장 의미 있는 데이터들을 수집하고 데이터 기반으로 오류와 트랜잭션을 빠르게 추적할 수 있게 합니다.파이썬 개발 시의 동작성을 체크하기 위한 와탭만의 고유의 진행 중인 트랜잭션 실시간 모니터링 기능인 아크 이퀄라이져와 동작된 웹 트랜잭션의 종료시간을 기준으로 시각화하여 동작된 트랜잭션의 상황을 한눈에 파악할 수 있습니다.와탭 Python APM위의 그림을 보면, Active Transaction으로 불리는 원형( 아크 이퀄라이져라 함 )으로 실제 동작중인 트랜잭션의 개수와 동작속도 등을 체크할 수 있으며, Hitmap을 통해서 종료된 트랜잭션의 속도를 시각화하여 볼 수 있습니다. 이 두 개의 시각화 만으로도 느린 트랜잭션을 추적 관리할 수 있습니다.Python 트랜잭션 추적 및 분석개발자는 단지 APM을 동작시켰을 뿐이지만, postgreSQL 데이터베이스에 연결하고 SQL문장을 주고받는 부분들을 하나의 시각화된 관점으로 나열해서 확인할 수 있습니다.각각의 동작 시간을 추적하는 것은 물론이고, 이 내용은 ORM으로 매핑된 상태에서도 SQL의 동작 순서대로 시각화되기 때문에 순서가 꼬이거나 문제가 발생되는 부분들을 손쉽게 찾아볼 수 있게 합니다.이외에도 와탭 APM( Java, Node, PHP 등의 모든 APM)에 기본적으로 제공되는 트랜잭션 추적 모듈 이외에도 사용자가 원하는 모듈 추적에 대한 기능들을 플러그인 형태로 정의할 수 있습니다. 더 복잡한 추적을 위해서 와탭의 고유기능을 추가적을 확대 사용이 가능합니다.WHATAP_HOME 의 plugin.json파일에 적절한 내용을 수정하여 특정 모듈의 데이터를 추적할 수 있습니다. 특정 모듈의 데이터를 추적하거나, 사용자 별로 원하는 모듈을 추적할 수 있습니다.*사용 안내:•[module_name]: 추적하고자 하는 대상의 모듈 명. import 하는 모듈 명 이기도 하다.•[class_name]: 추적하고자 하는 대상의 클래스 명. 없다면 ‘’(empty string)으로 사용한다.•[def_name]: 추적하고자 하는 대상이다.•args_indexes: 추적하고자 하는 대상의 아규먼트 인덱스. 여러 개일 경우 , 로 구분한다.•kwargs: 추적하고자 하는 대상의 키워드 명. 여러 개일 경우 , 로 구분한다.Plugin 기능 사용위의 예제에서는 Plugin과 SQL update문장의 순차적인 실행,세부 Plugin 설정에서 사용자의 모듈명, 추적 클래스 명, 추적대상과 아규먼트 인덱스, 키워드 등을 추적할 수 있습니다.*사용 예:plugin.json{"[module_name]": {      "class_name": "[class_name]",      "def_name": "[def_name]",      "args_indexes": ", ",      "kwargs": ", "},"httplib2": {      "class_name": "Http",      "def_name": "request",      "args_indexes": "1",      "kwargs": "method"},"faker.providers.address": {      "class_name": "Provider",      "def_name": "street_address",      "args_indexes": "",      "kwargs": ""}}두 번째, 데이터베이스를 매핑한 ORM과 SQL의 순서와 속도, Slow Query!매우 당연하게 파이썬을 기반으로 백엔드 개발을 할 경우에 데이터베이스를 사용하게 되며, 이에 대한 Slow Query와 관련된 추적하는 것이 개발자에게 필요하게 됩니다. 향후, RDS기반을 사용하게 되면 Query추적은 대부분의 데이터베이스 처리에 기본이 될 것입니다.현재 지원되고 있는 mysql / postgresql에 대하여 SQL Query, Fetch Count, SQL Query수행 시간을 수집합니다.Python개발 시에 RDBMS(관계형 데이터베이스 관리 시스템)를 선택하면 거의 항상 ORM(객체 관계 매핑) 라이브러리를 함께 사용하게 됩니다.특히, 파이썬에서는 이런 ORM라이브러리가 다양하고 사용하기 쉽기 때문에, 매우 흔하게 사용하고 있습니다.ORM의 장점으로는 쿼리를 생성하거나 추상화하는 대신, 데이터 베이스 시스템에 대한 접근을 쉽게 할 수 있는 장점이 있습니다. 다만, 이러한 장점 때문에 실제 만들어진 쿼리가 어떠하고 쿼리 수행 시간이 얼마나 걸리는지에 대해서는 추적이 어렵다는 점이 있습니다.이처럼, 파이썬의 특징상 ORM(객체 관계 매핑) 라이브러리를 사용할 경우에 추상화된 쿼리가 어떻게 동작하고, 실제 어떤 상황으로 발생 및 동작되는지를 한눈에 파악할 수 있게 합니다.ORM으로 매핑된 SQL의 순차적인 동작 상태 파악그리고, 세 번째. 외부 호출 추적파이썬 백엔드 개발 시에 사용되는 외부 호출(request/httplib2)등의 외부 호출과 관련된 호출 정보 및 수행 시간 등을 수집합니다.외부 호출을 사용하는 경우에는 각각의 호출에 대한 지연시간에 대해서 세밀하게 추적해야 하므로, 이와 관련된 에러와 지연시간 등을 추적하는 것은 매우 중요한 개발 시의 관점입니다.Python 외부 호출 추적마지막 중요 관점 네 번째는, 튜닝을 위한 다양한 프로파일 데이터의 제공을 이야기할 수 있습니다.와탭의 파이썬 에이전트는 위에서 나열되는 성능 저하를 위한 요소들의 전체적인 관점에서 수집하고 그 데이터들을 시각화할 수 있습니다.데이터베이스를 효율적으로 사용하고 있는지, 사용하는 ORM툴과 매핑과의 관계, 쿼리와 쿼리의 수행 시간과 상태에 대한 추적, 외부 호출시간과 각각의 지연되는 외부 호출과의 관계와 순서 등이 전체적으로 백엔드로 개발되는 Python의 성능 튜닝에 영향을 주게 되는 것이죠.그 이외에도 전체적으로 백엔드 서비스의 TPS, 응답 시간, 서비스 리소스 사용량과 어떤 에러가 발생되고 있는지를 알 수 있습니다.서비스 사용자가 사용하는 상세한 정보들을 프로파 일릉 함으로써 이들의 연관관계를 한분에 파악하게 해줍니다. 와탭에서 관리되는 프로파일 정보는 - 트랜잭션, SQL Query, 외부 HTTP호출, Error, User Agent, Client IP 등의 상관관계들입니다.그리고, 덤으로... Python이 설치 운영되는 전체적인 패키지의 버전을 한눈에 파악할 수 있는 것은 너무도 당연한 기능입니다.설치된 Python 패키지 확인그리고, 와탭의 DNA를 그대로 이어받은 APM이기 때문에, 기본적인 APM의 기능들을 대부분 담고 있습니다. 처음 와탭 APM을 접하시는 분들을 위해서 간단하게 설명드리면 다음과 같습니다.CUBE 메뉴는 시간을 기점으로 와탭 Python APM이 설치된 이후부터 현재까지의 모든 상황들을 추적 관찰할 수 있습니다. 주말에 오류 간 난 상황이라던지, 특정 오류의 발생 시점을 알고 있는 경우에 빠르게 해당 문제가 발생한 위치나 SQL 등을 추적할 수 있습니다.상세한 일간, 주간, 월간 리포트나 MAU 등을 추적할 수 있는 리포트 기능들은 와탭만이 가지고 있는 장점에 해당됩니다.Python으로 백엔드 웹서비스를 개발하고 계시다면, WHATAP Python APM은 개발과 운용을 매우 풍요롭고 빠르게 해줍니다.파이썬 백엔드 서비스 개발자라면 와탭 APM!
조회수 3599

워크로그 개발기

저는 야놀자 CX 서비스실의 API 파트에서 백엔드(90%)와 웹 프론트엔드(10%) 프로그래머로 일하는 송요창입니다.개정된 근로기준법에 따라 2018년 7월 1일부터 300인 이상 규모 기업인 경우주 40시간(최대 52시간) 근로합니다. 이에 따라 야놀자에서도 업무 집중도 향상과 함께 업무 시간을 명시하는 방안이 논의되었습니다. 이런 배경 속에서 만들어진워크로그개발 경험을 이야기하겠습니다.개인의 업무 시간 작성근로 시간이 기존 대비 단축되면서 각 개인의 업무 시간을 기록하고 기준 근로 시간을 초과하였을 때 이를 소진하도록 하는 방향이 결정되었지만 어떤 도구를 사용할지가 문제였습니다. Timing, TMetric, 출퇴근 기록기 알밤 등 다양한 도구를 사용해서 각자 기록을 시작했습니다.1차 시도 - Workflow + Alfred 활용그러던 중에 캘린더를 이용해서 출/퇴근 기록을 남기고 슬랙(Slack)으로 메시지를 발송하는 방법을 CX 서비스실 강미경 님이 공유합니다.캘린더와 - 유료인 경우 - 슬랙 모두에 기록이 남는 장점이 있습니다. 사용하기 쉽습니다.iOS 앱인 Workflow를 이용해서 캘린더에 이벤트를 등록하고 슬랙으로 메시지를 전송.데스크톱이나 노트북은 Alfred의 Workflows 기능으로 해결할 수 있었습니다.Workflow + Alfred로 워크로그를 기록하는 단점개인적으로 편리했지만 CX 서비스실 내부로 전파하여 사용하기에는 문제가 있었습니다.안드로이드 휴대전화를 사용하는 경우 Workflow를 사용할 수 없습니다.아이폰을 쓰더라도 유료로 판매되는 Workflow를 사지 않으면 쓸 수 없습니다.Alfred를 쓰더라도 Power Pack을 구매한 사용자만 Workflows를 적용할 수 있습니다.2차 시도 - 슬랙봇 활용위에서 언급된 문제를 해결하고 구성원 누구나 추가 앱 설치 없이 손쉽게 접근할 수 있는 슬랙봇에 주목합니다. 캘린더가 아니라 데이터베이스를 활용해서 개발하면 어떨지 논의했습니다.늦은 저녁(대략 23시부터 03시)에 Firebase 실시간 데이터베이스(Realtime Database)와 Firebase 클라우드 함수(Functions)를 활용해서 단순한 슬랙봇을 만들었습니다.슬랙을 실행한 뒤 슬래시 커맨드(slash command)로 /wl 출근을 입력하면 출근 로그가 추가되고 완료 메시지를 수신합니다.슬랙의 3초 이내 응답 요구단순한 기능이었지만 슬랙봇을 활용해서 워크로그를 작성하는 동료가 조금 늘었을 때 치명적인 문제가 발생했습니다.슬랙의 슬래시 커맨드는 3초 이내로 응답할 때 완료 메시지를 노출합니다. 3초를 초과하면 아래 메시지를 노출합니다.Firebase 클라우드 함수로 작성한 코드에 문제가 있었습니다. 단순한 로그 데이터와 사용자 요청에 대한 기록을 모두 완수한 후에 응답을 보내도록 했습니다. 이 부분에서 응답 지연이 발생합니다.기록은 된다고 변명해봤지만, 사용자가 기록 여부를 알 수 없으니 재시도하는 횟수가 늘어났습니다. 중복된 데이터를 삭제 요청하는 사용자가 늘었습니다. 이런 불편을 겪고 초기 사용자가 이탈했습니다.위 문제를 제외하고도 다수 사용자의 특정 기간 내 로그를 모두 살펴보기에 슬랙봇은 그다지 좋은 도구가 아니었습니다.제가 잘 못 쓴 것이지 슬랙봇에게는 죄가 없습니다.3차 시도 - 웹페이지 도입앞서 말한 문제가 대두하기 전 다수의 로그를 살펴보기 위해 웹페이지를 제작 중에 있었습니다. 프로그래밍에는 야놀자 앱 하이브리드에서 다뤄본 React.js 외에 최근 소개받은 razzle, After.js를 사용했습니다(이에 관한 회고는 아래서 짧게 다룹니다).Firebase 실시간데이터 베이스에 쌓인 로그를 Firebase 클라우드 함수로 제작된 API로 사용자별, 일자별로 불러서 표시하는 정도로 개발 착수.웹페이지로 조회 기능을 만든 시점과 맞물려 슬랙봇이 무용지물이 되었습니다. 로그인 기능을 제작하고 웹페이지에서 워크로그를 추가할 수 있도록 했습니다. 기록과 조회가 웹페이지로 대체 된 것입니다????????.Firebase 인증은 정말 편리합니다.대형 이벤트이렇게 만들었지만 떠나버린 사용자를 돌아오게 만드는 일은 불가능했습니다. 저를 제외하고 몇몇 분들만 사용하는 소소한 서비스로 사라질 예정이었습니다. 그런데 CX 서비스실 실장이신 하희진 님이 전격적으로 CX 서비스실 전 구성원이 워크로그를 통해 기록을 남겨달라고 요청하셨습니다. DAU가 10배는 급상승했습니다(1~2명에서 20명 이상으로). 많은 트래픽????이 들어오니 부족한 기능과 어설픈 기록 시스템 등이 문제가 되기 시작합니다.엎친 데 덮친 격으로 초과 근무 차감이란 주 기능 오픈에 대한 관리자(희진 님)와 사용자의 요구가 커졌습니다.할 일이 넘쳐난다.DAU 20의 공포요구사항을 분석하고 구현하면서 미비한 규칙을 관리자와 자주 논의했습니다. 논의 결과에 따라 메뉴가 생겼다가 사라졌다가를 반복해서 사용자의 혼란이 가중되었습니다. 아직 제작되지 않은 관리자 기능 때문에 데이터베이스를 직접 수정하는 일도 빈번했습니다.무엇보다 갑자기 새로운 도구를 사용하는 사용자의 질문이 쏟아졌습니다. 주 40시간을 어떻게 측정할지, 초과근무시간의 근거나 법정 휴식시간 발생 요건 등 대부분은 규칙에 관한 질문이었습니다. 30분 안에 같은 질문을 5번 듣고 동일하게 답변하는 헤프닝도 있었습니다.???? 어디서 많이 본 모습인데? 바로 IT산업 전체에서 자주 일어나는 일입니다.점진적 개선우선 비슷한 질문을 모아 FAQ 페이지를 개설했습니다(우리 PO가 자주 하는 업무라서 배운 풍월이 도움이 되었습니다). 지나치게 사용자 기능을 제한하여 CS가 늘어난 측면이 있어서 규칙이 확정된 부분만 사용자 기능 제한을 풀었습니다.금주 내의 로그는 언제든 추가 및 수정할 수 있도록 변경했습니다.누적된 초과시간은 금주 중 언제라도 사용할 수 있도록 변경했습니다.한 주가 끝나면 잘못된 로그가 있는지 검사한 뒤 로그 수정 후 초과시간 확정하는 일은 하고 있습니다.배포되는 버전마다 변경사항을 문서에 남기고 전체 사용자에게 공지했습니다.차감 기능은 자투리 시간과 CX 서비스실 구성원의 배려로 개발하였습니다.다행히 6월에 태어난 둘째가 새벽 4~5시면 한 번씩 울어서 알람 없이 기상할 수 있었습니다????.개인 회고워크로그를 제작하면서 크게 2가지를 느꼈습니다.미비한 요구사항 분석은 개발 비용을 상승시킨다하나의 요구사항은 여러 기능을 필요로 합니다. 자세한 분석 없이 뇌내 망상으로만 개발에 착수했더니 구조를 변경하느라 시간을 많이 소모했습니다.초과 시간을 예로 들면 우선 차감 메뉴를 만들고 있었습니다. 그런데 차감에 근거가 되는 누적 시간이 없습니다. 그럼 누적을 기록할 수 있는 모델을 제작합니다. 1일 8시간 기준으로 기록하도록 개발합니다. 주 40시간이 넘을 때 초과 시간이 발생하는 규칙이라서 1주일 단위로 마감하는 방식으로 변경합니다.이렇게 우왕좌왕하며 개발하니 밀고 나가는 힘이 약했습니다. 프로덕트 개발 시 PO가 이 부분을 많이 돌봐줘서 기본 없는 프로그래머가 되었습니다(????).개발은 50%. 운영이 나머지 50%다마이너 버전이라도 개발을 완료하고 배포할 때마다 한고비 넘었다고 생각했습니다. 그렇지만 진짜 서비스가 단단해지는 것은 사용자를 만날 때부터였습니다.사용자는 관리자보다 인내심이 없습니다. 개선 사항을 슬랙을 통해서 말해주고, 잘못된 기록이 있으면 수정을 요구했습니다. 이상한 규칙이 발견될 때마다 피드백이 왔습니다. 정당한 요구와 피드백이지만 1인 개발자가 감당하기는 벅찬 부분이 있었습니다.피드백을 정리해서 수정할 부분을 JIRA에 정리하고 작업하기를 반복했습니다. 이 과정을 통해 초기보다 더 다듬을 수 있었습니다.저는 근무시간 중에만 CS 대응을 했음에도 피곤했습니다. 이런 일을 매일 매시간 겪고 있는 야놀자 PO와 IT 업계 동료들은 정말 대단한 사람입니다. 이 자리를 빌려 다시 한번 존경합니다.개발 관련 회고(신약???? 임상 결과)토이 프로젝트이기 때문에 회사에서 사용하는 기술 외에 새로운 기술을 다뤄봤습니다. React.js와 함께 엄청나게 사랑받고 있는 vue.js가 아닌 이유는 개발 시간이 촉박해서 공부할 시간이 없었다고 핑계 대봅니다.razzle + After.js = ????React.js를 사용할 때 주로 Next.js를 사용해왔지만 이번에는 razzle과 After.js를 사용했습니다.razzle은 create-react-app처럼 React.js 애플리케이션을 제작할 수 있도록 초기 구성을 도와줍니다. React.js 외에도 Vue, Angular, Preact, Elm 등을 지원합니다.After.js는 Next.js처럼 서버사이드 렌더링을 지원합니다. Next.js와 다르게 React Route 4를 이용해서 라우팅을 지원합니다.사용해본 소감은 razzle이 아무런 설정도 하지 않도록 도와주고 있어서 편리했습니다. TypeScript 도입도 예시가 있어서 쉽게 적용할 수 있었습니다. 코드 수정 후 웹페이지를 다시 로딩하는 핫 리로드(hot reload)도 잘 작동합니다. After.js는 서버사이드 렌더링 시 getInitialProps 를 사용할 수 있어서 Next.js에 익숙한 저에게 편리했습니다. 무엇보다 Next.js처럼 route를 변경하기 위해서 next-route에 의존하지 않아서 편리했습니다(대신 React Route를 의존합니다).저처럼 프로젝트 셋업을 어려워하는 초심자에게 유용합니다(검색할 때 사례를 더 많이 찾으려면 Next.js가 더 유리합니다).배포는 초기에 Aws의 beanstalk을 활용하다가 Zeit가 운영하는 now로 변경했습니다. Node.js나 docker에 익숙하고 커맨드 라인 인터페이스(cli)를 사용하는 데 어려움이 없다면 사용할만 합니다. 리전이 모두 해외라서 응답속도가 빠르진 않습니다.Zeit는 Next.js 프레임워크를 제작한 회사입니다.도움 주신 분???? 아이디어와 기획에 도움을 주고 사용자가 돼주신 R&D CX 서비스실 강미경 님???? 제보에 적극적인 R&D CX 서비스실 노현석 님DAU를 비약적으로 높여주신 R&D CX 서비스실 하희진 님미약한 사용성과 구린 UI임에도 잘 사용해주고 계신 R&D CX 서비스실 모든 구성원!!공감의 ????????! 눈물 흘리는 역할로 열연해주신 R&D UX/UI팀 김하연 님이 글을 리뷰해주신 유관종 님, 노현석 님, 구본한 님무엇보다 이런 프로젝트가 가능하도록 도와준 R&D CX 서비스실 내 API파트 전원에게 ????‍ 감사합니다.참고한 자료https://medium.com/evenbit/building-a-slack-app-with-firebase-as-a-backend-151c1c98641dhttps://api.slack.com/slash-commandshttps://firebase.google.com/docs/database/web/start#야놀자 #개발자 #개발팀 #문제해결 #버그수정 #백엔드 #인사이트 #경험공유
조회수 1038

Team Profile: Meet Yonghyun

Read In KoreanAs a yet minuscule startup, each member holds a significant power over the overall atmosphere of the team. And in our ultimate quest to make big waves in the data world, we need to make sure that the people at the helm are at least kind of cool. We think we’ve done a pretty good job so far in assembling a society of unique but equally driven members.So we bring you this seven-part series, one of each devoted to interviewing each of our members in detail, to give you an in-depth glimpse into the people responsible for bringing you the future of machine learning with Daria. Plus, we peppered the interviews with questions from Dr. Aron’s “The 36 Questions that Lead to Love”*, cherry picked to make work appropriate and concise, but interesting.(*actually falling in love with our members highly discouraged)Yonghyun joined the XBrain team in August as a software engineer, and has worked closely with other members in constructing the software that Daria runs on. But his interests run beyond just making sure that Daria become the future star of machine learning and data science — Yonghyun is also an avid soccer player, and an enthusiastic dabbler of virtual and artificial reality. Learn more about him here!Yonghyun saves a few minutes of his day for some introspection/staring broodily out the windowHi Yonghyun! Start by telling us about your role.YH: I work with JM as a software engineer at XBrain, developing and testing our software infrastructure.How do you usually spend a work day?YH: I usually come to work around lunchtime, and devote my time to whatever needs to be done for the day. Today we worked on tests involving transferring data from MS SQL. I enjoy afternoon walks sometimes, and usually head home after working a little post-dinner.Tell us about the parts of your job that you most enjoy.YH: I enjoy transforming machine learning modules into Spark to fit with the cloud system, and looking at the code Suzin’s written in order to understand the process.What about the aspects that you least enjoy or find challenging?YH: Setting up the environment to test our systems is something I least enjoy. It’s frustrating, because you can follow all the steps and still go the wrong way.Pick one item on your desk that tells us something about you.YH: I don’t have a whole lot on my desk…so I would probably have to say my laptop. The very very big laptop provided to me by the company.Laptop in photo is larger than it appearsWhat made you want to become a software engineer?YH: I was originally majoring in History in college, but I was struck by how computer science could help you create something tangible. Programming helps turn your ideas into reality on the screen, which is something I was really drawn to.So why XBrain?YH: As an incoming programmer, you don’t really come across the opportunity to participate in the making of a product that’s still under development. It’s a good learning experience for me to watch Daria’s progress. Furthermore, because I started programming at a relatively later stage, I still need help with my mathematical background, which working here allows me to do.As the one of the newest additions to the team, tell us about your vision for XBrain.YH: I think my vision is one of becoming a household name for a machine learning tool that a lot of people use on the daily — Daria doing useful things in every facet of the world, big or small.What is your go-to work playlist?YH: When I’m coding, I usually prefer EDM, so stations like Hardwell On Air, and hip-hop as well.Recommend a movie for our next Cinema Society, please.YH: Watchmen (2009). Its protagonist Rorschach is an anti-hero, and the plot line is complex and interesting to follow.Where do you see yourself 10 years from now?YH: Career-wise, honestly I wouldn’t mind what I have right now — working a job that I love without getting too swamped with deadlines, with plenty of time for exercise and socializing, playing soccer with my friends.Given the choice of anyone in the world, whom would you want as a dinner guest?YH: Mark Zuckerberg, maybe? I’d like to hear about his ideas for the future.If you had to have dinner with one XBrain member, who would it be and why?YH: JP, our new machine learning engineer. I’d like to get to know him better, and he seems like an interesting person.Would you like to be famous? In what way?Nope.What would constitute a “perfect” day for you?YH: A day productive enough that I could go to bed without worrying about the next day.If you were able to live to the age of 90 and retain either the mind or body of a 30-year-old for the last 60 years of your life, which would you want?YH: The body of a 30 year old… I don’t think that youth isn’t everything when it comes to minds.For what in your life do you feel most grateful?YH: The privilege to have been able to learn and achieve everything I’ve wanted is something I’ll always be thankful for, and also the flexibility to be able to change directions I’m headed in.If you could wake up tomorrow having gained any one quality or ability, what would it be?YH: I’ve always wanted more drive to carry out the projects I’ve devised in my head, the ability to see things through no matter what.Is there something that you’ve dreamed of doing for a long time? Why haven’t you done it?YH: I’ve always wanted to learn how to cook. I lived in a dorm in college so I didn’t have the opportunity then, but now would be a good time as any.What is the greatest accomplishment of your life?YH: I would say my greatest accomplishment is putting my best efforts into learning and improving my mind, inside and outside of school.What is your most memorable XBrain moment?YH: My fondest memories are usually of events we held outside — the hike we went on in September, or the soccer game we had. I like that we got to bond as a team and get some exercise.If you knew that in one year you would die suddenly, would you change anything about the way you are now living? Why?YH: I haven’t been able to get decent sleep recently, so I’d probably give myself some time to rest.If you were going to become close friends with someone, please share what would be important for him or her to know.YH: I don’t have very strong likes or dislikes, so I usually get along with most people.What, if anything, should never be joked about?YH: You should never joke about the disadvantaged, or others’ insecurities.If you could sum up XBrain in three words or less?YH: Freedom. Consideration. Learning…. Is that too serious?#엑스브레인 #팀원소개 #팀원인터뷰 #기업문화 #조직문화 #팀원자랑
조회수 2711

Next.js 튜토리얼 4편: 동적 페이지

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기 2편: 페이지 이동 3편: 공유 컴포넌트4편: 동적 페이지  - 현재 글5편: 라우트 마스킹6편: 서버 사이드7편: 데이터 가져오기8편: 컴포넌트 스타일링9편: 배포하기개요여러 페이지가 있는 Next.js 애플리케이션을 만드는 방법을 배웠습니다. 페이지를 만들기 위해 한 개의 실제 파일을 디스크에 만들어야 합니다.그러나 진짜 애플리케이션에서는 동적 컨텐츠를 표시하기 위해 동적으로 페이지를 생성해야 합니다. Next.js를 사용해 이를 수행하는 여러 방법들이 있습니다.쿼리 문자열을 사용하여 동적 페이지를 생성해봅시다.간단한 블로그 애플리케이션을 만들 예정입니다. 이 애플리케이션은 home (index) 페이지에 전체 포스트 목록을 가지고 있습니다.포스트 제목을 클릭하면 뷰에서 각 포스트를 볼 수 있어야 합니다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.포스트 목록 추가하기먼저 home 페이지 안에 포스트 제목 목록을 추가해봅시다.pages/index.js에 다음과 같은 내용을 추가해주세요.위의 내용을 추가하면 다음과 같은 페이지가 보입니다:첫 번째 링크를 클릭하면 404 페이지가 나지만 괜찮습니다.페이지의 URL은 무엇인가요?- /?id=Hello Next.js- /post?title=Hello Next.js- /post?title=Hello Next.js- /post쿼리 문자열을 통해 데이터 전달하기쿼리 문자열(쿼리 파라미터)를 통해 데이터를 전달했습니다. 우리의 경우에는 "title" 쿼리 파라미터입니다. 다음에서 보이는 것처럼 PostLink 컴포넌트를 이용해 구현해봅시다:(Link 컴포넌트의 href prop를 확인해주세요.)이처럼 쿼리 문자열을 이용하여 원하는 모든 종류의 데이터를 전달할 수 있습니다."post" 페이지 생성이제 블로그 포스트를 보여줄 post 페이지를 생성해야 합니다. 이를 구현하기 위해 쿼리 문자열로부터 제목을 가져와야 합니다. 어떻게 구현하는지 살펴봅시다:pages/post.js 파일을 추가하고 다음과 같이 내용을 작성해주세요:다음과 같이 보입니다:위의 코드에서 무슨 일이 일어났는지 살펴봅시다.- 모든 페이지에서 현재 URL과 관련된 내용들을 가진 "URL" prop를 가져옵니다.- 이 경우 쿼리 문자열을 가진 "query" 객체를 사용하고 있습니다.- props.url.query.title를 사용해 제목을 가져왔습니다.애플리케이션에서 몇 가지를 수정해봅시다. "pages/post.js"를 다음과 같이 변경해주세요: http://localhost:3000/post?title=Hello Next.js 페이지로 이동하면 무슨 일이 일어날까요?- 예상대로 동작할 것이다.- 아무 것도 랜더링하지 않을 것이다.- 해더만 랜더링할 것이다.- 에러를 발생시킬 것이다.특별한 prop "url"보다시피 위의 코드는 이와 같은 에러를 발생시킵니다:url prop는 페이지의 메인 컴포넌트에만 전달되기 때문입니다. 페이지에서 사용되는 다른 컴포넌트에는 전달되지 않습니다. 필요하다면 다음과 같이 전달할 수 있습니다:마치며쿼리 문자열을 사용하여 동적 페이지를 생성하는 방법을 배웠습니다. 이제 시작일 뿐입니다.동적 페이지를 렌더링하기 위해 더 많은 정보가 필요합니다. 그리고 쿼리 문자열을 통해 모든 것을 전달할 수는 없을 것입니다. 또는 http://localhost:3000/blog/hello-nextjs와 같은 깔끔한 URL을 원할 것입니다.다음 편에서 이것들에 대해 모두 배울 수 있습니다. 이번 편은 모든 것의 기초입니다.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 1669

코인원코어 엔진, PM과 개발자가 직접 답해드립니다!

‘코인원코어 엔진을 탑재하고 새로운 심장을 품게 된 코인원.’오늘은 차세대 엔진 프로젝트 ‘랩터TF’ 구성원들과 함께 엔진을 탑재하기까지의 비하인드 스토리와 코인원에서 일하는 이유에 대해 들어보려고 합니다.차세대 엔진 프로젝트는 코인원 크루의 치열했던 고민과 협업의 결과물입니다. 짧게 주어진 시간 동안 출산을 경험한 크루, 공휴일을 반납하고 개근상을 탄 크루 등 여러 에피소드를 남기고 무사히 서비스를 오픈할 수 있었습니다. 이 모든 것은 크루들의 헌신과 열정이 모여 이룰 수 있었던 성과였어요.'랩터TF'를 성공적으로 이끈 랩터 5총사. 지금 바로 이들이 만들어낸 성공 스토리를 공개합니다.Q. 안녕하세요, 자기소개와 함께 현재 하고 계신 일을 소개해주세요!요한 : 이번 랩터 프로젝트 PM과 더불어 블록체인셀에서 Cell Owner (이하 ‘CO’)로 이중생활(?) 하고 있는 조요한입니다. 블록체인셀에서는 주로 암호화폐 자산 입출금을 위한 지갑을 개발하고, 코인원에 블록체인 기술을 연구하고 적용하고 있습니다!자현 : QA셀의 CO 겸 Release Manager를 맡은 구자현입니다. (저도 이중생활을 하네요!) QA셀은 SW테스팅을 통해 코인원 서비스의 품질 경쟁력을 확보하는 것을 목표로 하고 있어요. 그리고 서비스 일정에 차질이 없도록, 전사 배포 프로세스와 일정을 관리합니다.지훈 : 모바일셀의 백엔드 개발자로 일하고 있는 김지훈입니다. 백엔드 개발자에 대해 간략하게 말씀드리면, ‘눈에 보이지 않는 부분을 개발한다.’라고 말씀드릴 수 있겠네요. 저는 코인원 모바일 애플리케이션의 백엔드 API 서버를 담당하고 있습니다. 은호 : 트레이딩셀의 백엔드 개발자 이은호입니다. 지훈님이 모바일 쪽이라면, 저는 웹 영역에서 ‘보이지 않는 손’의 역할을 맡고 있습니다. 서버 뒷단의 작업을 통해 코인원 유저가 안정적이며 신속하게 거래를 할 수 있는 환경을 제공하고 있고요. 랩터 프로젝트에서는 주로 매칭엔진 API 개발을 담당했습니다.허민 : 플랫폼셀의 시스템 엔지니어 허민입니다. 코인원을 지탱하는 인프라 플랫폼의 아키텍처 설계부터 구축과 운영까지 통합적으로 담당하고 있습니다. 특히 이번 랩터 프로젝트가 안정적으로 운영될 수 있도록 서비스 구성부터 많은 정성을 기울였습니다.Q. 차세대 엔진 프로젝트에 왜 랩터라는 별칭이 붙게 되었나요?요한 : 새로운 엔진으로 교체한 이후에 유저들이 서비스적으로 크게 체감할 수 있는 부분은 아무래도 속도일 겁니다. 요새 제 첫째아들이 동물도감에 푹 빠져있어요. 동물도감에서는 지구상에서 가장 빠른 동물로 ‘랩터'라는 공룡을 지칭하고 있습니다. 엔진교체로 거래 속도가 빨라지는 것을 가장 잘 표현할 수 있는 단어라 생각되어 이렇게 별명을 붙여보았어요. 자현 : 저도 랩터 별칭 때문에 찾아본 것이 또 하나 있어요. 전투기 중에서도 가장 빠른 기종을 ‘랩터'라고 하더라고요. 랩터 전투기는 신기술의 집합체이며 아주 정밀하게 만들어졌다고 하네요. 새롭게 교체된 엔진을 가장 잘 표현하는 것 같아 TF원으로서 맴이 아주 뿌-듯합니다!Q. 코인원의 새로운 차세대 엔진 ‘코인원코어'에 대한 자세한 설명 부탁드릴게요.요한 : 코인원코어는 초당 300만 건 이상의 거래 체결 처리가 가능한 고성능 엔진입니다. 수백 대의 서버로 수평 확장이 가능한 분산시스템을 갖추고 있어요. 서비스 중단 없이 거래 엔진을 확장할 수 있고, 신규 암호화폐 상장도 가능합니다. 또한 예상치 못한 장애 상황에서도 별도의 점검 없이 실시간으로 대응할 수 있습니다.코인원은 2014년에 출발해 4년이라는 적지 않은 시간 동안 거래소를 운영하면서 발생한 한계점들을 해결할 솔루션이 필요했어요. 이에 코인원의 다년간 거래소 운영 경험과 서버 엔진 전문기업 아이펀팩토리의 대규모 분산처리 기술이 융합된 거래엔진을 탄생시켰습니다.※ 코인원코어에 관한 자세한 설명은 (https://coinonecore.com/)에서 확인할 수 있습니다!▲화기애애하게 회의중(?)인 랩터TF 크루들!Q. 코인원이 새로운 엔진을 장착하기 이전과 이후, 무엇이 달라졌나요?은호 : 먼저, 시스템 확장성 부분에 대해 말씀드릴게요. 이전에는 상장되어 있는 암호화폐의 개수가 늘어날수록 시스템에 많은 부하가 발생했어요. 시스템을 수평 확장할 수 없는 구조적 한계를 지니고 있었죠. 기존 자원을 더 높은 사양의 자원으로 업그레이드하여 시스템의 부하를 해결했었는데, 이는 매우 높은 유지 비용을 요구하고 확장성 측면에서 한계점이 명확했어요.이제는 코인원코어 엔진을 새롭게 탑재하면서 이러한 부분들을 해결했습니다. 무한히 확장할 수 있는 병렬구조의 아키텍처를 구성했고, 더 많은 암호화폐를 상장해도 끄떡없는 코인원이 되었어요.고가용성과 장애 극복 측면에서 보자면, 모든 서버가 이중화 요건을 만족하여 단일 장애점(Single Point of Failure : SPOF)없는 안정적인 아키텍처로 구성되었습니다. 예상치 못한 장애 상황에서도 별도의 점검 없이 실시간 대응이 가능해 더 안정적인 거래소 운영을 할 수 있습니다.요한 : 이어서 성능에 관한 부분입니다. 거래체결량이 이전보다 100배 이상 향상되었습니다. 암호화폐 거래소 운영에 있어 안정적인 서버 운영은 가장 중요한 요소로 꼽히고 있어요. 특히 거래 서버의 경우 단시간에 수많은 요청을 처리해야 하는데, 코인원 코어는 초당 300만 건 이상 체결 처리를 합니다. 이는 증권사 수준 이상의 체결 엔진 성능이라 말씀드릴 수 있습니다.Q. 이번 코인원코어에 새롭게 적용된 기술적 특징이 있다면?허민 : 한국의 ‘Amazon EKS (Kubernetes Management Service)’가 오픈하고 나서, 가장 빠르게 도입한 회사가 코인원일겁니다. 대부분의 작업을 코드화 한 후, GUI 화면에서 반복적으로 작업하느라 속도가 나지 않던 부분들을 개선하게 되었고요. Kubernetes를 구축하면서 대부분의 서비스를 도커 컨테이너로 전환시키고 서비스들을 마이크로화 했습니다. 이제는 각각의 서비스 배포를 분리해서 업데이트 할 때, 다른 서비스에는 영향을 주지 않도록 시스템을 설계했어요. 개발한 서비스를 안정적이면서도 손쉽게 배포할 수 있고, 문제가 발생했을 때는 빠르게 복구가 가능하게 되었답니다.지훈 : 모바일쪽에서는 이번에 랩터 프로젝트를 하게 되면서 기존 베이스 코드를 리팩토링 한 부분이 절반정도 됩니다. 좀 더 효율적으로 프레임워크를 쓸 수 있도록 리팩토링 작업이 많이 들어갔고요. 많은 성능 향상을 기대하고 있어요!은호 : 앞서 요한님께도 말씀해주셨지만, 코인원코어의 가장 큰 특징은 세계적인 증권 거래엔진을 상회하는 체결성능과 이를 뒷받침하는 안정성이라고 생각해요. 백엔드 개발자 입장에서 성능과 안정성이라는 두가지 품질 요건은 대부분의 상황에서 Trade-off 관계에 놓이게 되는 아키텍처 요건이거든요. 한가지 요건을 달성하게 되면 다른 한가지 요건은 어느정도 희생을 감수할 수 밖에 없습니다. 그러나 코인원코어는 두 마리 토끼를 모두 포기하지 않았어요.초당 300만건이상의 주문을 체결할 수 있는 성능을 제공함과 동시에 장애 발생 상황에서도 단 한 건의 주문 누락 없이 서버가 복구되고 대체됩니다. 이 모든 과정이 전략적으로 자동화 되어 고객의 자산을 보다 더 안전하게 지킬 수 있게 되었어요. 코인원과 코인원코어의 뛰어난 기술력으로 이뤄낸 성과라 자부합니다.▲늦은 시간까지 열정적으로 진행되었던 '랩터TF'▲랩터 TF의 파워업을 위한 영양제와 야식 또한 빠질 수 없겠죠? ;)Q. 코인원 크루로 일하시면서 가장 큰 장점은 무엇인가요?요한 : 직무에 상관없이 자유롭게 소통하는 코인원 크루의 모습이 좋습니다. 코인원에서는 PM, 개발자, 디자이너가 모여 데일리 스탠드업 미팅, 회고 등 원활하게 소통하는 문화가 잘 구축되어 있습니다. 일하면서 좋을 때도 있지만 때론 힘든 부분들도 있을 거에요. 이에 대해 코인원 크루는 서로 투명하게 소통하고 피드백을 건네며 함께 성장합니다.지훈 : 코인원 입사 후에 개발 시간을 그래프로 나타내보았어요. 제 고향인 모바일셀보다 랩터 프로젝트에서 보낸 시간이 더 많더라고요. 랩터 프로젝트를 하면서 느낀 것 중, 코인원 크루는 자부심과 일에 임하는 태도가 남다르다는 것입니다. 저 또한 다른 크루분들의 열정적인 모습을 보고 다시 불태우게 되더라고요. 앞으로 코인원에서 새롭고 재미난 개발작업들을 많이 할 것 같아요.은호 : 코인원이라는 공간은 혼자가 아닌 모두가 함께 만들어나가는 공간이라는 것을 느끼고 있어요. 코인원에서 일하면서 함께 도전하고 성취하려는 크루들의 마인드가 무척 좋습니다. 더불어 모두가 거리낌 없이 새로운 기술을 받아들이려고 하는 스타트업 정신을 잘 가지고 있다는 것이 큰 장점이라고 생각해요. 새로운 기술을 이곳저곳 적용해보면서 시행착오를 겪어야 하는 개발자에게 있어서 가장 중요한 부분입니다. 허민 : 코인원은 이전에 몸담았던 다른 산업군의 회사들보다 훨씬 스펙타클한 곳이에요. 저는 그동안 새로움에 대한 갈증이 매우 컸어요. 시스템 엔지니어의 특성상 기존 서비스를 안정적으로 운영하면서 새로운 기술을 도입하는 부분에는 어려움이 많았거든요. 현재는 다양한 인프라 환경과 블록체인 기술에 관해 공부하고 도전해 볼 수 있는 제 모습이 좋습니다. 그리고 코인원의 크루분들도 새로운 기술에 대해 적극적으로 수용하려 하고, 회사 차원에서도 투자도 많이 이뤄져 뿌듯하네요!자현 : 코인원은 책임감으로 똘똘 뭉친 좋은 사람들이 모여있는 곳이에요. 빠듯한 일정 속에 고생하신 분들이 정말 많습니다. 힘들다고 하기 전에 먼저 알고 서로 응원해주는 모습들이 보기 좋아요. 그렇기 때문에 더욱 힘을내서 랩터 프로젝트를 할 수 있었고요. 또한 새로운 기술에 대해 회사 차원에서 끓임 없이 지원 해줍니다. 저 또한 QA 엔지니어로서 새로운 툴들을 찾아보고 활용할 수 있도록 노력하고 있죠! ▲크루 여러분, 정말 고생 많으셨습니다 :-)Q. 앞으로 코인원에서 이루고 싶은 목표가 있다면?요한 : 블록체인셀의 CO로서 좀 더 안정적인 무중단 입출금 플랫폼을 구축하고 운영하고 싶어요. 아직 블록체인과 암호화폐 생태계가 기술적 관점을 요구할 때가 많아, 일반 유저들이 이해하는데 상당히 어려움을 겪고 있습니다. 저는 유저가 쉽고 편리하게 이용할 수 있는 서비스를 만들고 싶습니다. 마지막으로, 유망한 블록체인 프로젝트들을 더 많이 코인원에 상장하고 싶네요!자현 : QA는 SW제품의 품질을 높이기 위해 개발 전 단계에 걸쳐 코인원의 품질을 체계적으로 잡아가는 조직입니다. 테스팅을 통해 결함을 조기 발견하고 제품 품질을 높여 유저가 서비스를 이용하는 데 문제가 없도록 하고 있어요. 현재 코인원 서비스가 놀이터 수준이라면 고도화된 서비스로 유저들이 즐길 수 있는 놀이동산이 되었으면 좋겠습니다. 코인원 놀이동산에 모여 많은 분이 다양한 서비스를 즐길 수 있으면 좋겠네요!지훈 : 제가 코인원에 입사한 지 얼마 되지 않아, 전체적인 개발을 이해하는데 조금은 감이 오지 않았던 때가 있었습니다. 그래서 특히 랩터TF에 감사해요. 하드코어 심화 속성(?) 수업으로 코인원에 대한 모든 것을 숙지할 수 있게 해주었거든요. 이제는 모바일셀의 백엔드 개발자로서 새로운 서비스나 기능을 많이 개발하고 싶어요. (P.S. 모바일 개발에 대한 A to Z까지 크루분들에게 알려드릴 수 있도록 하겠습니다.)은호 : 개발자의 자아실현 실천법 중에 ‘기여’라는 방법이 있습니다. 개발자는 누구나 오픈 소스 커뮤니티의 도움을 받아 개발을 해왔고, 앞으로도 하고 있을 거에요. 저는 제 멘토와도 같은 오픈소스 커뮤니티에 기여하는 것을 소소하게 목표로 삼고 있어요. 오픈소스 프로젝트를 기획하고, 관심있는 프로젝트에 더 큰 기여를 해 제가 받았던 도움들을 보답하고 싶네요. 이러한 기여의 방법은 저의 개발 커리어로서도 명예이고, 제가 속한 조직에 더 큰 선순환을 불러일으킬거에요.허민 : IT업계 중에서 좋은 개발문화가 회자되는 곳으로 넷플릭스와 페이스북을 꼽습니다. 이들의 경우,  좋은 아이디어나 유저의 요구사항을 빠르게 적용해서 서비스에 반영하고 있어요. 이러한 실행속도는 안정적인 플랫폼이 뒷받침되기에 가능하다고 생각합니다. 코인원 또한 지속적으로 플랫폼을 개선해 나갈 생각이고, 이러한 개발문화를 스며들게 하는것이 제 목표라고 할 수 있겠네요.Q. 코인원에 합류할 예비 PM 그리고 개발자분들에게 한 말씀 부탁드려요!요한 : 코인원은 크루들에게 많은 오너십을 부여하고 있어요. 자신이 맡은 Product에 많은 역할과 권한을 갖는 것을 좋아하고 블록체인을 좋아한다면 코인원으로 오세요! 자현 : 책임감이 강한 분이 오셨으면 좋겠어요. 최소한 자신이 구현한 것에 대해서 끝까지 책임질 수 있는, 마지막 실행단계까지 끝까지 확인할 수 있는 분이었으면 좋겠네요. 모두가 편해지는 개발 월드를 위해!지훈 : 아무래도 금융 쪽에 서비스를 하다 보니까 머리가 좋으신 분들이 정말 많이 지원하실 것 같아요! 아, 추가로 테스트코드를 같이 작성하시는 분이 오시면 정말 좋을 것 같네요!은호 : 자신의 결과물에 자부심과 책임감이 있는 크루가 함께했으면 좋겠습니다! (진지,궁서체입니다.) 허민 : 수평적인 협업을 하고 싶으신 분! 같이의 가치를 소중히 여기시는 분! 어려운 문제가 앞에 있어도 즐기면서 넘어갈 수 있는 분들! 창의적이면서도 효율적으로 일하고 싶으시다면 저희와 함께해요!▲수줍게 웃음짓고 계신 랩터TF 인터뷰이들!지금까지 랩터TF 크루들의 이야기를 들어봤습니다.인터뷰 내내 엔진 서비스를 개발했을 때의 열정이 고스란히 전해졌어요. 함께 서비스를 만들고 성장하면서 서로의 신뢰가 더 두터워졌다는 코인원 크루들. 이러한 믿음 안에서 불가능한 일도 가능하게 만든 힘을 엿볼 수 있었습니다.앞으로 코인원은 더 빠르고, 더 안전하고, 더 단단해진 서비스로 여러분들을 찾아뵐 예정입니다. 엔진 프로젝트를 시작점으로 최고의 서비스를 만들어나가는 이들의 모습이 기대됩니다!끝으로, 특별한 개발문화를 경험할 기회! 코인원 채용에 함께하는 것도 잊지 마세요 :-)
조회수 4955

Gradle Dependency 분리하기

본 포스팅은 아래 코드를 보시면 좀 더 이해하기 쉽습니다.build.gradledependencies-variable.gradledependencies-classpath.gradledependencies-app.gradleGradle 의 역할Gradle 은 이제 안드로이드 개발에 있어서 그 중심이 되는 빌드 환경입니다. 안드로이드 빌드에 대한 기본 설정 뿐만 아니라 빌드에 필요한 Task 를 지정하거나 의존성을 추가할 수 있습니다.특히 의존성에서 일반적인 서비스들은 다양한 오픈소스를 활용하게 됩니다. 네트워크 라이브러리, 이미지 라이브러리, DI 라이브러리, Support 라이브러리,Play-Service 라이브러리 등등 이젠 프로젝트를 시작함에 있어서 기본적으로 10개 이상의 라이브러리를 추가하게 됩니다. 이러한 라이브러리들이 많아질수록 필연적으로 빌드 스크립트가 길어지게 됩니다. 이는 나중에 빌드에 관련된 코드를 추가/수정할 때 유지보수에 영향을 끼치게 됩니다.Gradle 의존성 분리하기토스랩에서는 꽤 많은 숫자의 라이브러릴 사용하고 있습니다. 테스트용 라이브러리들까지 포함해서 60여개의 라이브러리를 쓰고 있습니다. 이러한 라이브러리 코드들이 1개의 빌드 스크립트 안에 포함되어 진다면 라이브러리의 버전을 변경하거나 수정하는 작업을 할 때에는 불가피하게 시간이 소요될 수 밖에 없습니다.그에 따라 Gradle 에서 라이브러리들을 변수화 해서 분리하는 작업을 하였습니다.1. 라이브러리 변수화 하기ext { retrofit = 'com.squareup.retrofit2:retrofit:2.1.0' retrofit2_gson = 'com.squareup.retrofit2:converter-gson:2.1.0' retrofit2_rxjava2 = 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:2.1.0' } 가장 간단한 변수화였습니다. 하지만 Retrofit 은 관련 라이브러리들이 함께 수반되기 때문에 버전명을 다시 분리하였습니다.2. 라이브러리 버전 변수화 하기ext { retrofit_version = '2.1.0' retrofit = "com.squareup.retrofit2:retrofit:$retrofit_version" retrofit2_gson = "com.squareup.retrofit2:converter-gson:$retrofit_version" retrofit2_rxjava2 = "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:$retrofit_version" } 하지만 버전명과 라이브러리이름이 함께 있는 것이 깔끔해보이진 않습니다. 그래서 아래와 같이 바꿨습니다.3. 라이브러리 이름과 버전의 분리ext { retrofit = '2.1.0' } ext.dependencies = [ retrofit2 : "com.squareup.retrofit2:retrofit:$ext.retrofit", retrofit2_gson : "com.squareup.retrofit2:converter-gson:$ext.retrofit", retrofit2_rxjava2 : "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:$ext.retrofit_rxjava2", ] 실제에는 다음과 같이 사용하면 됩니다.dependencies { compile rootProject.ext.dependencies.retrofit2 compile rootProject.ext.dependencies.retrofit2_gson compile rootProject.ext.dependencies.retrofit2_rxjava2 } 이제 라이브러리를 변수화 해서 분리를 하였습니다.이제 변수로 지정한 라이브러리들은 build.gradle 파일안에 존재하게 됩니다.// build.gradle ext { retrofit = '2.1.0' } ext.dependencies = [ retrofit2 : "com.squareup.retrofit2:retrofit:$ext.retrofit", retrofit2_gson : "com.squareup.retrofit2:converter-gson:$ext.retrofit", retrofit2_rxjava2 : "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:$ext.retrofit_rxjava2", ] buildscript { // blah blah } 라이브러리가 3개뿐이니 깔끔해보이는군요. 하지만 토스랩의 라이브러리는 60여개 입니다. 변수명도 60여개라는 말이죠. 그래서 라이브러리 변수들만 파일을 분리하기로 했습니다.4. 라이브러리 변수를 파일로 분리하기// dependencies-variable.gradle ext { retrofit = '2.1.0' } ext.dependencies = [ retrofit2 : "com.squareup.retrofit2:retrofit:$ext.retrofit", retrofit2_gson : "com.squareup.retrofit2:converter-gson:$ext.retrofit", retrofit2_rxjava2 : "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:$ext.retrofit_rxjava2", ] // build.gradle apply from :'dependencies-variable.gradle' buildscript { // blah blah } 이제 좀 교통정리가 되어가는 기분이네요.하지만 app 의 build.gradle 을 보았습니다.// app 의 build.gradle apply plugin: 'com.android.application' dependencies { // 라이브러리 60개 compile rootProject.ext.dependencies.library.retrofit2 compile rootProject.ext.dependencies.library.retrofit2_gson compile rootProject.ext.dependencies.library.retrofit2_rxjava2 } android { // 중략 } 뭔가 잘못되어 가고 있습니다. 여전히 dependencies 가 큰 부분을 차지하고 있습니다.5. app.dependencies 분리하기이제 dependencies 를 분리할 차례입니다.// dependencies-app.gradle repositories { jcenter() } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile rootProject.ext.dependencies.library.retrofit2 compile rootProject.ext.dependencies.library.retrofit2_gson compile rootProject.ext.dependencies.library.retrofit2_rxjava2 compile rootProject.ext.dependencies.library.okhttp3 compile rootProject.ext.dependencies.library.okhttp3_logging compile rootProject.ext.dependencies.library.stetho_okhttp3 } // app 의 build.gradle apply from: 'dependencies-app.gradle' 이제 dependencies 와 관련된 스크립트가 분리되었습니다.하지만 저 apply from 이 항상 app 의 build.gradle 에 따라 붙어야 하는 것이 아쉽습니다. 그래서 buildscript 에 아예 추가하기로 하엿습니다.6. 빌드 스크립트에 dependencies 추가 동작하기먼저 빌드 스크립트용 스크립트를 만들겠습니다.// dependencies-classpath.gradle rootProject.buildscript.repositories { jcenter() } rootProject.buildscript.dependencies { classpath rootProject.ext.dependencies.classpath.android } 그리고 buildscript 가 시작될 때 모든 dependencies 스크립트가 인식할 수 있게 하겠습니다. 인식할 스크립트는 다음과 같습니다.dependencies-variable.gradle - 라이브러리 변수 저장dependencies-classpath.gradle - 빌드용 스크립트 저장dependencies-app.gradle - 라이브러리 추가 스크립트 저장rootProject 의 build.gradle 를 아래와 같이 변경합니다.// rootProject 의 build.gradle buildscript { apply from: "dependencies-variable.gradle" apply from: "dependencies-classpath.gradle" } apply from: 'dependencies-app.gradle' 위와 같이 변경을 하면 빌드스크립트가 동작하는 시점에 변수를 인식하고 빌드용 스크립트를 인식합니다.하지만 앱용 라이브러리 추가 스크립트는 아직 준비가 덜 되었습니다. “app” 프로젝트가 인식이 된 시점에 라이브러리가 추가되어야 하기때문에 처음 만들었던 스크립트로는 한계가 있습니다.그래서 아래와 같이 변경하겠습니다.// dependencies-app.gradle rootProject.allprojects { project -> if (project.name == 'app') { project.afterEvaluate { repositories { jcenter() } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile rootProject.ext.dependencies.library.retrofit2 compile rootProject.ext.dependencies.library.retrofit2_gson compile rootProject.ext.dependencies.library.retrofit2_rxjava2 } } } } afterEvaluate 는 프로젝트의 인식이 완료되면 동작이 되는 함수이기 때문에 모든 것이 끝나고 dependencies 가 추가되는 것으로 이해하시면 됩니다.정리위의 과정을 거침으로써 gradle 파일은 좀 더 나뉘었지만 app 의 build.gradle 은 안드로이드 프로젝트 그 자체에 집중 할 수 있도록 하였습니다.이렇게 나누었던 본래의 목적은 의존성 라이브러리와 코드 품질 관리용 스크립트가 1개의 스크립트 파일에 담겨지면서 관리하는 데 있어서 큰 문제가 발생하게 되었습니다. 그에 따라 각각을 나누고 그 목적에 맞도록 각가의 파일 만들었습니다.라이브러리의 변수용 파일buildscript 용 classpath 를 관리하는 파일본 프로젝트의 라이브러리 의존성 관리 파일참고 소스Github : https://github.com/ZeroBrain/DataBind-MVVM-Sample#토스랩 #잔디 #JANDI #개발 #개발후기 #인사이트

기업문화 엿볼 때, 더팀스

로그인

/