스토리 홈

인터뷰

피드

뉴스

조회수 945

단일 TABLE을 SELECT하자!

OverviewDB를 다뤄봤다면 SELECT문도 아실 겁니다. 가장 먼저 접하는 명령어 중에 하나이기도 하죠. 보통은 아래처럼 사용합니다. SELECT문SELECT     * FROM 테이블명  ; 명령을 주면 지정한 테이블에 저장된 모든 내용을 검색합니다. 이번 글에서는 테이블을 만들고 SELECT하는 과정을 다뤄보겠습니다. DB는 MySQL 5.6을 기준으로 하고, Tool은 MySQLWorkbench를 사용하겠습니다.Query, 너란 녀석테이블은 위와 같이 생성할 수 있습니다. 위의 내용은 MySQLWorkbench를 이용해 Model을 표시하면 아래와 같습니다. 구성원의 정보를 저장하도록 했고, 컬럼마다 의미를 갖게 됩니다. MBR_ID (구성원 아이디) : DB에서 구성원을 식별하는 아이디MBR_INDFY_NO (구성원 식별 번호) : 구성원을 실제 구별하는 번호로 과거에는 주민등록번호가 많이 사용되었고, 요즘은 e-mail 이 많이 사용됩니다.MBR_NM (구성원 명) : 구성원의 이름 테스트 데이터를 입력해 실행하면 어떤 결과가 나오는지 보겠습니다.가장 기본적인 SELECT문 실행계획을 보면 아래와 같이 나옵니다.실행 계획은 DB가 어떻게 Query를 수행할 건지 보여줍니다. Query가 복잡해지면 실행 계획을 보면서 Query가 올바르게 작성됐는지 확인하고 필요하다면 Query를 수정해야 합니다. DB를 시작할 때부터 실행 계획을 보는 습관을 기르는 게 중요한 이유입니다. 각 항목에 대한 설명id : SELECT 문에 있는 순차 식별자로 Query 를 구분하는 아이디select_type : SELECT의 유형SIMPLE : Subquery나 union 이 없는 단순한 SELECTtable : 참조되는 테이블의 명칭TB_MBR_BAS : 참조되는 테이블명type : 검색하는 방식ALL : TABLE의 모든 ROW를 스캔 위의 이미지는 임의로 만든 자료를 이용해 Query를 실행한 결과입니다. 실행 계획은 TABLE : TB_MBR_BAS 를 TYPE : ALL 전체 검색한다고 나옵니다. 실행한 내용도 같습니다. 여기서 MBR_NM 이 “나서영”인 자료를 검색해볼까요. WHERE 조건이 들어가자 실행 계획도 내용이 변경되었습니다. rows와 Extra에도 값이 있는데요. 두 항목을 잠시 짚고 넘어가겠습니다. rows : Query를 수행하기 위해 접근해야 하는 열의 수Extra : MySQL 이 Query 를 수행할때의 추가 정보Using where : Query 수행시 TABLE에서 값을 가져와 조건을 필터링 함 위의 결과처럼 전체를 검색해 필요한 자료만 추출하는 것을 FULL TABLE SCAN or FULL SCAN 이라고 합니다. 그러나 FULL SCAN은 성능이 좋지 않기 때문에 우선 꼭 필요한 Query인지 검토해야 합니다. 보통 MBR_NM에 INDEX를 추가해서 해결하는데요. INDEX를 추가해서 같은 Query를 수행하면 실행 계획은 어떻게 달라질까요. 분명 같은 Query였는데 INDEX에 따라 실행 계획이 변경된 걸 알 수 있습니다. INDEX를 추가해도 수행한 결과는 같지만 검색 속도에 많은 차이가 있습니다. 각 항목에 대한 설명type - ref : 인덱스로 자료를 검색하는 것으로 현재는 매칭(=) 자료 검색을 나타냄possible_keys : 현재 조건에 사용가능한 INDEX를 나타냄(인덱스가 N개일 수 있음) IX_MBR_BAS_02 : 현재 조건에 사용 가능한 INDEXkey : Query 수행시 사용될 INDEX (possible_keys 가 N 개일 경우 USE INDEX, FORCE INDEX, IGNORE INDEX 로 원하는 INDEX 로 바꾸어 수행할수 있음)key_len : 수행되는 INDEX 컬럼의 최대 BYTE 수를 나타냄152 : 수행되는 INDEX 컬럼의 BYTE 수가 152ref : INDEX 컬럼과 비교되는 상수 여부 or JOIN 시 선행 컬럼 constant : 상수 조건으로 INDEX 수행rows : 678 : 678 rows 접근하여 값을 찾음Extra : using index condition : INDEX 조건에 대하여 스토리지 엔진이 처리(MySQL의 구성에서 스토리지 엔진과 MySQL 엔진이 통신을 주고 받는데 스토리지 엔진에서 처리 하여 속도가 향상됨) ConclusionINDEX가 없으면 결과가 나오기까지 5초 정도 걸리지만, 반대로 INDEX가 있으면 1초 안에 결과가 나옵니다. 별거 아닌 것 같아 보이지만 실무에서는 엄청난 차이입니다. Query를 작성할 때 실행 계획을 확인하고 조금이라도 빨리 결과가 나올 수 있도록 하는 것이 중요하기 때문이죠. 다음 글에서는 단일 TABLE 을 SELECT하는 것을 주제로 이야기를 나눠보겠습니다. 무사히 SELECT하길 바라며.글한석종 부장 | R&D 데이터팀hansj@brandi.co.kr브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 687

[Buzzvil Culture] Strategy Talk for Engineer Hiring : How we hire engineers

 버즈빌에서는 전사 차원에서 고민하고 있는 회사의 현안과 전략적 방향성에 대해 모두와 함께 공유하고 의견을 나눈다는 취지 하에 한 달에 한 번 Strategy Talk을 진행하고 있습니다. Strategy Talk의 주제는 매 달의 화두와 고민에 맞게 진행되고 있는데요. 지난 번에 버즈빌 블로그를 통해 소개드렸던 Machine Learning(AI) 부터 프로덕트 로드맵, 시장 동향, 그리고 회사의 비전과 미션 등 다양한 주제로 진행되고 있습니다. 이번 달, Strategy Talk은 ‘버즈빌의 Engineer Hiring Strategy’ 라는 주제로 진행되었습니다. 이번 세션은 버즈빌의 Product side를 총괄하고 있는 Young의 주도하에 진행되었는데요. 세션을 통하여 왜 버즈빌이 더 많은 엔지니어가 필요한지에 대한 배경부터 어떤 방법들을 통해 채용을 해 나갈 것인지, 나아가 버즈빌이 어떤 모습으로 변해갈 것인지에 대한 내용을 공유하고 함께 논의하는 시간을 가졌습니다.새로운 개발자들을 대규모 채용하는 것이 어떤 의미가 있을까요? 기본적으로 새로운 사업을 만들어가며 공격적으로 성장하고 현재 리소스의 한계 때문에 진행하지 못하고 있는 기존 Product의 개선 작업들을 진행 하기 위해서는 당연히 충분한 개발자들이 합류하는 것이 필요합니다. 뿐만아니라 다양한 경험을 가진 더 많은 개발자를 채용하는 것은 ‘버즈빌의 개발문화’와도 큰 연관이 있습니다. 버즈빌은 좋은 개발문화를 가지고 있기로 유명합니다. 수평 / 자율 / 성장 삼박자가 고루 갖추어진 환경이라고 할 수 있는데요. 개발팀의 개발자들 모두가 동등한 Software Engineer로 일하고 있고 그만큼 개발 과정에서 의견 교환이 자유롭게 일어납니다. 누군가의 일방적인 지시가 아닌 모두가 최적이라고 합의할 수 있는 방향으로 개발을 진행해 나가고 있습니다. 뿐만아니라 개발 과정에서 각각의 엔지니어가 본인의 업무를 맡아 주도적으로 처리해 나가며 자신이 맡은 이슈에 대해 주인의식을 가지고 일하고 있습니다. 따라서 특정 개발 방향이 주어져서 틀에 박힌 개발을 해야한다거나 다른 사람의 눈치를 보면서 맞춰가는 개발을 해야하는 건 버즈빌의 개발문화와는 거리가 멀다고 할 수 있습니다. 그리고 개인의 성장을 위해 여러가지 지원을 하고 있는데요. 업무를 진행하는 과정중에 필요하다면 AWS의 다양한 서비스를 포함한 여러가지 툴들을 자유롭게 사용해 볼 수 있습니다. 외부에서 열리는 세미나 / 강연등에 참여하는 것을 독려하며 회사에서 관련비용을 지원하기도 합니다. 이러한 개발문화를 가지고 있기에 버즈빌은 개발자들이 자신의 역량을 100% 발휘할 수 있고 새로운 것들을 배워가며 성장해 나가기에는 최적의 조건을 가지고 있다고 할 수 있습니다. (버즈빌에 개발문화에 대한 보다 자세한 내용은 여기를 참고해 주세요!)이러한 버즈빌의 개발 문화를 유지하고 더 나아가 발전시켜 나가기 위해서도 다양한 경험을 가진 많은 엔지니어들이 합류하는 것이 긍정적인 영향을 미치리라 생각합니다. 지금도 내부적으로 개발자들이 돌아가면서 기술 관련 세미나를 진행하면서 서로의 노하우와 새로운 기술에 대한 논의들을 하고는 있지만 더 많은 개발자들이 합류 하면서 이런 기회 들을 더욱 확장해 나갈 수 있음은 물론 관심사가 맞는 개발자들 끼리 모여 관련 주제로 스터디 모임을 진행한다거나 새로운 사업모델 발굴을 위해서 버즈빌의 자원들을 활용하여 새로운 프로젝트들도 진행해 볼 수 있을 것입니다.
조회수 1350

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팀gojs@brandi.co.kr브랜디, 오직 예쁜 옷만
조회수 729

[인공지능 in IT] '머신 비전', 내 눈에 걸리기만 해봐

50~60년대 국내 상황은 말로 표현하기 힘들다. 당시 강대국들은 전쟁 직후 한국이 다시 정상적으로 복귀하는 것은 불가능하다고 여길 정도였으니, 여러 모로 살아남기 힘든 환경이었던 것만은 분명하다. 하지만, 뭐든지 열심히 노력하는 특유의 국민성을 바탕으로 한걸음씩 내딛기 시작했고, 1988년 서울 올림픽까지 개최할 정도로 경제 성장을 이뤘다. 당시 필자가 태어난 것은 아니었지만, 여러 자료나 부모님 세대의 말씀을 조합하면, 이 같은 성장의 중심에는 제조업의 부흥이 있었기 때문이다.제조업은 국가 실물 경제의 근간이라고 할 정도로 중요한 역할을 담당한다. 단단한 제조업 생태계가 창출해 내는 부가가치를 바탕으로 서비스업이 발전한다면, 산업의 경쟁력을 잃지 않으면서 지속적인 성장을 이뤄낼 수 있는데 큰 보탬이 된다. 최근에는 인공지능과 같은 고도의 기술이 널리 퍼져 제조업의 중요성을 더욱 부각하고 있다. 전통적인 기계 산업 기술은 과학기술을 지탱하는 뿌리의 역할을 하고, 인공지능이나 데이터의 확장 등 탄탄한 제조업 중심의 주력 산업과 융합해 폭발적으로 성과를 낼 수 있다. 결국, 아무리 새로운 기술이 등장한다 해도, 제조업과는 떼려야 뗄 수 없는 관계인 셈이다.인공지능은 제조업에서 매우 유용하게 쓰이고 있다. 그 중에서 공장 자동화에 큰 역할을 하고 있는 '머신 비전(Machine Vision)'에 대해서 이야기를 해보자. 머신 비전은 사물인식, 얼굴인식, 이미지 캡션, 문자 인식 등 여러 형태로 적용되며, 최근 들어 딥 러닝을 통해 더욱 강력해지고 있다. 특히, 비전을 활용해 불량품을 검출하는 'Defect Detection'은 제조업에서 큰 역할을 할 수 있다. 대다수의 공장에서 제품 생산 마지막 공정은 '품질보증(Quality Assurance, QA)'이다. QA를 통해서 생산한 제품 혹은 부품에 문제가 없는지 확인한 후, 구매자에게 좋은 품질의 제품만을 제공해야 하기 때문에 매우 중요하다.실제로 대량생산라인을 보유하고 있는 제조업 기반 기업은 QA에 막대한 비용을 소모하고 있다. 때문에 유심히 확인하지 않거나, 몇몇 샘플들만 체크하고, 심지어 QA를 생략하는 경우도 있다. 결국 피해는 고스란히 최종 구매자에게 이어진다. 예를 들어, 새로 장만한 스마트폰이나 자동차 부품에 흠집이 있는 경우, 최종 구매자가 겪어야 할 불편함은 작지 않다. 또한, 고객 충성도 하락까지 이어질 수 있어 기업은 사전에 방지해야 한다.불량품 검출이 이루어지는 프로세스를 간단하게 알아보자. 스켈터랩스의 정수익 책임 PM의 도움을 받아 이미지로 구성했다.< 불량품 검출 프로세스, 출처: 스켈터랩스 >먼저 부품 생산 과정 중 불량을 탐지하기 위해서는 광학 기기를 사용해 사진을 찍어야 한다. 그리고 촬영된 사진을 이용해 머신 비전으로 탐지하는 것이다. 하지만, 머신 비전이 적용되었다고 해서 바로 족집게처럼 불량품을 검출해낼 수 있는 것도 아니다. 이미 많은 이들이 알고 있지만, 딥 러닝은 수많은 데이터셋을 바탕으로 선행한 학습 전제가 필요하다. 결함으로 판명된 부품들에 대한 데이터를 수집하고, 학습해 '이 부품은 이런 형태의 손상이 있으니 불량이다'라고 판단하는 방식이다. 인식하고, 학습하고, 검출하는 단계를 계속해서 반복하며 기계가 점점 '똑똑해진다'라고 할 수 있다.이어서 스켈터랩스의 사례를 참고해보자. 내부에서 개발하고 있는 불량품 검출 서비스는 크게 세가지 부분으로 구성된다. 파란색 네모 안에 있는 이름은 가제다.< 스켈터랩스의 머신 비전 불량품 검출 서비스 >하나씩 살펴보면, 'Dulok'은 실제로 현장에서 촬영되는 이미지를 모니터링하거나, 이를 클라우드에 업로드하는 '모니터링 모듈'이며, 'Ewok'은 웹상으로 부품 정보에 대해 'curation', 'labeling', 추론 결과를 확인할 수 있도록 하는 '애플리케이션'이다. 마지막으로 'Gorax'는 '학습을 통해 부품의 결함을 검출하는 모델'이다. 이 부분은 실제 서비스에서 단순히 딥 러닝을 통한 추론 외에도 다른 피쳐들이 제공되어야 한다.기존에는 사람이 이미지 상에서 결함에 대한 정의를 하나하나 내리고, 결함의 특징을 수동으로 설정해야 했다. 때문에 반도체나 LCD처럼 표면 형태가 정형화되어 있는 분야에서만 머신 비전 기술을 활용할 수 있었다. 반대로 섬유나 천연가죽 등 표면 형태가 비정형화된 분야에서는 결함 특징 값을 수동으로 설정하기 어려워 육안검사에 의존해야만 했다.그러나 점차 '머신 비전' 기술이 발전하면서 적용되는 영역은 계속 늘어나고 있다. 이는 품질을 높이는 결과로 이어져, 결과적으로는 최종 소비자들이 혜택을 받는다. 이처럼 인공지능 기술은 향후 지속적으로 발전을 거듭해 제조업의 일자리를 뺏는 것이 아닌, 함께 공생하는 생태계를 구축하는데 도움될 것이라 생각한다.이호진, 스켈터랩스 마케팅 매니저조원규 전 구글코리아 R&D총괄 사장을 주축으로 구글, 삼성, 카이스트 AI 랩 출신들로 구성된 인공지능 기술 기업 스켈터랩스에서 마케팅을 담당하고 있다#스켈터랩스 #기업문화 #인사이트 #경험공유 #조직문화 #인공지능기업 #기술기업
조회수 1027

장애를 가장 빠르게 알아내는 액티브 트랜잭션

IT 서비스는 장애가 발생할 수 밖에 없습니다. 현대의 서비스는 지속적으로 커지고 복잡해지고 있습니다. 이를 해결하기 위한 MSA 구조는 장애의 규모를 줄여줄수 있지만 장애는 여전히 발생합니다. 그렇기 때문에 최근 애플리케이션 운영은 장애의 규모를 줄이고 장애를 빠르게 해결하는데 집중하고 있습니다.  그런데 이미 오래전부터 장애를 빠르게 해결하는 문화를 가진 지역이 있습니다. 바로 우리가 살고 있는 대한민국 서울입니다. 2000년에 엔터프라이즈의 IT 서비스가 태동될 때, 경험많은 해외 IT 기업들이 5년이 걸릴것이라 예상하는 ERP 시스템 통합을 한국의 기업들은 2년에서 3년만에 이뤄내는 기적(IT 지옥의 시작)을 이뤄냅니다. IT 기술이 전혀 없던 나라에서 빠르게 엔터프라이즈 IT 서비스들을 만들어 나가다보니 많은 문제들이 생겼습니다. IT 엔지니어의 혹사도 문제였지만 급하게 만들어진 IT 서비스들을 운영하는 것도 쉽지가 않았습니다. 2000년 중반의 어렵던 프로그래머의 삶을 대표하는 이미지이 때, 국내에 APM(Application Performance Mangement, 애플리케이션 성능 관리) 솔루션들이 혜성처럼 나옵니다. APM 솔루션을 통해 서비스 장애 원인을 알아낼 수 있었기 때문에 국내 엔터프라이즈 서비스를 운영하던 기업들에게 APM 솔루션은 단비와 같았습니다. 그리고 국내 APM 솔루션들은 해외 솔루션들과 비교되는 몇몇 특징을 가지고 있었는데, 그 중 하나가 실시간 어플리케이션 분석이였습니다. 그 중에서도 대표적인 실시간 분석 기능이 액티브 트랜잭션입니다. 액티브 트랜잭션애플리케이션 성능 분석 솔루션은 종료된 트랜잭션을 분석하는 기술입니다. 고객의 요청에서 응답까지의 과정을 트랜잭션이라고 합니다. 이렇게 완료된 고객의 요청을 하나 하나 분석하면 애플리케이션의 성능을 알아낼 수 있습니다. 그리고 액티브 트랜잭션은 종료되기 전의 트랜잭션을 분석하는 것입니다. 아직 완료되지 않은 트랜잭션을 분석하기 때문에 액티브 트랜잭션은 장애를 가장 빠르게 볼 수 있는 선행지표가 됩니다. 이해를 돕기 위해 아래에 벤더별 액티브 트랜잭션을 보여드립니다. 제니퍼소프트의 Active Service트랜잭션의 요청건수, 진행건수(Active Service), 완료건수가 상단에 나옵니다. 하단에는 다양한 방법으로 진행건수Active Service)를 보여주고 있습니다. 액셈의 Active Transaction트랜잭션의 요청건수, 대기건수(Active Service), 완료건수가 상단에 나옵니다. 하단에는 대기건수만(Active Transaction)를 보여주고 있습니다.와탭랩스의 Active Transaction트랜잭션의 진행건수를 원형으로 보여주고 있습니다. 와탭의 서비스는 대용량 분석을 위해 기존의 이퀄라이즈를 원형으로 보여주는 특징을 가지고 있습니다. 액티브 트랜잭션은 서비스를 오픈하는 과정에서 큰 효과를 보입니다. 아직 서비스가 완벽하지 않은 상태에서 부하 테스트를 하게 되면 서비스에 락이 걸리면서 트랜잭션이 연속으로 홀딩되면서 서비스 전체가 다운되기도 하는데, 이렇게 되면 종료된 트랜잭션으로는 분석이 불가능하기 때문입니다. 장애 상황에서의 액티브 트랜잭션장애 상황이 되면 일반적으로 액티브 트랜잭션의 양이 증가하게 됩니다. 아래는 와탭의 성능추이에서 볼수 있는 엑티브 트랜잭션의 건수를 표현하는 지표입니다. 평소 액티브 트랜잭션이 10건 이하였다면 아래와 같은 상황은 장애 상황일 확률이 높습니다. 마무리애플리케이션 성능을 분석하는 기준은 트랜잭션입니다. 데이터 분석 기준으로는 종료된 트랜잭션을 추적하는 것이 가장 중요합니다. 하지만 액티브 트랜잭션은 선행지표로서의 의미와 함께 종료된 트랜잭션으로 분석할 수 없는 상황을 알아낼 수 있는 중요한 지표이기도 합니다. 여러분이 사용하는 애플리케이션 성능 분석 도구가 있다면 액티브 트랜잭션 지표도 잘 활용하시기 바랍니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지

기업문화 엿볼 때, 더팀스

로그인

/