스토리 홈

인터뷰

피드

뉴스

조회수 6606

Elastalert로 로그 알람 구축하기

Elasticsearch로 로그를 수집하고 추세를 분석하기는 좋지만 실시간 알람을 받으려면 X-Pack Alerting 등을 이용해야 한다. 하지만 사용자 편의성 측면에서 개선할 점이 많다. 로깅 알람 전용이 아닌 다양한 용도로 커스터마이징해서 쓸 수 있게 설계한 탓일 수도 있다. 아무튼 대안을 살펴볼 필요가 있겠다 싶어서 Yelp가 공개한 Elastalert로 알람을(도) 적용해보았다.X-Pack Alerting과 비교했을 때 Yelp/elastalert의 장점은 명확하다. 무엇보다 시나리오별로 정해놓은 패턴에 따라 알람을 작성하면 일이 쉽게 끝난다. 여덟 가지 정도의 알람 타입이 있어서 상황에 맞는 템플릿을 가져다 쿼리만 살짝 고치면 된다.“Match where there are X events in Y time” (frequency type)“Match when the rate of events increases or decreases” (spike type)“Match when there are less than X events in Y time” (flatline type)“Match when a certain field matches a blacklist/whitelist” (blacklist and whitelist type)“Match on any event matching a given filter” (any type)“Match when a field has two different values within some time” (changetype)“Match when a never before seen term appears in a field” (new_term type)“Match when the number of unique values for a field is above or below a threshold (cardinality type)예를 들어 OutOfMemoryError라는 단어가 로그에 찍혔을 때 알람을 받고 싶다면 다음과 같이 Rule 파일을 준비한다.# Alert when the rate of events exceeds a threshold # (Required) # Rule name, must be unique name: OutOfMemoryError # (Required) # Type of alert. # the frequency rule type alerts when num_events events occur with timeframe time type: frequency # (Required) # Index to search, wildcard supported index: logstash-%Y.%m.%d* use_strftime_index: true # (Required, frequency specific) # Alert when this many documents matching the query occur within a timeframe num_events: 1 # (Required, frequency specific) # num_events must occur within this amount of time to trigger an alert timeframe: hours: 1 # (Required) # A list of Elasticsearch filters used for find events # These filters are joined with AND and nested in a filtered query # For more info: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html filter: - query_string: query: "message: OutOfMemoryError OR log: OutOfMemoryError" # (Required) # The alert is use when a match is found alert: - "slack"기본 템플릿을 가져다가 filter에 들어갈 쿼리만 다시 작성하면 일이 끝난다. 파이썬으로 작성한 간단한 소프트웨어라 사용하기도 쉽고 Docker로 만들기도 쉽다. pip install elastalert 그 외에 경험한 특이사항만 정리하고 이 글을 끝내려 한다.Elasticsearch의 플러그인으로 작동하는 X-Pack과 달리 ElastAlert는 독립 실행 애플리케이션이다. Kubernetes 같은 환경에서는 독립 실행 애플리케이션이 더 관리하기 쉽다.알람을 추가/삭제할 때마다 도커 빌드를 하기 힘든 환경이라면 RESTful API를 지원하는 X-Pack이 편할 것이다. back-end / elastalert 같이 RESTful API를 지원하는 ElastAlert 환경이 있긴 하지만 도커 배포환경에서는 여러 모로 한계가 있다. 도커를 올렸다 내렸다 하더라도 설정이 날아가지 않게 하려면 고민이 많아진다. node 애플리케이션과 Python 애플리케이션 둘을 하나의 도커 이미지로 제공하다 보니 다른 문제도 많다. 이런 식의 구성을 구현해봤다면 무슨 이야기인지 알 것이다.ElastAlerts는 Index Aliases를 지원하지 않는다. 물론 오픈소스이니 소스코드를 고쳐서 Pull Request를 보내면 될 일이다.X-Pack Alerting과 달리 알람 메시지를 정형화했다. 알람의 메시지 포맷을 조금 고칠 수는 있지만 기본적으로는 주어진 그대로 써야 한다. 간단하게 쓰기에는 낫고 그렇지 않다면 소스코드까지 손을 대야 한다.ElastAlert는 중복 알람 처리 등의 정책을 지정할 수 있다. 알람을 하루에 수백통 넘게 받아보면 이 기능이 왜 중요한지 알게 된다.문서에서 언급하듯 Elasticsearch 5.x와 함께 쓰려면 다음과 같이 버전을 명시하는 편이 좋다. pip install elasticsearch>=5.0.0 && pip install elastalert==0.1.8테스트 환경은 elastalert-test-rule 명령어를 제공하는 ElastAlert쪽이 더 낫다. 검색 쿼리를 제대로 작성했는지 알람 설정은 맞는지 확인하기가 쉬웠다.더 읽기ElastAlert: Alerting At Scale With Elasticsearch, Part 1ElastAlert: Alerting At Scale With Elasticsearch, Part 2Originally published at Andromeda Rabbit.#데일리 #데일리호텔 #개발 #개발자 #개발팀 #일지 #후기 #도입후기 #Elastalert #인사이트
조회수 3071

채널 iOS에 Redux를 적용하게 된 7가지 이유.

친숙한 MVC 패턴개발자라면 누구에게나 친숙한 MVC (모델 - 뷰 - 컨트롤러) 패턴은 꽤 오랜 시간 동안 사용됐고 아직까지 많은 개발자들에게 사랑받고 있는 패턴이다. 그 이유로는 이 패턴이 일단 진입장벽이 낮기도 하지만 코드 재사용성, 동시 개발의 용이성 때문이다. 만약 당신이 초보 iOS 개발자라면 높은 확률로 MVC 패턴을 쓰게될 것인데 그 이유는대부분의 예제 및 튜토리얼이 MVC 패턴을 쓰고 있고iOS의 IDE인 Xcode에서 (Swift 는 예외지만) 클래스를 생성할때 기본으로 이름에 ViewController라고 들어간다.위와 같은 이유로 많은 iOS 개발자에 영향을 주리라 생각된다. (2011년도부터 iOS 세계에 빠진 저자도 사실 iOS에서는 software architectural design pattern으로는 MVC가 넘사벽이라고 생가하고 있었기에) 문제는 상대적으로 복잡도가 높아지거나 코드의 양이 많은 제품의 개발에서는 생산성이나 가독성에 그다지 도움을 주지 못하는 데 있다고 생각한다. 예를 들어, 한 페이지의 복잡도가 높아지면 ViewController 파일 한 개의 코드 라인이 기하급수적으로 증가한다. 또 (코드 관리에 매우 신경을 쓰지 않는 이상) 객체 간의 통신 및 데이터의 통일성이 없어져서 가독성이 떨어지기 쉽고, 기능을 추가할 때 생산성이 점점 떨어지게 된다.왜 MVC 패턴은 이렇게 문제가 생기는걸까라는 질문에서부터 시작해보자.MVC 패턴, 도대체 뭐가 문제인가?!그림 1. 보편적인 MVC 패턴의 구조보편적으로, MVC 패턴의 구조는 위의 그림과 같다. 그림을 간단히 설명하자면:뷰에서 이벤트가 발생하면 컨트롤러에 알린다컨트롤러는 그것을 처리하고 모델에 업데이트를 하라고 전달한다.모델은 업데이트를 하고 컨트롤러에 다시 알린다컨트롤러는 모델이 업데이트되었다는 것을 뷰에 알린다뷰는 모델의 업데이트된 값에 따라 다시 뷰를 그린다그림 1과 위의 설명만 놓고 보면 각각의 역할이 명확하다고 생각한다. 구조가 복잡하지 않기 때문에 초보자들도 쉽게 이해하고 적용 가능하다는 것이 장점이다. 하지만 MVC 패턴은 객체 간에 어떤 방향으로 커뮤니케이션 해야 하는지에 대해서는 강제하지 않기 때문에 파생된 패턴들이 많이 있다. 실제로 구글에서 “MVC pattern”이라고 검색을 하면 위 그림과 다른 MVC 패턴 이미지들을 볼 수 있다. 그 한 가지 예가 밑에 그림 2이다.그림 2. 또 다른 MVC 패턴의 구조그림 2를 보면 그림 1과는 다른 커뮤니케이션 방향을 나타내고 있다. 바꿔 말하면 개발자가 원하면 언제든지 세 가지 구조 안에서 방향을 유동적으로 바꿔 써도 무방하다는 것이 된다 (그것이 원하는 MVC 패턴이든 아니든지 간에). MVC의 변형으로써는 여러 가지가 있지만, 대표적인 것들은 아래의 그림과 같이 MVP, MVVM 같은 것들이 있다.그림 3. MVC, MVP, MVVM 패턴의 비교실제 저자도 MVC 패턴이 커뮤니케이션 방향을 강제하지 않는 것과 관련해 문제를 겪은 경험이 여러 번 있었던 것을 기억한다. 한가지 예를 들어보자.ViewA.swift (뷰)protocol ViewADelegate {       func updateA() }   class ViewA : UIView {        var delegate: ViewADelegate?       //update through protocol      func didClickOnA() {          self.delegate?.updateA()     }      //update through notification     //maybe same kind of update can happen in other views      func didClickOnAA() {         NotificationCenter.default.post(             name: NSNotification.Name(rawValue: “updateFromA”),              object: nil         )     }      func render(_ model: product) {         //update based on model      }  } ViewController.swift (컨트롤러)class ViewController : UIViewController, ViewADelegate {       Var viewA: ViewA?     Var product = Product()     func viewDidLoad() {         self.viewA = ViewA()         self.viewA.delegate = self         // ...         self.view.addSubview(self.viewA)     }      func updateA() {         self.product.update(name: “aa”, version: “123”)         self.viewA.update(self.product)         //re-render viewA     }  } Product.swift (모델)class Product {       var name = “”     var version = “”     init() {         NotificationCenter.default.addObserver(             self,             selector: #selector(self.doSomething),             name: “updateFromA”, object: nil)     }      deinit {         NotificationCenter.default.removeObserver(self)     }      func update(name: String, version: String) {         self.name = name         self.version = version     }      func doSomething() {          //do something…          //notify viewA or any objects through notification     }  } 조금 극단적인 예처럼 보이긴 하지만 실제 개발을 하다 보면 충분히 일어날 수 있는 상황이다. 코드에 대해 간략하게 설명하자면:ViewA에서는 delegate와 notification으로 각각 ViewController와 Product에 이벤트를 날리고 있고ViewController에서는 delegate method를 구현해서 Product를 업데이트 후, 다시 ViewA를 그리라는 로직을 가지고 있다.Product 에서는 객체를 업데이트 할 수 있는 메소드가 있고 notification을 통한 업데이트를 하고 있다.이건 아주 간단한 예이지만 프로젝트가 커진다면 특정 이벤트에 대해 데이터가 업데이트되는 경로가 달라질 수 있다. ViewA -> Product -> SubProduct -> Product -> ViewA 의 경로라던가, ViewA -> Controller -> Product -> SubProduct -> Controller -> ViewA 의 경로 등이 가능하다. 이처럼 특정 이벤트에 대해 여러 가지 체인형식으로 업데이트가 이루어질 경우 그 경로를 일일이 추적하는데 시간이 걸릴 수밖에 없는 구조를 가지고 있는 것을 볼 수 있다.(프로젝트의 크기가 어느정도 커지게 된다면 이렇게 될지도 ㅎㅎ)이런 케이스가 발생하는 근본적인 이유는 결국 MVC 패턴의 장점이라고도 말할 수 있는 유연성과 양방향 커뮤니케이션 때문이다. 이 패턴 자체가 문제가 있는 것은 아니지만 결국 코드는 사람이 작성하는 것이기에 생산성과 가독성을 떨어뜨리는 결과를 초래할 가능성이 높다. 여기에서 우리는 기존 웹 개발에서 쓰이고 있던 Redux 도입을 생각하게 된 것이다.Redux는 무엇인가?Redux 로고Redux는 Facebook의 Flux 를 모태로 삼고 있고 예측 가능한 상태를 자바스크립트 프로그램에서 구현하기 위한 애플리케이션 아키텍쳐이다. Redux는 본래 자바스크립트에서 시작한 오픈소스 프로젝트이지만 다른 개발자들에게 영감을 주었고 2015년 말쯤 iOS 플랫폼에서는 ReSwift(Redux + Swift)가 생겨났다. ReSwift는 결국 Redux랑 크게 다르지 않고 Redux의 세 가지 법칙을 따른다.Single source of truth — 애플리케이션의 전체 상태(State, 또는 데이터)는 트리 형태의 하나의 저장소(Store)로 저장된다.Changes with pure functions — 상태 트리를 변경하는 리듀서(Reducer)는 순수 함수(pure function)이어야한다.Read-only states — 상태는 오직 액션(Action, 어떤 일이 일어날 것인지 설명할 수 있는 객체)으로만 변화가 가능하다.쉽게 말하자면 “Redux는 한 개의 상태 저장소를 가지고 있고 그 안에 있는 데이터만이 신뢰할 수 있으며 저장소의 상태는 오직 순수 함수인 리듀서를 통해서만 변화가 가능하다” 라고 축약 할 수 있다.그림 4 Redux 패턴의 구조위의 그림 4을 보면 충분히 프로그램의 흐름이 어떤 식으로 흐르는지 감이 왔으리라 생각한다.이벤트가 뷰에서 생성되면 그에 해당이 되는 액션을 통해 알린다.액션은 특정 리듀서에서 처리한다.리듀서는 액션에 따라 저장소를 업데이트한다.저장소에 변화가 오면 구독(Subscribe)을 하고 있는 모든 객체에 알린다.이것이 Redux의 커뮤니케이션 사이클이다. Redux만으로도 충분히 여러가지 블로그 주제가 나올 정도로 할 이야기가 많지만 여기까지만 하고, 좀 더 자세한 디테일을 알기 원한다면 옆의 링크를 클릭하시면 된다. :) -> 리덕스 공식 링크Redux vs. MVCMVC와 Redux에 대해 소개를 했으니 간단히 비교해 보자.The Flow — Redux는 데이터 및 애플리케이션의 흐름을 강제한다. 저장소의 변화는 오직 액션을 통해서만 가능하기 때문이다. 이와 다르게 MVC는 강제성이 없기 때문에 여러가지 파생 패턴이 생길 수 있다.Unidirectional flow — Redux에서 흐름은 액션으로만 변화가 일어나기 때문에 오직 한쪽으로만 흐른다. MVC에서는 양방향이 될 수도 있고 한 방향이 될 수도 있지만 보통 양방향이다.Stores — Redux에서는 상태 및 데이터가 하나의 저장소에 있기 때문에 관리하기가 쉬운 반면, MVC에서는 여러 군데에 상태가 분리되어 있기 때문에 동기화에 신경을 써야 한다. (로컬 데이터 스토리지를 쓴다면 문제가 해결되기는 하지만 패턴 이외에 추가적인 노력이 필요함)그 이외에 여러가지 다른 점이 있겠지만, 위의 3가지가 가장 다른 점이라고 저자는 생각한다.채널 데스크 iOS에 Redux를 적용하게 된 이유이제 MVC와 Redux의 차이점을 알게 되셨으리라 생각한다. 우리 팀이 채널 데스크 iOS에 Redux를 적용한 이유를 소개하려고 한다. 아직 모든 부분에 완벽히 적용한 상태는 아니지만 (부분적으로 Notification, Delegate 그리고 Reactive를 쓰고 있다) 그럼에도 Redux를 적용함으로써 얻는 이점이 많다고 느끼고 있다.Explicit data flow — 새로운 개발자가 왔을 때나 여러 명이 작업을 할때 애플리케이션의 전체 흐름을 파악하고 이해하기 쉽다.Unidirectional flow — 데이터 관련 부분을 전부 Redux로 대체하니 모든 데이터 흐름이 한 방향으로 강제되었다. 덕분에 데이터가 어디에서 왔고 어디로 가는지를 파악하기 매우 쉽다.Single storage — 한 곳에서만 데이터를 관리하기 때문에 데이터에 관한 부분은 리듀서만 잘 짜 놓으면 관리하기 쉬워진 점이 있다. Redux를 적용하기 전에 CoreData를 데이터 저장소로 쓰고 있었는데, 어느 시점에 어떻게 저장되는지 눈에 들어오지 않아 불편한 점을 Redux를 사용함으로써 해결할 수 있었다.Immutability and data consistency — 변경 가능한(Mutable) 객체는 보통 iOS 개발에서나 다른 플랫폼 개발에서 장점일수도 있다. 하지만 데이터의 일관성이 깨지기 쉽다. 만약 A에서의 데이터와 B에서의 데이터가 다르면 어떤 것을 신뢰해야 하는지의 문제도 생길 수 있다. 우리는 Redux의 저장소에 있는 데이터를 모두 변경 불가능한 객체(Immutable, Swift에서는 Struct을 쓴다)로 구현하여 이 문제를 해결하였다. 이 부분은 코딩할 때 불편한 점이 조금 있지만, 그 불편함을 감수할만한 가치가 있다고 생각한다.Predictability — 저장소는 오직 액션을 통해서만 변경할 수 있다는 점이 무엇보다 장점인 것 같다. MVC와 같이 데이터를 어디서든 변경할 수 있다면 데이터와 관련된 버그를 찾는 데 소비하는 시간이 길어지곤 한다. Redux는 어떤 액션이 어디에서 불리는지 아는 것만으로도 그 시간을 비약적으로 단축할 수 있다.Maintainability — 저장소, 상태, 액션 그리고 리듀서로 역할과 레이어를 분리하게 되니 보통 코드 라인이 100줄을 넘지 않는다. 그만큼 유지보수 비용이 적어졌다.Organized Code — MVC 패턴에서는 비지니스 로직이 뷰에 들어가는 경우가 있기도 했었는데 Redux의 가이드라인을 따름으로써 자연스럽게 대부분의 뷰는 그저 데이터를 받고 시각화하는 dummy 뷰의 형태가 되었다. 비즈니스 로직이 완전히 뷰와 분리됨으로써 뷰의 복잡도와 코드를 관리하기가 쉬웠다.ReSwift 도입 시 주의할 점ReSwift 도입을 고려하는 분들을 위해 몇 가지 주의할 점을 소개하겠다.Performance — ReSwift에서는 저장소가 변경될 때마다 newState: 메소드가 호출이 되어 화면을 업데이트할 수 있게 되어있다. 채널 데스크 같은 경우는 실시간 애플리케이션(Real-time application)이라서 API 이벤트와 Socket 이벤트가 자주 발생해서 저장소가 변경되는데, 도입 초기 단계에 이 부분을 간과해서 화면이 거의 멈출 정도로 퍼포먼스가 나오지 않았었다. 만약 ReSwift를 적용했는데 퍼포먼스가 나오지 않는다면 newState: 함수 부분을 최적화하거나 미들웨어(middleware)를 만들어서 batch 형식으로 액션을 처리하는 방식을 고려해봐야 한다.Not thread safe — ReSwift는 thread-safe 하지 않아서 초반에 알 수 없는 crash들이 자주 발생했었다. 저자 같은 경우는 ActionWrapper를 만들어서 액션은 항상 메인스레드에서 처리되도록 강제했다.글을 마무리하며..Redux는 이미 자바스크립트 개발에서는 React와 함께 많이 쓰이고 있지만 iOS에서는 아직도 생소한 아키텍쳐이다. ReSwift는 아직 2년도 되지 않은 프로젝트이고 자바스크립트에서 처럼 유용한 Redux 미들웨어도 많지 않다. 또한 인지도도 MVC, MVVM, MVP에 아직 미비한 편이다. 프로덕션에 참고할 만한 예제도 찾기 어려웠기에 초기 러닝 커브는 조금 있었던 것으로 회상한다. 그럼에도 불구하고 우리 팀은 ReSwift를 적용해 보다 깨끗하고 유지보수하기 쉬운 프로그램을 만들 수 있었고 만족하며 사용하고 있다. 기존 MVC의 불편함을 아시는 분들은 충분히 도입할 가치가 있다고 생각한다.#조이코퍼레이션 #개발자 #개발팀 #인사이트 #경험공유 #일지 #Redux
조회수 5554

Next.js 튜토리얼 1편: 시작하기

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기  - 현재 글2편: 페이지 이동3편: 공유 컴포넌트4편: 동적 페이지5편: 라우트 마스킹6편: 서버 사이드7편: 데이터 가져오기8편: 컴포넌트 스타일링9편: 배포하기개요요즘은 싱글 페이지 JavaScript 애플리케이션을 구현하는게 꽤 어려운 작업이라는 것을 대부분 알고 있습니다. 다행히도 간단하고 빠르게 애플리케이션들을 구현할 수 있도록 도와주는 몇 가지 프로젝트들이 있습니다.Create React App이 아주 좋은 예시입니다.그렇지만 여전히 적당한 애플리케이션을 구현하기까지의 러닝 커브는 높습니다. 클라이언트 사이드 라우팅과 페이지 레이아웃 등을 배워야하기 때문입니다. 만약 더 빠른 페이지 로드를 하기위해 서버 사이드 렌더링을 수행하고 싶다면 더 어려워집니다.그래서 우리는 간단하지만 자유롭게 설정할 수 있는 무언가가 필요합니다.어떻게 PHP로 웹 애플리케이션을 만드는지 떠올려봅시다. 몇 개의 파일들을 만들고, PHP 코드를 작성한 다음 간단히 배포합니다. 라우팅에 대해 걱정하지 않아도 됩니다. 그리고 이 애플리케이션은 기본적으로 서버에서 렌더링됩니다.이것이 바로 우리가 Next.js에서 수행해주는 일입니다. PHP 대신에 JavaScript와 React를 사용하여 애플리케이션을 구현합니다. Next.js가 제공하는 유용한 기능들은 다음과 같습니다:기본적으로 서버 사이드에서 렌더링을 해줍니다.더 빠르게 페이지를 불러오기 위해 자동으로 코드 스플릿을 해줍니다.페이지 기반의 간단한 클라이언트 사이드 라우팅을 제공합니다.Hot Module Replacement(HMR)을 지원하는 Webpack 기반의 개발 환경을 제공합니다.Express나 다른 Node.js HTTP 서버를 구현할 수 있습니다.사용하고 있는 Babel과 Webpack 설정을 원하는 대로 설정할 수 있습니다.설치하기Next.js는 Windows, Mac, Linux와 같은 환경에서 동작합니다. Next.js 애플리케이션을 빌드하기 위해서는 Node.js가 설치되어 있어야 합니다.그 외에도 코드를 작성하기 위한 텍스트 에디터와 몇 개의 명령어들을 호출하기 위한 터미널 애플리케이션이 필요합니다.Windows 환경이라면 PowerShell을 사용해보세요.Next.js는 모든 셀과 터미널에서 동작하지만 튜토리얼에서는 몇 개의 특정한 UNIX 명령어를 사용합니다.더 쉽게 튜토리얼을 따르기 위해서는 PowerShell 사용을 추천합니다.맨 먼저 다음 명령어를 실행시켜 간단한 프로젝트를 생성하세요:$ mkdir hello-next$ cd hello-next$ npm init -y$ npm install --save react react-dom next$ mkdir pages그런 다음 hello-next 디렉토리에 있는 "package.json" 파일을 열고 다음과 같은 NPM 스크립트를 추가해주세요.이제 모든 준비가 끝났습니다. 개발 서버를 실행시키기 위해 다음 명령어를 실행시키세요:$ npm run dev명령어가 실행되었다면 브라우저에서 http://localhost:3000 페이지를 여세요.스크린에 보이는 출력값은 무엇인가요?- Error No Page Found- 404 - This page could not be found- Hello Next.js- Hello World404 Page다음과 같은 404 페이지가 보일 것입니다.첫 번째 페이지 생성하기첫 번째 페이지를 생성해봅시다.pages/index.js 파일을 생성하고 다음의 내용을 추가해주세요:이제 http://localhost:3000 페이지를 다시 열면 "Hello Next.js" 글자가 있는 페이지가 보일 것입니다.pages/index.js 모듈에서 간단한 React 컴포넌트를 export 했습니다. 여러분도 React 컴포넌트를 작성하고 export 할 수 있습니다.React 컴포넌트가 default export 인지 확인하세요.이번에는 인덱스 페이지에서 문법 에러를 발생시켜봅시다. 다음은 그 예입니다: (간단하게HTML 태그를 삭제하였습니다.)http://localhost:3000 페이지에 로드된 애플리케이션은 어떻게 되었나요?- 아무일도 일어나지 않는다- 페이지를 찾을 수 없다는 에러가 발생한다- 문법 에러가 발생한다- 500 - Internal Error가 발생한다에러 다루기기본적으로 Next.js는 이런 에러들을 추적하고 브라우저에 표시해주므로 에러들을 빨리 발견하고 고칠 수 있습니다.문제를 해결하면 전체 페이지를 다시 로드하지 않고 그 페이지가 즉시 표시됩니다. Next.js에서 기본적으로 지원되는 웹팩의 hot module replacement 기능을 사용하여 이 작업을 수행합니다.You are Awesome첫 번째 Next.js 애플리케이션을 구현하였습니다! 어떠신가요? 마음에 드신다면 더 많이 배워봅시다.마음에 들지 않는다면 우리에게 알려주세요. Github 저장소의 issue나 Slack의 #next 채널에서 이야기 할 수 있습니다.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 1420

린더를 만들고 있는 이유 2.0

본문은 2017년 8월 작성한 린더를 만들고 있는 이유 1.0 의 후속편입니다.히든트랙이 해결하고자 한 문제히든트랙팀은 '린더'라는 일정을 받아보는 경험을 만들어가고 있습니다. 2018년 4월 기준 약 16만명의 사용자가 린더를 통해 일정을 받아보고 있으며, 린더가 존재하기 전 사람들을 일일히 자신들이 필요로 하거나 궁금한 일정들을 검색하여 확인해야만 했습니다. 우리가 문제를 해결한 방식은 매우 간단합니다. 매번 필요할 때마다 검색해봐야 했던 일정을 우리가 대신 기록하여 그것을 받아볼수 있도록 제공 하는것, 다시 말해 다수가 공통적으로 안고 있던 귀찮음을 소수의 노력으로 해결하고자였으며 이와 같은 문제 해결 방식은 명함 수기 입력 앱 - 리멤버 또는 전단지 모음 앱 - 배달의 민족이 접근한 방식과 유사합니다.첫 번째 선택, 캘린더 기반 일정 구독 ( https://linder.kr/ )일정을 받아보는 경험은 모바일앱, 챗봇, AI 스피커 등 다양한 방식으로 구현될 수 있습니다. 그중에서도 히든트랙팀이 선택한 첫 번째 방식은 이미 다수가 활용중인 캘린더 앱의 구독 기능을 활용한 것입니다. 스마트폰 기본앱인 캘린더를 하나의 정보 전달 채널로 활용함으로써 거부감 없이, 낮은 진입장벽으로 출시 반년 만에 15만명이 넘는 사용자를 확보할수 있었습니다.캘린더 기반 일정 구독의 한계하지만 캘린더를 기반으로 한 일정 구독에는 명확한 한계가 몇 가지 있었습니다. 1) 구독 캘린더의 특성상 리마인더 기능이 매우 제한적이었으며  2) 각 플랫폼 별 다른 동기화 시간으로 인해 실시간 업데이트가 불가했습니다. 3) 또한 기존 캘린더에 입력되어있던 개인 일정과 받아보는 일정이 혼재되어 분류가 어려웠으며 4) 일정을 삭제하거나 메모를 입력할 수 없었습니다.캘린더의 한계를 극복할 수 있는 자체 앱 제작 ( http://bit.ly/2EB41TW )이에 히든트랙팀은 지난 2017년 말 진행한 다수의 유저 인터뷰를 바탕으로 2018년 1월부터 약 3개월 간 일정을 받아보는 경험에 최적화된 모바일 앱을 개발하였습니다. 모바일의 핵심은 필요한 일정을 정확한 시점에, 검색 없이도 쉽게 받아 볼 수 있는데 초점을 두고 있습니다.린더 : 받아보는 캘린더 - Google Play 앱play.google.com 일정을 받아보는 경험에 대한 사용자와 이해 관계자히든트랙팀이 캘린더 기반의 일정 구독자와 모바일앱 사용자 모두에게 공통적으로 제공하고자 하는 것은 사용자가 자신에게 필요한 일정을 보다 쉽고 확실하게 소비할 수 있도록 돕는 것입니다. 사람들에게 필요한 일정은 아이돌 스케줄부터 화장품 세일, 학사일정에서부터 마트 휴무일까지 다양한 분야에 존재합니다. 일정을 받아보는 경험을 만들어가는 과정에서 우리가 일반 사용자 외에도 고려해야 할 나머지 두 종류의 이해 관계자는 일정을 공급하는 공급 파트너와 유통을 돕는 유통 파트너가 있습니다.망하기 딱 좋은 일정 데이터 생산 비즈니스일정을 받아보는 경험을 만들어가는 과정은 여느 타 서비스에 비해 매우 소모적입니다. 일정 데이터는 리뷰(왓챠)나 댓글(크리마), 연락처(리멤버) 등 과는 다르게 데이터의 휘발성이 매우 강하며 변동성 또한 매우 크기 때문에 다수의 기업들이 기피하는 데이터 형태라고 볼 수 있습니다. 일례로 2016년부터 2017년 중순까지 운영되었던 SKT의 Someday(썸데이)는 내부 조직장 교체와 비효율적인 ROI로 서비스가 종료된 바 있습니다.같은 실수를 저지르지 않기 위한 일정 데이터 서비스 전략 로드맵히든트랙팀은 2017년 1월부터 다수의 일정 관련 서비스 개발을 진행해왔으며 이 과정에서 습득한 노하우를 바탕으로 일정 데이터 생산 및 공급망을 구축할 수 있는 3단계 계획을 세우게 되었습니다.STEP.1 린더 파트너스 - 데이터 공급 파트너 확보캘린더 기반 일정 마케팅 솔루션 '린더 파트너스'는 해외 eCal, CalendarX, Eventable 등 다수의 캘린더 마케팅 업체를 벤치마킹하여 국내 인터넷 환경에 맞추어 최적화시킨 아시아 유일의 캘린더 마케팅 솔루션 입니다. 2018년 3월 기준 롯데자이언츠, 두산베어스, 수원삼성FC, 아디다스 코리아 등 20여 개의 데이터 공급 파트너를 확보한 린더 파트너스를 기반으로 히든트랙팀은 공식적인 데이터 공급 파트너를 확보함과 동시에 데이터 생산을 위한 초기 자본을 조달할 수 있게 되었습니다. 파트너스 영업은 현재 영업팀을 주축으로 이루어지고 있으며 2018년 말까지 현 20여 개의 파트너를 50여 개 수준으로 늘리는 것을 목표로 하고 있습니다.STEP.2 린더 모바일앱 - 일반 사용자 확보린더 파트너스를 통해 확보한 자금과 일정 생산력을 바탕으로 모바일앱 데이터의 정확도와 품질을 향상하고 사용자 중심의 서비스를 구축합니다. 기업 친화적인 린더 파트너스와는 다르게 린더 모바일앱은 오로지 일반 사용자를 위한 서비스로서 사용자 친화적인 인터페이스와 일정 콘텐츠 소비 경험을 핵심으로 합니다. 다수의 일반 사용자를 확보함으로써 제보 기능(크라우드소싱)을 활용하여 데이터의 정확도와 유저별 선호 캘린더 데이터를 파악할 수 있게 됩니다.  2018년 4월 안드로이드/iOS 앱 출시가 예정되어 있으며 2018년 연말까지 5만 이상의 MAU 확보를 목표로 하고 있습니다.STEP.3 린더 데이터헙 - 데이터 유통 파트너 확보글의 서두에서 언급한 바와 같이 일정을 받아보는 경험은 단순히 캘린더나 모바일앱 외에도 다양한 방식으로 제공될 수 있습니다. 확보한 데이터 공급 파트너와 일반 사용자 제보를 바탕으로 일정 데이터량과 품질을 향상하고, 더 나아가서는 보유한 유저 Pool을 바탕으로 사용자들의 선호도를 사전에 파악할 수 있게 됩니다. 이러한 다양한 종류의 데이터를 기반으로 현재 스피커 및 기타 AI 서비스를 제공 중인 네이버, 카카오, 삼성, SKT, KT 등의 유통 파트너를 대상으로 영업을 진행, 협력을 통해 다양한 방식으로 사용자들에게 일정 정보를 전달할 수 있게 됩니다.히든트랙의 3가지 비즈니스 모델위 언급한 3단계의 전략 로드맵을 통해 히든트랙은 3가지 수익창출 기회를 확보할 수 있습니다. 1) 캘린더 마케팅 솔루션 - 린더 파트너스의 Enterprise SaaS 형태 공급 및 데이터 관리 용역을 통한 수익2) 린더 앱 내 확보한 사용자 선호도를 바탕으로 일정 기반의 마케팅 광고주들에게 제공하여 창출하는 수익 3) 그리고 유통 파트너들에게 일정 데이터를 제공하는 대가로 받는 데이터 판매 및 용역에 대한 수익 이 바로 그 3가지 입니다.'린더' 하다 = 일정을 받아보다다각적인 비즈니스 모델과 단계가 존재하지만 결과적으로 이를 통해 확보한 매출의 재투자와 회사의 방향성은 하나로 일원화 될 수 있습니다. 그것은 바로 사람들의 소중한 일정을 놓치지 않도록 도와주는것. 자동차 네비게이션과 같이 서비스가 삶에 완벽히 녹아들어 그것이 부재하던 시절의 삶을 상상할 수 없게 되는 것이야 말로 가장 높은 수준의 서비스 구현이라 할 수 있습니다. 과거에 지도에만 의존하여 길을 찾던 시절 소수의 사람들이 네비게이션의 가능성을 보고 그것을 만들어왔던 것처럼, 사람들이 린더를 통해 그들의 소중한 일정을 놓치지 않도록 도와주는 것이 우리의 최종 목표입니다.#히든트랙 #챗봇 #기술기업 #개발자 #개발팀 #인사이트 #경험공유
조회수 4358

Flask로 만들어 보는 WSGI 어플리케이션

안녕하세요. 스포카 크리에이터팀 문성원입니다. 오늘은 WSGI(Web Server Gateway Interface)어플리케이션을 직접 작성해보고, 또 이런 작성을 보다 쉽게 도와주는 프레임워크 중 하나인 Flask에 대해서 알아보겠습니다.WSGIWSGI에 대해 기억이 가물하신 분들을 위해 지난 글의 일부를 잠깐 다시 살펴보죠.이 경우 uwsgi는 일종의 어플리케이션 컨테이너(Application Container)로 동작하게 됩니다. 적재한 어플리케이션을 실행만 시켜주는 역할이죠. 이러한 uwsgi에 적재할 어플리케이션(스포카 서버)에는 일종의 규격이 존재하는데, 이걸 WSGI라고 합니다.(정확히는 WSGI에 의해 정의된 어플리케이션을 돌릴 수 있게 설계된 컨테이너가 uwsgi라고 봐야겠지만요.) WSGI는 Python 표준(PEP-333)으로 HTTP를 통해 요청을 받아 응답하는 어플리케이션에 대한 명세로 이러한 명세를 만족시키는 클래스나 함수, (__call__을 통해 부를 수 있는)객체를 WSGI 어플리케이션이라고 합니다.글로는 감이 잘 안오신다구요? 그럼 코드를 보면서 같이 살펴봅시다. (모든 코드는 Python 2.7에서 테스트 되었습니다.)Hello World!def app(environ, start_response):    response_body = 'Hello World!'    status = '200 OK'    response_headers = [('Content-Type', 'text/plain'),                         ('Content-Length', str(len(response_body)))]    start_response(status, response_headers)        return [response_body]view rawgistfile1.py hosted with ❤ by GitHubapp은 일반적인 Python 함수지만, 동시에 WSGI 어플리케이션이기도 합니다. environ과 start_response를 받는 함수기 때문이죠.(PEP-333) 사실은 꼭 함수일 필요도 없습니다. 다음은 위의 app과 동일한 동작을 하는 WSGI 어플리케이션입니다.class App(object):    def __init__(self, environ, start_response):        self.environ = environ        self.start_response = start_response    def __iter__(self):        status = '200 OK'        response_body = "Hello World!"        response_headers = [('Content-Type', 'text/plain'),                            ('Content-Length', str(len(response_body)))]        self.start_response(status, response_headers)        yield response_bodyview rawgistfile1.py hosted with ❤ by GitHubApp는 Python 클래스(Class)로 environ과 start_response를 멤버 변수로 가지는데, 여기에는 약간의 트릭이 있습니다. 생성자(Constructor)인 App를 함수처럼 사용하게 하여 리턴되는 결과(실제로는 생성자를 통해 생성된 객체겠죠.)가 \_\_iter\_\_를 구현한 순회 가능한(Iterable) 값이 되게 하는 것이죠. (덤으로 이 객체는 발생자(Generator)를 돌려주게 됩니다.)그럼 이제 이 코드들을 실행하려면 어떻게 해야할까요? 그러려면 먼저 WSGI 규격에 맞게 어플리케이션을 실행시켜 줄 서버를 작성해야합니다. 하지만 다행히도 Python 2.5부터 제공되는 wsgiref.simple_server를 이용하면 간단히 테스트 해 볼 수 있습니다.(서버를 직접 작성하는 부분에 대해선 나중에 다루도록 하겠습니다.)from wsgiref.simple_server import make_serverhttpd = make_server('', 8000, app)httpd.serve_forever()view rawgistfile1.py hosted with ❤ by GitHub위 코드를 실행시킨 후에(당연히 app이나 App도 만들어져 있어야겠죠?) 웹 브라우져를 통해 localhost:8000으로 접속하면 작성한 어플리케이션의 동작을 확인할 수 있습니다.environ과 start_response테스트도 해봤으니 코드를 조금만 더 자세히 살펴봅시다. 함수 버젼의 app이나 클래스 버젼의 App모두 공통적으로 environ과 start\_response를 인자로 받아 요청을 처리하는 것을 확인할 수 있습니다. (당연한 이야기겠지만, 반드시 이름이 environ이나 start\_response일 필요는 없습니다만 편의상 이후 계속 environ과 start_response로 표기하겠습니다.)하나씩 살펴보자면, environ은 Python 딕셔너리(dictionary)로 HTTP 요청을 처리하는데 필요한 정보가 저장되어있습니다. HTTP 요청에 대한 정보는 물론, 운영체제(OS)나 WSGI 서버의 설정 등도 정의되어있지요. 다음 코드는 이러한 environ의 내용을 응답으로 주게끔 수정한 WSGI어플리케이션입니다.def dump_environ_app(environ, start_response):    response_body = "\n".join(["{0}: {1}".format(k, environ[k]) for k in environ.keys()])    status = '200 OK'    response_headers = [('Content-Type', 'text/plain'),                         ('Content-Length', str(len(response_body)))]    start_response(status, response_headers)        return [response_body]view rawgistfile1.py hosted with ❤ by GitHublocalhost:8000에 각종 쿼리 스트링(Query String)을 붙이거나, 브라우져를 바꿔가면서 확인해보면 출력되는 값이 바뀌는 것을 확인하실 수 있습니다.그럼 이제 start\_response를 한번 볼까요. start\_response는 일종의 콜백(Callback)으로 인터페이스는 다음과 같습니다. start_response(status, response_headers, exc_info=None) 실제 서버에서 어플리케이션으로부터 응답(Response)의 상태(Status)와 헤더(Header), 그리고 예외(Exception)의 유무를 확인받아 실행하게 되는데, status와 response_headers는 HTTP 응답 명세에 근거하여 작성하게 됩니다.Middleware지금까지 우리는 어떻게 WSGI 어플리케이션을 작성하는지에 대해 살펴봤습니다. 요청을 받아 처리하는 HTTP의 기본 기능에 충실한 어플리케이션이었죠. 그런데 일반적으로 우리가 작성하는 웹 어플리케이션에서 주로 다루게 되는 쿠키(Cookie), 세션(Session)에 대해서는 어떻게 처리해야 좋을까요? WSGI 명세에는 이러한 내용을 직접적으로 다루고 있지 않습니다. WSGI 자체는 서버나 프레임워크 자체가 아니라 서버가 어플리케이션과 통신하는 명세를 다루고 있기 때문이죠. 따라서 이러한 기능은 작성자가 직접 이를 구현해야 합니다. 그런데 이런 구현을 어플리케이션을 작성할때마다 하는건 너무 번거로운 일입니다. 그것보다는 이미 작성한 어플리케이션을 확장하는 것이 간단하겠지요. 이러한 확장을 위해 필요한 것이 WSGI 미들웨어(Middleware)입니다.미들웨어는 어플리케이션을 처리하기 전후의 처리나 environ의 추가등을 통해 작성된 어플리케이션을 확장할 수 있습니다. 다음은 쿼리 스트링의 \_\_method\_\_에 따라 HTTP 메소드(Method)를 임의로 변경하는 처리를 도와주는 간단한 미들웨어입니다.from werkzeug import url_decodeclass MethodRewriteMiddleware(object):    """        app = MethodRewriteMiddleware(app)    """    def __init__(self, app, input_name = '__method__'):        self.app = app        self.input_name = input_name    def __call__(self, environ, start_response):        if self.input_name in environ.get('QUERY_STRING', ''):            args = url_decode(environ['QUERY_STRING'])            method = args.get(self.input_name)            if method:                method = method.encode('ascii', 'replace')                environ['REQUEST_METHOD'] = method        return self.app(environ, start_response)view rawgistfile1.py hosted with ❤ by GitHubMethodRewriteMiddleware는 \_\_call\_\_를 통해 app을 대체하게 됩니다. (데코레이터(Decorator)가 생각나셨다면 정확한 이해십니다.)Flask미들웨어를 통해 어플리케이션을 확장하는 방법까지 알아봤습니다. 그러나 이것만 가지고 웹 어플리케이션을 만들기에는 아직 귀찮은 부분이 많이 남아있습니다. 각종 파라미터를 처리하기 위해서는 environ를 일일히 뒤져야하며, 요청에 대한 응답으로 전달할 HTML도 일일히 문자열로 적어야하죠. 이런 여러가지 불편함을 해결하기 위해 알아볼 것이 WSGI 마이크로프레임워크를 자처하는 Flask입니다. Flask는 WSGI 라이브러리인 Werkzeug를 만들기도 한 Armin Ronacher가 만든 프레임워크로 “마이크로”라는 수식어에 어울리게 아주 핵심적인 부분만을 구현하고 있지만, 유연하게 확장이 가능하게 설계된 것이 특징입니다.다시 한번 Hello World!우선 Flask를 시스템에 설치해야하는데, pip가 설치되어있다면 pip install flask로 설치 가능합니다.(환경에 따라 루트(root)권한이 필요할 수도 있습니다.) easy_install의 경우도 마찬가지로 easy\_install flask로 설치 가능합니다.설치가 완료되었으면 다음과 같이 아주 간단한 어플리케이션을 작성해봅시다.from flask import Flaskapp = Flask(__name__)@app.route("/")def hello():    return "Hello World!"if __name__ == "__main__":    app.run()view rawgistfile1.py hosted with ❤ by GitHubFlask(정확히는 Werkzeug)는 테스트를 위해 간단한 WSGI 서버를 자체 내장하고 있기 때문에 app.run을 통해 어플리케이션을 직접 실행할 수 있습니다.Route이번에 작성한 Flask 어플리케이션에는 이전까지 보지 못하던 개념이 들어 있습니다. app.route가 바로 그것인데요. 이 메서드는 URL 규칙을 받아 해당하는 규칙의 URL로 요청이 들어온 경우 등록한 함수를 실행하게끔 설정합니다. 위의 Hello World! 예제 같은 경우엔 “/”가 해당되겠지요. 또한 이런 규칙을 URL로 부터 변수도 넘겨 받을 수 있습니다.# http://flask.pocoo.org/docs/api/#[email protected]('/')def index():    [email protected]('/')def show_user(username):    [email protected]('/post/')def show_post(post_id):    passview rawgistfile1.py hosted with ❤ by GitHub이렇게 URL을 통해 처리할 핸들러를 찾는 것을 일반적으로 URL 라우팅(Routing)이라고 합니다. 이런 URL 라우팅에서 중요한 기능 중 하나가 핸들러에서 해당하는 URL을 생성하는 기능인데, Flask는 이를 url_for 메서드를 통해 지원합니다[email protected]('/')def index():    return ""@app.route('/')def show_user(username):    return [email protected]('/post/')def show_post(post_id):    return str(post_id)from flask import [email protected]("/routes")def routes():    return "".join([            url_for("index"),            url_for("show_user", username="longfin"),            url_for("show_post", post_id=3)            ])view rawgistfile1.py hosted with ❤ by GitHub보다 자세한 사항은 API 문서를 참고하실 수 있습니다.Template여태까지 우리는 요청에 대한 응답으로 단순한 문자열을 사용했습니다. 하지만 일반적인 웹 어플리케이션의 응답은 대부분이 그것보다 훨씬 복잡하지요. 이를 보다 쉽게 작성할 수 있게끔 도와주는 것이 바로 Flask의 템플릿(Template)입니다. Flask는 기본 템플릿 엔진으로 (역시 Armin Ronacher가 작성한)Jinja2를 사용합니다.기본적으로 템플릿엔진은 별도의 규칙(여기서는 Jinja2)에 맞게 작성된 템플릿 파일을 읽어 환경(Context)에 맞게 적용한 결과물을 돌려주는데 이 과정을 Flask에서는 render_template()가 담당하고 있습니다. 다음 코드는 hello.html이라는 템플릿 파일을 읽어서 이름을 적용한 뒤에 돌려주는 코드입니다.# from http://flask.pocoo.org/docs/quickstart/#rendering-templatesfrom flask import [email protected]('/hello/')@app.route('/hello/')def hello(name=None):    return render_template('hello.html', name=name)view rawgistfile1.py hosted with ❤ by GitHub쉽게 작성할 수 있게 도와준다고 해도, 템플릿 역시 나름의 학습을 필요로 합니다. 자세한 사항은 Jinja2의 API 문서를 참고하시기 바랍니다.RequestHTTP 요청을 다루기 위해서 때로는 environ의 내용은 너무 원시적일때가 있습니다. HTML 폼(Form)으로부터 입력받는 값이 좋은 예인데요. Flask에서는 request라는 객체(역시 Werkzeug에서 가져다가쓰는 거지만요)를 통해 이를 보다 다루기 쉽게 해줍니다. 다음은 HTML 폼으로부터 입력받은 message라는 값을 뒤집어서 출력하는 코드입니다.from flask import [email protected]("/reverse")def reverse():    message = request.values["message"]    return "".join(reversed(message))view rawgistfile1.py hosted with ❤ by GitHubSession로그인등으로 대표되는 요청간의 상태를 유지해야하는 처리에 흔히 세션(Session)을 사용하실 겁니다. Flask에서는 session객체를 지원합니다.# from http://flask.pocoo.org/docs/quickstart/#sessionsfrom flask import Flask, session, redirect, url_for, escape, requestapp = Flask(__name__)@app.route('/')def index():    if 'username' in session:        return 'Logged in as %s' % escape(session['username'])    return 'You are not logged in'@app.route('/login', methods=['GET', 'POST'])def login():    if request.method == 'POST':        session['username'] = request.form['username']        return redirect(url_for('index'))    return '''        <form action="" method="post">           <input type=text name=username>           <input type=submit value=Login>        </form>    '''@app.route('/logout')def logout():    # remove the username from the session if its there    session.pop('username', None)    return redirect(url_for('index'))# set the secret key.  keep this really secret:app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'view rawgistfile1.py hosted with ❤ by GitHubFlask는 기본적으로 시큐어 쿠키(Secure Cookie)를 통해 세션을 구현하므로 길이에 제한이 있습니다. 때문에 파일이나 DB기반의 세션을 구현하려면 Beaker와 같은 프레임워크를 통한 확장이 필요합니다.(하지만 이 또한 매우 쉽습니다.)#스포카 #개발 #개발자 #개발팀 #인사이트 #기술스택 #꿀팁 #Flask
조회수 3221

야놀자 앱은 왜 자동실행 되나요?

pluu 04 JUL 2018저는 야놀자 CX서비스실의 Android 파트에서 레이아웃 깎기와 Kotlin과 새로운 Android 기술을 전파하는 노현석입니다. 야놀자에 합류하고서 경험한 가장 독특한 케이스에 대해서 이야기해 보려고 합니다.시작은 물음표부터언제부터인가 야놀자앱을 설치하거나 업데이트하면 앱이 자동으로 실행된다는 리뷰가 들어오기 시작했습니다.네?! 그게 무슨 말이에요?안드로이드 개발을 시작한 이래로 처음 들어보는 내용이라, 원인도 정확한 해결책도 떠오르지 않는 그런 리뷰였습니다. 그래서 자연스럽게 브라우저를 켜서 구글에 검색을 먼저 해봤습니다. Android, Auto Start, Install 등 다양한 검색 결과로 일정한 패턴의 내용을 확인할 수 있습니다.  Intent Action 관련 내용android.intent.action.PACKAGE_ADDEDandroid.intent.action.PACKAGE_CHANGEDetc.Broadcast Receiveretc.일반적으로 안드로이드 앱이 설치 및 업데이트될 때 발생하는 이벤트(이하 Broadcast)를 받는 방법에 대한 설명이 많습니다. Broadcast는 배터리 변화, 전화 여부, 와이파이 등 시스템의 상태 변화를 감지하거나 서비스 내부적에서 이벤트를 전달하기 위해 사용합니다. ???? 실질적인 해결책은 되지 않지만, 범위를 좁혀서 찾아볼 포인트로 Intent 의PACKAGE관련 액션을 포커스로 잡았습니다. 하지만, 야놀자앱에서는 마케팅 성과 측정을 위해com.android.vending.INSTALL_REFERRER를 광고 트래킹 SDK에서 사용하는 것 이외에는 별도의 작업을 하지는 않습니다. 그러나, 이를 알 리가 없는 사용자는야놀자 앱이 일으키는 문제라고 인지하기 쉽습니다.  일차적으로, 어느 경로를 통해서인지는 모르지만 누군가가 야놀자 앱을 실행하는 것이라고 생각했습니다.야놀자 앱 사용자의 기기에 설치된 모든 앱 리스트를 받아올 수도 있고, 리퍼럴에 따른 앱 실행경로를 모두 수집할 수도 있지만, 단순히 버그를 찾기 위해 사용자의 동의 없이 정보를 수집할 수는 없기 때문에 장기전으로 돌입하게 되었습니다. 하지만 동일한 리뷰는 계속되었고 여전히 뚜렷한 해결책이 없는 채로 시간이 흘러갔습니다.  저 재현되는데요증상이 나타나지만 재현은 되지 않고, 재현 경로를 단기간에 파악하기는 어려운 과제였습니다. 한두 명에 불과하던 제보가 시간이 지날수록 Android 파트의 목을 조르듯이 점점 유입되는 횟수가 늘어만 갔습니다. 그런데 어느 날, 다른 팀의 분께서저 재현되는데요라는 한 줄기의 빛과 같은 언급을 해주셨습니다.믿고 싶지 않은 일이 현실이 되었다네? 그게 … 정말로 일어났습니다.이제부터가진짜시작역시버그는재현이되어야제대로잡을수있겠죠! 저에게는재현되는 단말이 있어요!Android에서 디버깅을 할 수 있는 다양한 수단이 있습니다. 이번 사례의 경우는Log혹은Dump를 확인해보는 선택지가 있습니다.Log민감한 정보라고 판단되는 부분은 모자이크했습니다.앱 설치 후 광고 SDK가 수집하는 것으로 보이는 Log에는 다양한 항목들이 나열되는 것을 볼 수 있습니다. 이때 설치한 앱의 정보가 SDK를 통해 특정 API로 전송되는 것도 확인할 수 있습니다. 하지만 Log는 Log일 뿐입니다.  Dumpsys이렇게 Log만으로 추적이 어려울 때, 추가적으로 시스템의 상태를 얻어내 디버깅 할 수 있는 방법이 있는데 바로dumpsys입니다. dumpsys는 Android 단말에서 실행되며 시스템 서비스에 대한 다양한 정보를 제공하는 도구입니다. ADB(Android Debug Bridge)를 사용하여 dumpsys를 호출 시 해당 단말에서 실행 중인 모든 시스템 서비스에 대한 정보를 가져올 수 있습니다. 간단하게 말하면 배터리의 잔량, 메모리 소비량, 네트워크 통신 상태 등을 명령어로 확인할 수 있습니다. dumpsys의 기능에 대해서는 방대한 설명이 필요하므로, 자세한 내용은 아래 링크로 대체합니다.  Android Developers ~ dumpsyshttps://android.googlesource.com/platform/frameworks/native/+/master/cmds/dumpsys/dumpsys.cppActivity DumpDumpsys 에서 좀 더 Activity 와 관련된 정보를 얻기 위해서는 아래의 명령어를 적용해볼 수 있습니다.// Activity Log Dump adb shell dumpsys activity activities 결과를 확인해봅니다. 아래와 같은 Activity 의 활동 이력을 얻을 수 있습니다.Activity Dump에 나타난mCallingPackage값으로 야놀자 앱을 시작시킨 앱의 패키지를 확인할 수 있습니다. 해당 패키지를 실제 Play Store에서 확인해본 결과, 사진 보정 필터앱으로 유명한카메라 앱중 하나였습니다.???? 야놀자와는 전혀 연관성이 없는 앱인데, 호출하고 있네요… ????Process ID// 애플리케이션의 Process ID 취득 adb shell ps Activity Dump에서 확인한mCallingUid는u0a423였는데, 이는 Activity를 호출한 uid 값을 가리킵니다. 실제로 Process 가 호출되는 Application ID도 카메라 앱에서 호출한 ID 정보와 일치합니다.대상 앱 자료 분석단순하게는 APK 를 분석하여 추측하는 방법이 있습니다. Android Studio 에서 제공되는Analyze APK기능을 이용하여 해당 앱에서 사용되는 서비스의 정보를 파악할 수 있습니다. 이 방법을 이용하여 문제의 앱이 사용하는 광고 SDK 서비스에서 패키지 설치/제거 관련 Broadcast Receiver를 수집하는 것을 확인 할 수 있습니다.패키지 관련 Broadcast인android.intent.action.PACKAGE_ADDED, android.intent.action.PACKAGE_REMOVED를 앱이 사용하는 것은 잘못된 것이 아닙니다. 예를 들어 런처 앱의 경우 단말기 내부의 앱 정보가 변경되었다는 이벤트를 이용하여 화면 렌더링 및 동작을 변경하는 처리를 할 수 있습니다 해당 광고 SDK의 경우에는 앱을 설치 및 실행하는 것으로 사용자에게 포인트 및 여러 혜택을 제공할 것이라고 예상할 수 있습니다.개인적인 의견으로는 사용자의 액션과 상관없이 동작하는 부분에 대해서는 분명히 Android 의 개선도 필요하다고 생각됩니다. 이런 정상 동작과 어뷰징은 아슬아슬한 경계에 있지만, 자칫 어뷰징으로 이어지는 경우 서비스의 품질이 떨어지게 되면서 사용자와 개발사 모두에게 좋지 않은 경험을 줄 뿐입니다.설마 이것도 되려나?동일 패키지명이번 포스팅을 작성하게 된 카메라 앱과 야놀자 서비스 사이에 특별한 관계가 없다면, 왜 이런 현상이 발생하는지 고민해봤습니다. SDK도 연결하지 않았다면, 앱을 추적할 수 있는 유일한 키는패키지명이지 않을까라는 생각으로 패키지명만 야놀자 앱과 동일한 샘플 앱으로 테스트해봤습니다.동일 재현 성공!!그럼… 해결… 끝?많은 사람들에게 이름이 널리 알려진 여러 서비스에서조차 이번 포스팅에서 다룬 내용과 같은 현상이 발생하고 있습니다. 발생 유무에 따른 차이점이나 현상의 인과 관계를 명확히 판단하기엔 아직 정보가 많이 부족합니다. 그리고 이번 분석에서 발견한 문제의 앱을 비롯하여 또 다른 제2, 제3의 앱들이 등장할 거란 가능성도 배제할 수 없는것이 현재 상황입니다. 슬프게도 아직 이 현상은 지금도 계속되고 있으며, 불편을 호소하는 리뷰가 등록되어 서비스 전체의 이미지와 평점을 갉아먹고 있습니다. 안드로이드 생태계가 사용자 및 서비스 제공자에게 더 유익한 방향으로 나아갔으면 하는 바람을 담아 작성했습니다.도움 주신 분동일 증상을 발견하고, 단말을 빌려주신 R&D SF팀 전호숙님같이 추적해주신 R&D CX 서비스실 유관종님Dump/Log 관련 조언을 주신 Wind River의 차영호님 (????????????)국어가 많이 부족한 저를 도와주신 리뷰어 ???????????? R&D CX 서비스실 강미경님, 송요창님, 유관종님, 유용우님, 이미혜님이번 현상 추적에 도움을 주신 분들에게 감사함을 전합니다.#야놀자 #개발자 #개발팀 #문제해결 #버그수정 #안드로이드 #인사이트 #경험공유
조회수 900

2017 NDC 리뷰) 몬스터 슈퍼리그 리텐션 프로젝트

 2017년 4월 25일부터 27일까지 진행된 Nexon Developer Conference 에 나녀온 후기입니다.제가 들었던 재밌는 세션들 하나하나 올릴 테니 기대해 주세요! :)2017 NDC 재밌었다능!!!몬스터 슈퍼리그 리텐션 15% 개선 리포트 - 숫자보다 매력적인 감성 테라피"몬스터 슈퍼리그"의 게임 리텐션 개선 리포트였는데요, 기본적인 서비스의 소비자를 향한 어프로치인"당신은 똑똑한 유저!"라는 인식 심기(쉬운 접근성/ 심도 깊은 진행 유도)"빠른 어필"(이벤트에 대한 빠른 피드백)"축복받은 계정" (다양하고 많은 초반 보상)이라는 인식과,"주어지면 알아서 하겠지""보상이 있으면 알아서 하겠지"라는 생각을 지양하고, "의도하지 않았지만 수행을 할 수 있도록" 하는 넛지(Nudge) 효과를 일으킬 수 있는 무의식을 자극하는 재밌는 전략에 대해서 흥미를 느꼈습니다. 그리고 이후 리텐션 강화를 위한 프로젝트로 가장 중요한 건,단지 "무슨 기능을 만들 것이냐?"가 아닌, "어떤 부분에서 유저가 이탈"하게 되고,이탈한 유저들 중 "우리가 진짜 챙겨야 할 유저"가 어떤 유저들인가에 집중한다.라는 부분에서 항상 우선돼야 하지만, 되지 못한 부분들을 생각하게 되었던 것 같아요. 그래서 이를 통해스스로 모험 입장을 몇 번 한 유저: 원하는 것에 접근하지 못한 유저에 대한 파악 후 개선다음 지역에 접근 한 유저: 지속 플레이 의향 있으나, 니치를 못 찾은 유저들의 의도 파악 후 개선이라는 개선에 필요한 정확한 목표를 가지고, 어떤 방식으로 접근해야 하는지에 대한 고민을 보는 것에 정말 재미를 느꼈고요, 이에 대한 진행방향을 듣는 것도 정말 값진 경험이었습니다.개선 시퀀스1차 개선 (-)보상 10배 상향에도 불구, 큰 성장 없음.>"가치비교가 익숙하지 않은 유저들에게 보상의 절대적 수치 증가는 큰 감흥이 없다."라는 점을 파악하고, 유저에 "감정"을 터치하는 방법을 고안.2차 개선 (+ & -)조사 결과, 첫 패배 지점에서 유저들의 높은 이탈률을 파악> 패배 지점을 인위적으로 미루지 않되, 패배에 신경 쓰지 않도록 다른 부분들에 대한 장치를 추가.> "도전 가능한 포인트를 생성하는 것은 유효하다."는 부분을 Metric으로 확인했으나, 타깃 유저 범위를 정확하게 파악하지 않고  Metric만 집중해서 정확한 범위 파악을 놓침.3차 개선 (+ & +)유저가 얻을 수 있는 보상의 기회를 꼭 찾아가도록 유도옵션 1. 텍스트 강조? 텍스트는 망각의 영역 (X)옵션 2. 강제 터치? 이미 자유 플레이가 된 유저에게 부자연스러운 접근 (X)옵션 3. 얻고 싶은 보상이 있다면, 어떨까? 그리고 보상 등에 대해서 스토리 텔링이 될 수 있다면?  (O)  - 부정 경험 개선을 통한 리텐션 향상 효과  - 초반에 한 일을 다시 하게 하는 것은 큰 부정 경험을 초래  - 강제적 이동보다는 원하는 보상을 통해 부여4차 개선 (+)스토리 텔링 요소 추가  1. 일러스트  2. 캐릭터에 대한 스토리 추가글로벌 원빌드로서 북미권 영역에서  특히 추가결과개선 프로세스 이후,"무언가가 무조건 있다."라는 이야기 보단, "기대치 않은 행동에 대해서 얻는 보상의 획득"으로 유저의 감성을 자극하는 스토리 텔링의 중요성 확인.교훈보상도 주지만, "보상을 준다"라는 이벤트를 행하는 것 만으로 서비스 제공자는 끝내선 안된다. 보다 감성적인 접근을 통해 유저의 감정을 이해하는 것이 더 중요하다. 첫날 첫 번째 세션이었는데요, 아침부터 정말 보람찬 세션 들을 수 있어서 정말로 감사했습니다. 사실, 게임이건, 모바일 서비스건, 웹 서비스건 "소비자를 이해한다."라는 부분은 언제 어디서나 필요한 부분이지만, 결과적으로 서비스 제공자들은 "보상을 제공했다."로 서비스 제공을 스스로 끝을 내버리는 순간들을 더 많이 마주하기 때문에, 다양한 분야들에서 생각해 볼만한 이야기라고 생각합니다. 한줄평: 중요한 건, "내가 이런 걸 줬다!"보다는 "이런 걸 줘서 고마워"라는 것을 느낄 수 있도록 소비자의 마음에 초점을 맞추는 서비스 제공이 맞다!#코인원 #블록체인 #기술기업 #암호화폐 #스타트업인사이트
조회수 1829

프론트엔드 개발자라면!

Angular의 A마크를 알아본 프론트엔드 개발자님!이 글은 새로운 플랫폼을 개발하고 있는 타운컴퍼니 개발팀으로 당신을 모셔보려는 글이에요.이들이 당신과 함께 일하고 싶은 동료입니다..타운컴퍼니팀은 알고 있습니다.잘 만들어진 편리한 앱과 고객의 이탈률이 얼마나 밀접한 관계가 있는가를요.1%의 스타트업에는 1% 개발자가 필요하며 그들이 1%의 플랫폼을 만든다는 것을요.자율적이며 열정이 넘치는 팀으로 즐겁게 높은 수준의 개발을 할 수 있는 환경이죠!당신에게 즐거운 회사, 좋은 동료가 되어 줄 수 있습니다.급여는 협의 후 결정이니 원하는걸 말해봐요!좋은 동료를 얻을 수 있다면 그정도가 어렵겠어요 ; )우리는 현재 플랫폼(townus.co.kr)이 많이 부족하다는걸 알고 있어요.그래서 완전 멋있게 새롭게 만들고 있는 중입니다 :)일단 우리는 협업툴 JIRA와 Confluence, Slack을 사용하고 있어요.우리팀은 Agile 칸반을 바탕으로 테스트 주도 개발, 코드 리뷰, 페어프로그래밍으로 프로젝트를 진행하고 있죠.도메인이 잘 분석된 명세서가 Confluence에 정리되어 있고 사용자를 위한 깊은 고민이 녹아있는 디자인이 Zeplin에 올라가고 있어요.- Back-End는 Django(DRF) 기반으로 개발되고 있고, AWS, Vagrant, Docker같은 기술을 사용해요.- Front-End는 Angular 5를 사용해서 개발하고 있고, Less, RxJS, Webpack 등의 기술을 사용하고 있어요.Angular 상용 프로젝트 개발 경험이 있다면 격하게 환영하며 모십니다. 리엑티브 프로그래밍, Ionic 경험이 있다면 더 좋구요!엥 이거 완전 나 아니냐!? 라는 생각이 들었다구요? 그렇다면 얼른 지원해야지 뭐해요!무엇보다 개발을 즐기고 오픈소스활동을 좋아하는 사람이라면,종종 맛있는것도 먹으면서 많은 대화를 나눌수 있지 않을까요? :>우리 개발자는 맥주제조도 할 줄 알거든요 (겁나 맛이 좋더라구요)물론 당신에 대해 알 수 있는 Github 주소와 이력서를 보내준다면 우리가 연락하기 더 쉬울거에요!아! 참고로 보충역 산업기능요원 편입도 가능하니 문의가 필요하다면 언제든 환영이에요!타운컴퍼니팀에게 연락하고 싶다면 02–561–0950 잊지말아요,[email protected]로 메일 보내준다면 언제든 답변줄게요 :D#타운컴퍼니 #개발자 #채용 #팀빌딩 #조직문화
조회수 22563

Next.js 튜토리얼 9편: 배포하기

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기 2편: 페이지 이동 3편: 공유 컴포넌트4편: 동적 페이지5편: 라우트 마스킹6편: 서버 사이드7편: 데이터 가져오기8편: 컴포넌트 스타일링9편: 배포하기 - 현재 글개요아래와 같은 궁금증이 생긴 적이 있나요?어떻게 내가 만든 Next.js 애플리케이션을 배포할 수 있나요?아직 배포에 대해 이야기하지 않았지만 배포하는 것은 꽤 간단하고 직관적입니다.Node.js를 동작할 수 있는 곳이라면 어디든 Next.js 애플리케이션을 배포할 수 있습니다. 매우 간단한 ▲ZEIT now로 배포하는 데에도 불구하고 어떤 잠금 장치도 없습니다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.Build와 Start처음으로 프로덕션에 우리의 Next.js 애플리케이션을 빌드해야 합니다. 빌드는 최적화된 프로덕션의 코드 세트를 생산합니다.이를 위해 간단히 다음의 npm 스크립트를 추가하세요:그런 다음 하나의 포트에서 Next.js를 시작해야 합니다. 사이드 렌더링을 수행하고 페이지를 제공합니다. (위의 명령으로 빌드됩니다)이를 위해 다음의 npm 스크립트를 추가하세요:이러면 3000 포트에서 우리의 애플리케이션이 시작됩니다.이제 프로덕션에서 애플리케이션을 동작시키 위해 다음의 명령어를 실행할 수 있습니다:두 개의 인스턴스 실행하기애플리케이션의 인스턴스 두 개를 실행시켜 봅시다. 대부분 앱을 수평으로 확장하기 위해 이 작업을 수행합니다. 처음으로 start npm 스크립트를 다음과 같이 변경해봅시다:만약 Winodws라면 next start -p %PORT%로 스크립트를 변경해야 합니다.이제 애플리케이션을 처음으로 빌드해봅시다.npm run build그러면 터미널에서 다음의 명령어로 실행시켜 봅시다:PORT=8000 npm startPORT=9000 npm startWinodws에서는 다른 명령어를 실행시켜야 합니다. 하나의 옵션은 애플리케이션에 cross-env npm 모듈을 설치하는 것입니다.그런 다음 커맨드 라인에서 cross-env PORT=9000 npm start를 동작시켜 주세요.두 개의 포트 모두에서 애플리케이션에 접근할 수 있나요?- 네. http://localhost:8000와 http://localhost:9000 둘 다 접근할 수 있습니다.- http://localhost:8000에서만 접근 가능합니다.- http://localhost:9000에서만 접근 가능합니다.- 둘 다 접근할 수 없습니다.한 번의 빌드로 많은 인스턴스 실행시키기보다시피 애플리케이션을 한 번 빌드해야 합니다. 그런 다음 원하는만큼의 많은 포트들을 시작할 수 있습니다.▲ZEIT now에 배포하기Next.js 애플리케이션을 빌드하고 시작하는 방법을 배웠습니다. npm 스크립트를 사용하여 모든 것을 수행했습니다. 그래서 원하는 배포 서비스를 사용해서 동작하도록 애플리케이션을 설정할 수 있습니다.하지만 ▲ZEIT now를 사용하면 딱 한 번의 과정만 수행하면 됩니다.다음과 같은 npm 스크립트만 추가해주세요:그런 다음 now를 설치해주세요. 설치 후 다음 명령어를 적용해주세요:now기본적으로 애플리케이션의 루트 디렉터리 안에서 "now" 명령어를 실행합니다.여기에서 애플리케이션을 시작하는 포트로 8000 포트를 지정했지만 ZEIT now에 배포할 때 변경하지 않았습니다.그러면 ZEIT now에 배포할 때 애플리케이션에 접근할 수 있는 포트는 어떤 것일까요?- 8000- 443 (혹은 언급되는 포트가 없음)- URL에 언급한 모든 포트- 에러를 표시한다. "443 포트에서만 시작할 수 있습니다"ZEIT는 항상 443 포트를 사용합니다실제로 8000 포트에서 애플리케이션을 시작해도 now에 배포될 때는 443 포트를 사용해서 접근할 수 있습니다. ("https" 웹사이트의 기본 포트)이것은 ▲ZEIT now의 특징입니다. 원하는 포트에서 애플리케이션을 시작해야 합니다. ▲ZEIT now는 항상 443 포트로 매핑합니다.로컬에서 애플리케이션 빌드하기▲ZEIT now는 npm build 스크립트를 발견하고 빌드 인프라 내부에 빌드합니다.하지만 모든 호스팅 제공자가 이와 같은 것을 가지고 있지는 않습니다.이 경우 로컬에서 다음의 명령어를 사용해서 빌드할 수 있습니다:npm run build그런 다음 .next 디렉터리를 사용하여 애플리케이션을 배포하세요.커스텀 서버를 사용하여 애플리케이션 배포하기우리가 막 배포한 애플리케이션은 커스텀 서버 코드를 사용하지 않았습니다. 하지만 만약 사용한 경우에는 어떻게 배포할 수 있을까요?다음의 브랜치로 체크아웃하세요:커스텀 서버를 사용하여 애플리케이션을 실행하기 위해 애플리케이션에 Express를 추가해주세요:npm install --save express애플리케이션 빌드하기이를 위해 next build를 사용하여 애플리케이션을 배포할 수 있습니다. 다음의 npm 스크립트를 추가해주세요:애플리케이션 시작하기프로덕션 애플리케이션임을 알리기 위해 커스텀 서버 코드를 생성해야 합니다.이를 위해 server.js로부터 이 코드를 살펴봅시다.이 부분을 살펴봅시다:그러면 프로덕션으로 이와 같이 애플리케이션을 시작할 수 있습니다.그래서 "npm start" 스크립트는 다음처럼 변경됩니다:마무리Next.js 애플리케이션을 배포하는 것에 대해 거의 다 배웠습니다.문서에서 Next.js 배포하기에 대해 더 배울 수 있습니다.배포에 대한 질문이 있다면 자유롭게 Slack에서 물어보거나 issue를 제출하세요.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 1511

레진 기술 블로그 - SVG를 이용해 간단한 웹 게임 만들어보기

근래 소규모로 게임 프로그래밍 스터디를 시작했습니다. 서비스 UI를 개발하는 프론트엔드개발자에게 있어 게임 프로그래밍은 언제나 커튼 뒤에 비친 풍경처럼 흐릿하고 형체를 쉽게 알 수 없는 신비한 존재입니다. 이번에 미약하게나마 커튼을 걷어 창문 너머 펼쳐진 풍경을 감상해 보자는 게 이번 스터디의 개인적인 목표입니다.왜 SVG를 선택했나게임을 만드는 데 어떤 기술을 사용할지 고민했습니다. 일반적인 DOM은 쉽게 객체를 조작할 수 있지만, 문서의 엘리먼트를 추상화한 것에 불과하므로 다양한 도형을 만들거나 좌표계에 사상(寫像, Mapping)1하기 쉽지 않습니다.캔버스는 그래픽 처리에 환상적인 성능을 보여주고 원, 다각형 등 다양한 도형을 그리기 쉽지만 일일이 객체화해야 하고 이를 관리하기 쉽지 않습니다. 여기에 필자가 캔버스를 좀 처럼 써 본 경험이 없어서 무턱대고 사용하기에도 부담을 느꼈습니다.하지만 SVG는 이 두 장점을 모두 갖고 있습니다. 확장 가능한 벡터 그래픽(Scalable Vector Graphics)이라는 이름을 통해서 알 수 있듯이 그래픽 요소를 그리는데 적합한 포멧이며 DOM처럼 추상화된 객체도 지원합니다.어떤 게임을 만들었나필자가 만든 게임은 크롬에 내장된 Running T-Rex와 비슷한 것으로 JUMPING CAR라고 이름을 붙였습니다. 플레이해보고 싶은 분은 uyeong.github.io/jumping-car를 방문하시기 바랍니다.규칙은 단순합니다. 게임을 시작하면 자동차가 달려나가고 이윽고 장애물을 만나게 됩니다. 장애물을 뛰어넘으면 점수가 1씩 증가하지만 부딪히면 게임이 종료됩니다.이 글에서는 게임을 만드는 과정을 소개하기보다 SVG를 이용하면서 알게 된 몇 가지 주요한 내용을 다룹니다.Pattern을 사용한 요소는 느리다이미지를 반복해서 출력할 때 HTML에서는 CSS의 background-url 속성으로 간단히 해결할 수 있습니다. 하지만 SVG에서는 Pattern 요소를 이용해야 합니다.아래 그림처럼 pattern#pat-land 요소를 만들고 이를 rect.parallax에서 사용하여 그림을 반복 출력되도록 합니다. 그리고 rect.parallax를 조금씩 Transform 하여 앞으로 이동하도록 구현합니다.코드는 다음과 같습니다(예제: svg-parallax-test/parallax1).<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="..."> <defs> <pattern id="pat-land" x="0" y="0" width="..." height="100%" patternUnits="userSpaceOnUse"> <image x="0" y="0" xlink:href="../images/land.png" width="..." height="100%"></image> </pattern> </defs> <g> <rect class="parallax" x="0" y="0" width="..." height="100%" fill="url(#pat-land)" transform="translate(0,0)"></rect> </g> </svg> 표면상으론 전혀 문제가 없는 코드지만 크롬 브라우저에서 이 코드를 실행하면 프레임이 50 이하로 떨어지는 경우도 발생합니다. 이 정도면 육안으로도 화면의 움직임이 매끄럽지 않게 느껴지는 수치입니다.따라서 성능에 영향을 주는 pattern을 제거하고 image 요소로 대체합니다. image 요소는 자동으로 반복할 수 없으므로 두 개의 요소를 이어 붙여 사용합니다(예제: svg-parallax-test/parallax2).<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="..."> <g> <image x="0" y="0" xlink:href="../images/land.png" width="..." height="100%"></image> <image x="..." y="0" xlink:href="../images/land.png" width="..." height="100%"></image> </g> </svg> 실행 결과 프레임이 안정적이고 육안으로도 이질감을 느낄 수 없습니다. 이처럼 Pattern을 이용한 SVG 요소를 애니메이션 처리할 때에는 주의가 필요합니다.일부 안드로이드 기종에서의 성능 문제pattern을 제거하고 image로 대체하면서 Parallax 처리 시 발생한 문제를 해결할 수 있습니다. 하지만 image로 대체하더라도 일부 안드로이드 기종에서는 여전히 성능 문제가 발생합니다.아래 영상처럼 image 요소를 Transform 할 경우 프레임이 급격하게 떨어집니다. 이는 크롬 개발자 도구에서도 쉽게 발견하기 힘든데 CPU 성능을 10배 줄여 테스트해도 수치상으로는 크게 차이 나지 않기 때문입니다.<style>.video-container { position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden; } .video-container iframe, .video-container object, .video-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style><iframe width="560" height="315" src="https://www.youtube.com/embed/F_-zXf1jb8I?rel=0" frameborder="0" allowfullscreen="">이 처리를 DOM으로 바꿔보면 어떻게 될까. 놀랍게도 큰 차이를 보여줍니다(예제: svg-parallax-test/parallax3).<iframe width="560" height="315" src="https://www.youtube.com/embed/VXQ1aT79D2s?rel=0" frameborder="0" allowfullscreen="">SVG에 대한 최적화 상황은 브라우저마다 조금씩 다릅니다. DOM은 과거부터 최적화 노력이 많이 이뤄졌지만, SVG는 pattern 요소나 다음 절에서 이야기할 리페인팅 문제 등 성능 문제를 일으키는 부분이 아직 남아있습니다.따라서 충돌 계산처럼 특별히 좌표계 연산이 필요 없는 배경은 DOM으로 옮기고 자동차, 장애물만 SVG로 구현했습니다(예제: svg-parallax-test/parallax4).SVG는 항상 페인트를 발생시킨다SVG는 이상하게도 svg 요소의 크기를 고정하더라도 자식 요소를 변경하면 페인팅이 발생합니다. 아래는 svg 요소의 자식 요소인 rect의 좌표를 수정하는 예제 코드입니다.<svg"http://www.w3.org/2000/svg" width="500px" height="500px" viewBox="0 0 500 500"> width="500" height="500" x="0" y="0"> </svg> [removed] setTimeout(() => { rect.setAttribute('x', '100'); }, 3000); [removed] svg는 viewBox로 설정한 사이즈 만큼 내부에 그림을 그립니다. 즉, 내부의 어떠한 그래픽적 변화가 문서에 변화를 일으킬 가능성이 없습니다. 그래서 개인적으로 쉽게 이해가 되지 않는 렌더링 흐름입니다.그러면 SVG 요소의 크기나 좌표를 바꾸지 않고 색상 또는 투명도를 변경하면 어떨까요. 이번에는 rect 요소의 좌표가 아니라 색상을 바꿔봅니다.<svg"http://www.w3.org/2000/svg" width="500px" height="500px" viewBox="0 0 500 500"> width="500" height="500" x="0" y="0"> </svg> setTimeout(() => { rect.setAttribute('fill', '#ebebeb'); }, 3000); 그래도 페인트가 발생합니다. 하지만 앞서 진행한 테스트의 페인팅 시간은 수십 마이크로세컨드로 크게 의미가 없어 보입니다. 그래서 현재 서비스 중인 레진코믹스의 메인페이지에 SVG를 넣고 테스트했습니다.페인팅에 0.51ms가 소요됐습니다. 작다고 느낄 수 있지만 페이지 전반적으로 영향을 줄 수 있으며, 애니메이션 처리 중인 SVG라면 성능적 문제를 발생시킬 수 있는 부분입니다.그래서 svg 요소에 null transforms 핵을 선언해 문서 상위 레벨까지 페인팅이 전파되지 않도록 합니다.<svg"http://www.w3.org/2000/svg" width="500px" height="500px" viewBox="0 0 500 500" style="transform:translate3d(0,0,0)"> width="500" height="500" x="0" y="0"> </svg> 또는 아예 svg 내부의 요소를 개별로 분리하는 방법도 있습니다(참고: Doubling SVG FPS Rates at Khan Academy).<svg> fill="red" transform="translate(2px, 3px)"> fill="blue" transform="scale(2)"> </svg> style="transform:translate(2px, 3px)"> <svg> fill="red"> </svg> style="transform:scale(2)"> <svg> fill="blue"> </svg> 끝으로여기까지 SVG를 이용해 게임을 개발하면서 만나게 된 이슈와 해결 방법을 간단히 정리했습니다.필자는 간단한 게임은 SVG로 만들 수 있고 괜찮은 성능을 보장할 것이라고 기대했습니다. 하지만 현실은 달랐습니다. 이 글에서 다룬 문제 외에도 사파리와 크롬 브라우저의 성능 차이, 자동차를 움직일 때 버벅이는 현상 등 다양한 문제를 해결해야 했습니다. 객체의 개수도 적고 애니메이션도 복잡하지 않은 단순한 게임이었는데 말이죠.다음 게임은 캔버스로 시작하고자 합니다.공간(空間)의 한 점에 대(對)하여, 다른 공간(空間) 또는 동일(同一)한 공간(空間)의 한 점(點)을 어떤 일정(一定)한 법칙(法則)에 의(依)하여 대응(對應)시키는 일 ↩
조회수 4722

Elasticsearch로 느린 쿼리 분석하기

응당 인덱스가 있으리라 생각한 칼럼에 인덱스가 없고 인덱스를 걸자마자 응답속도가 평균 10배 가까이 좋아지는 모습을 지켜보니 여러 생각이 들더라. 통계와 지표가 제공되는 곳은 주기적으로 검토하고 문제가 커지기 전에 손을 쓰는데 그렇지 않은 곳이 문제이다. 주기적으로 Slow query 로그를 훑어볼 수는 있다. 하지만 특정 시점에 일부 로그만 훑어봐서는 엉뚱한 문제를 해결하기 일쑤다. 예를 들어 1초짜리 쿼리보다 10초짜리 쿼리가 문제라고 생각하기 쉽지만 이 1초짜리 쿼리를 10초짜리 쿼리보다 1000배 많이 실행한다면 이야기가 달라진다. 요는 느린 쿼리를 지속적으로 수집하고 통계를 낼 필요가 있다는 것이다.이러한 모니터링 도구를 어떻게 구현할까? 우리 손에 있는 도구를 검토하는 일부터 시작했다.통계분석은 MySQL 또는 Elasticsearch 를 쓰면 된다.Elasticsearch를 쓴다면 Kibana를 이용해 시각화하기 편하다.느린 쿼리 로그를 Elasticsearch에 보내는 일은 Fluentd를 쓰면 된다.그러니까 Fluentd, Elasticsearch, Kibana 조합이라면 데이터를 눈으로 보고 문제를 해결하기 좋을 것이다. 그렇다면 어떻게 구현할 것인가?우선 RDS에서 느린 쿼리를 뽑아서 Fluentd에 보내는 방법을 찾아야 한다.Fluentd를 이용해 Elasticsearch에 데이터를 보내는 건 쉬우니 대시보드만 잘 구성하면 끝!문제는 RDS에서 느린 쿼리를 뽑아서 Fluentd에 보내는 것인데 크게 두 가지 방법이 있다. RDS 설정에 따라 느린 쿼리 로그를 테이블 또는 파일에 저장할 수 있는데 이에 따라 구체적인 구현방법이 달라진다. 하지만 기본적으로는 동일한 과정을 거치는데 대충 이런 식이다.느린 쿼리 로그를 읽는다.같은 쿼리라도 매개변수 값이 다를 수 있으므로 mysql_slow_log_parser 또는 pt-query-digest 같은 도구를 사용해 쿼리를 일반화한다.Fluentd를 통해 해당 로그를 ES로 보낸다.새로 추가된 로그만 읽어서 다시 ES로 보낸다.이와 관련해서는 AWS RDS Mysql SlowQuery monitoring on Kibana using Logstash 등의 글이 잘 설명한다.다행히 테이블에 저장한 로그를 읽어들이는 Fluentd 플러그인을 구하기는 쉽다. 변형체가 많은데 대부분은 kenjiskywalker/fluent-plugin-rds-slowlog에서 파생됐다. 파일에 저장한 로그의 경우는 in_rds_mysqlslowlog_stream.rb를 써서 처리하면 된다. 우리는 테이블에 저장하기 때문에 전자를 선택했다.이쯤 조사를 마치고 나니 진행방향은 매우 명확하다. 적당히 잘 만든 Fluentd 플러그인을 골라서 적용한 후에 ES에 대시보드를 만들면 된다. 물론 우리는 Kubernetes 위에 모니터링 도구를 띄워야 하니 Dockerize할 필요도 있다. 이쯤에서 또다시 구글링을 하니 무시무시한 게 나온다. inokappa/rds-slowquery-log-demo는 방금 설명한 모든 과정을 하나로 정리해서 제공한다. Docker로 만든 Fluentd와 ES 대시보드 설정을 한데 묶어놓았다. 거기에 파일 로그, 테이블 로그 둘 다 예제로 제공한다. 덕분에 일이 쉽게 끝날 줄 알았다. 하지만!개발한지 꽤 시간이 지난 지라 최신 버전의 Fluentd와 ES에서 계속 문제를 일으켰다. 문제점에 대해 구구절절 설명할 생각은 없고 DailyHotel/rds-slowquery-log-demo를 참고해서 적용하면 된다는 점만 이야기하고자 한다. 일어로 된 README 파일은 구글 번역기를 돌리면 적당히 읽을만해진다.삽질을 약간만 하면 아래와 같이 간지!나는 대시보드를 얻을 수 있으니 해볼만 할 것이다.참! DailyHotel/rds-slowquery-log-demo는 테이블 로그인 경우만 테스트했으니 파일 로그를 사용하는 경우라면 이 점을 주의해야 한다.더 읽을거리Collecting and Analying Slow Query Logs for MySQLRDS(MySQL) のスロークエリを EFK スタック + Docker で出来るだけ手軽に可視化する考察(2)〜 log_output: FILE の場合 〜#데일리 #데일리호텔 #개발 #개발자 #개발팀 #Elasticsearch #엘라스틱서치 #꿀팁 #도입후기 #일지
조회수 1727

변화하는 웹 플랫폼 따라가기

플랫폼이 어떻게 변화해가는지를 살펴보면, 직접 경험해보지 않더라도 사람들의 문제의식이 어디에 있는지, 어떤 문제들이 언제부터 풀리게 되는지를 파악할 수 있습니다.이 글에서는 이런 변화들을 살펴볼 수 있는 몇가지 유용한 링크들을 소개하려고 합니다.어떤 논의들이 어디서 오가고 있을까WICG discoursehttps://discourse.wicg.io/WICG는 웹 인큐베이터 커뮤니티 그룹이라고 해서, 웹 표준에 기여해본 사람이 아니라도 토론을 통해 아이디어를 발전시켜서 사람들이 실제로 겪는 문제를 W3C 표준까지 끌어올리는 것을 목표로 하는 커뮤니티입니다.이 곳에서는 주로 CSS, DOM API에 대한 아이디어가 올라옵니다.ES-Discusshttps://esdiscuss.org/ES-Discuss는 WICG와 비슷하게 ECMAScript 스펙에 대해서 논의하는 메일링 리스트입니다. 위 링크는 메일링 리스트에서 오간 이야기를 쉽게 조회할 수 있도록 아카이빙 해놓은 사이트입니다.논의된 아이디어는 어디서 표준으로 다듬어지고 있을까HTML: https://github.com/w3c/html 또는 https://github.com/whatwg/htmlCSS: https://github.com/w3c/csswg-draftsJS: https://github.com/tc39/ecma262HTML은 W3C의 WebPlat WG와 WHATWG에서, CSS는 W3C의 CSSWG에서, JS는 ECMA의 TC39에서 표준을 이끌고 있습니다.위 저장소들에 공개된 초안은 표준이 되기까지 여러 단계를 거치게 되는데, 여기서 다루지는 않겠습니다. (2018-01-25 수정) 이에 대한 내용은 다음의 블로그 포스트에서 자세히 설명하고 있습니다.W3C 표준화 제정 단계ECMAScript와 TC39다듬어진 표준은 어떤 브라우저에서 얼마나 구현되고 있을까다음의 링크에서 각 주제에 대해 브라우저들이 현재 어디까지 구현을 했는지 파악할 수 있습니다.Chrome: https://www.chromestatus.com/featuresFirefox: https://platform-status.mozilla.org/Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/Safari: https://webkit.org/status/크롬 플랫폼 사이트의 경우 각 주제들이 어떤 버전에 반영되었는지 같이 확인할 수 있어서 편리합니다.나머지 브라우저들의 버전별 구현 상태는 https://caniuse.com/에서 주제를 검색하여 참고할 수 있습니다.구현된 기능들은 언제부터 사용할 수 있었고, 언제부터 사용할 수 있게 될까크롬과 파이어폭스는 릴리즈 캘린더를 공개적으로 관리하고 있습니다. 위에서 확인한 기능들을 담고있는 안정 버전이 언제쯤 릴리즈 될 지 다음의 링크를 보고 대략적으로 예상할 수 있습니다.Chrome: https://www.chromium.org/developers/calendarFirefox: https://wiki.mozilla.org/RapidRelease/Calendar크롬과 관련된 플랫폼 따라가기특정 크롬 버전이 어떤 V8 버전을 사용하고 있는지는 https://omahaproxy.appspot.com/에서 확인할 수 있습니다.Node.jsNode.js의 릴리즈 스케쥴은 https://github.com/nodejs/Release에서 확인할 수 있습니다.어떤 Node.js 버전이 어떤 V8 버전을 사용하고 있는지는 https://nodejs.org/en/download/releases/에서 확인할 수 있습니다.Electronhttps://electronjs.org/에서 일렉트론 최신버전이 어떤 노드, 크로미움, V8 버전을 사용하고 있는지 확인할 수 있습니다.일렉트론의 크로미움 팔로업은 깃헙 일렉트론 저장소의 Projects에서 확인할 수 있습니다: https://github.com/electron/electron/projects따라가는데 도움이 되는 블로그브라우저 벤더들이 직접 운영하는 블로그를 구독하면 웹 플랫폼의 소식을 가장 빠르게 접할 수 있습니다.ChromeChromium Blog: https://blog.chromium.org/V8 Blog: https://v8project.blogspot.kr/FirefoxMozilla Hacks: https://hacks.mozilla.org/SafariWebKit Blog: https://webkit.org/blog/EdgeMicrosoft Edge Dev Blog: https://blogs.windows.com/msedgedev/(2018-01-25 수정): @SaschaNaz님 제보로 Webkit status 사이트와 Edge 블로그 추가#스포카 #개발팀 #개발자 #인사이트 #업무일지 #후기

기업문화 엿볼 때, 더팀스

로그인

/