스토리 홈

인터뷰

피드

조회수 3193

iOS에서 간결한 API 클라이언트 구현하기 (like Retrofit+GSON)

이 글은 안드로이드 개발에서 웹 서버 API 클라이언트를 간결하게 구현할 수 있도록 도와주는 강력한 오픈소스 라이브러리인 Retrofit과 GSON의 조합을 iOS 개발에서도 따라해보고 싶은 분들을 위해 작성되었습니다. Retrofit+GSON를 실제로 사용하는 좋은 예제는 다른 블로그 글에서도 찾아볼 수 있습니다.배경리디북스 서비스가 발전하면서 점점 복잡해지고, 자연히 앱의 기능도 다양해지기 시작했습니다. 기능이 다양해지면서 웹 서버와의 연동을 위한 API 종류도 늘어났고 앱 내에서 API 호출이 필요한 부분도 다양해지면서 관련된 중복 코드가 이곳 저곳에 산재하게 되었고 전체적인 코드 퀄리티 향상을 위해 이를 최소화하고 모듈화 할 필요성이 생겼습니다.안드로이드에서는 Pure Java로 작성되어 어노테이션을 통한 간결한 코드를 사용할 수 있게 해주는 Retrofit을 GSON과 연동하여 JSON 응답을 손쉽게 객체에 맵핑 하여 사용함으로써 이러한 문제를 성공적으로 해결할 수 있었습니다. 이후 iOS 개발을 진행하면서 비슷한 역할을 할 수 있는 도구가 있을까 찾아봤지만 마땅하지 않아 결국 사용 가능한 도구들을 이용해 비슷하게 따라해보기로 했습니다.목표Retrofit+GSON 조합을 최대한 따라해서 iOS 앱의 코드 퀄리티를 높이기 위한 작업을 진행하기는 하지만 모방하는 것 자체가 목적이 될 수는 없으므로, 구체적인 목적은 다음과 같은 것들로 상정해보았습니다.API 통신 부분을 모듈화하여 관련 중복 코드를 최소화하기NSArray, NSDictionary를 직접 사용하여 제어 했던 JSON 처리 부분을 추상화하여 모델 클래스를 정의, JSON 응답을 자동으로 객체에 맵핑 해서 사용할 수 있도록 하기필요한 것Retrofit과 GSON의 동작에 대한 이해AFNetworking비동기 HTTP 요청 처리에 용이하므로 기존에도 이미 API 호출을 위해서도 사용하고 있었습니다.이 글의 내용은 버전 2.6.3 기준입니다.Swift 언어와 그에 대한 이해사실 Objective-C를 사용해도 무방하지만, 작업 당시 Swift가 발표된 지 얼마 되지 않은 시점 이었기 때문에 시험 삼아 선택 되었으며 실제로 Swift가 Objective-C 대비 가진 장점들이 적지 않게 활용되었습니다.이 글의 내용은 버전 2.0 기준입니다.구조와 동작클래스 이름 앞에 붙어 있는 RB는 리디북스에서 사용하는 클래스 접두어 입니다.RBApiServiceAPI 통신을 담당하는 부분의 핵심은 중앙의 RBApiService 클래스를 포함한 상속 구조라고 할 수 있으며 상술하면 다음과 같습니다.AFNetworking에서, HTTP 요청 작업의 큐잉부터 시작과 종료까지 라이프 사이클 전반을 관리하는 역할을 하는 AFHTTPRequestOperationManager를 상속받는 RBApiService 클래스를 정의각 API들은 역할군에 따라 RBBookService(책 정보 관련 API), RBAccountService(사용자 계정/인증 관련 API) 등과 같은 RBApiService의 하위 클래스들의 메소드로 정의됨이 하위 클래스들이 AFHTTPRequestOperationManager의 역할을 그대로 이어받아 자신을 통해 이루어지는 API HTTP 요청 작업들을 관리이 설명에 따르면 웹 서버의 /api/foo/bar API를 요청하는 메소드는 RBFooService 클래스에 다음과 같이 정의될 것입니다.func bar(param1: String, param2: String, success: RBApiSuccessCallback, failure: RBApiFailureCallback) -> AFHTTPRequestOperation! { let paramters = ["param1": param1, "param2": param2] responseSerializer = RBJSONResponseSerializer(responseClass: RBFooBarResponse.class) return GET("/api/foo/bar", parameters: parameters, success: success, failure: failure) }RBApiSuccessCallback과 RBApiFailureCallback은 요청과 응답이 완료되고 각각 성공, 실패일 때 호출되는 람다 함수(Objective-C의 block에 대응되는 개념) 타입으로 다음과 같이 typealias를 통해 선언되어 있습니다. typealias RBApiSuccessCallback = ((operation: AFHTTPRequestOperation, responseObject: AnyObject) -> Void)? typealias RBApiFailureCallback = ((operation: AFHTTPRequestOperation?, error: NSError) -> Void)?GET 메소드는 AFHTTPRequestOperationManager의 메소드로 새로운 HTTP GET 요청 작업을 생성하고 큐에 넣은 뒤 그 인스턴스를 반환합니다. bar 메소드는 이렇게 반환된 인스턴스를 다시 그대로 반환하는데 API 호출을 의도한 측에서는 이 인스턴스를 통해 필요한 경우 요청 처리를 취소할 수 있습니다. API에 따라 GET 이외의 다른 방식의 요청이 필요하다면 POST, PUT, DELETE등의 메소드들 또한 사용할 수 있습니다.RBFooBarResponse 클래스는 이 API 호출의 JSON 응답을 맵핑하기 위한 모델 클래스입니다. 이 API 요청의 응답은 RBJSONResponseSerializer 클래스를 통해 사전에 정의된 규칙에 따라 적절히 RBFooBarResponse 인스턴스로 변환되고 이 모든 과정이 성공적으로 진행되면 RBApiSuccessCallback의 responseObject 인자로 전달됩니다.모델 클래스와 RBJSONResponseSerializer앞서 이야기했듯이 RBJSONResponseSerializer는 JSON 형태로 온 응답을 특정 모델 클래스의 인스턴스로 맵핑시키는 작업을 수행합니다(Retrofit+GSON 조합에서 GsonConverter의 역할에 대응한다고 볼 수 있습니다).iOS 개발에서 전통적으로 JSON을 다루는 방식은 Cocoa 프레임워크에서 기본적으로 제공하는 NSJSONSerialization 클래스를 이용하여 JSON Array->NSArray로, 그 외의 JSON Object는 NSDictionary로 변환하여 사용하는 방식입니다. 이러한 방식을 사용할 경우 별다른 가공이 필요 없다는 장점이 있는 대신 다음과 같은 문제들에 직면할 수 있습니다.데이터가 명시적으로 정의된 프로퍼티로 접근되지 않고 문자열 키 기반의 키-밸류 형태로만 접근되므로 데이터의 타입이 명시적이지 않아 타입 검사와 캐스팅이 난무하게 되어 가독성을 해침오타와 같은 개발자의 단순 실수로 인한 버그를 유발할 가능성도 커짐특히 오타로 인한 버그의 경우 명시적인 모델 클래스의 프로퍼티로 맵핑 해서 사용한다면 IDE가 에러를 검출해주거나 최소한 빌드 타임 에러가 발생할테니 미연에 방지할 수 있습니다. 이러한 문제는 사소한 실수로 인해 찾기 힘든 버그가 발생한다는 점과 코드 리뷰를 통해서도 발견하기가 힘들다는 점에서 지속적으로 개발자를 괴롭힐 수 있습니다.RBJSONResponseSerializer를 통한 인스턴스로의 변환은 이런 문제 의식에서 출발했고 Retrofit에 GSON을 연계하여 사용하기 위한 GsonConverter가 해결을 위한 힌트를 제공한 셈입니다.// AFJsonResponseSerializer는 NSJSONSerializer를 이용해 NSArray/NSDictionary로 변환하는 기본적인 작업을 해줌 class RBJSONResponseSerializer: AFJSONResponseSerializer { var responseClass: NSObject.Type! override init() { super.init() } required init(responseClass: NSObject.Type!) { self.responseClass = responseClass super.init() } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject? { // 파서를 직접 구현하는 건 노력이 많이 필요하므로 우선 AFJSONResponseSerializer를 이용해 NSArray/NSDictionary로 변환 let responseObject: AnyObject! = super.responseObjectForResponse(response, data: data, error: error) if let dictionary = responseObject as? NSDictionary where responseClass != nil { // 변환 결과가 NSDictionary이면서 responseClass가 정의되어 있다면 변환 작업 시작 return responseClass.fromDictionary(dictionary, keyTranslator: PropertyKeyTranslator) } // NSArray라면 JSON이 top level array로 이루어졌다는 뜻이므로 변환 불가로 보고 그대로 반환 // 혹은 responseClass가 정의되어 있지 않아도 그대로 반환 return responseObject } }Key translatorfromDictionary 메소드 호출 시 함께 인자로 전달되는 keyTraslator는 JSON에서 사용되는 키로부터 모델 클래스의 프로퍼티 이름으로의 변환을 나타내는 람다 함수로 개발자가 원하는 규칙에 따라 정의하면 됩니다. 위의 코드에서 사용 중인 PropertyKeyTranslator는 리디북스 API에서 사용 중인 규칙 및 Swift의 네이밍 컨벤션에 따라 다음과 같이 언더스코어(_) 케이스로 된 이름을 카멜 케이스로 바꾸는 형태로 정의되었으며 이는 GSON의 FieldNamingPolicy 중 LOWERCASE_WITH_UNDERSCORES와 유사합니다.let PropertyKeyTranslator = { (keyName: String) -> String in let words = keyName.characters.split { $0 == "_" }.map { String($0) } var translation: String = words[0] for i in 1..NSObject.fromDictionary 메소드fromDictionary 메소드는 NSDictionary로 표현된 데이터를 실제 모델 클래스의 인스턴스로 변환하는 작업을 수행하며 NSObject의 extension(Objective-C의 category 개념과 유사합니다)으로 정의하여 원하는 모델 클래스가 어떤 것이든지 간에 공통적인 방법을 사용할 수 있게끔 했습니다.extension NSObject { class func fromDictionary(dictionary: NSDictionary) -> Self { // keyTranslator가 주어지지 않으면 디폴트 translator 사용 return fromDictionary(dictionary, keyTranslator: { $0 }) } class func fromDictionary(dictionary: NSDictionary, keyTranslator: (String) -> String) -> Self { let object = self.init() (object as NSObject).loadDictionary(dictionary, keyTranslator: keyTranslator) return object } func loadDictionary(dictionary: NSDictionary, keyTranslator: (String) -> String) { // 주어진 dictionary에 포함된 모든 키-밸류 쌍에 대해 작업 수행 for (key, value) in (dictionary as? [String: AnyObject]) ?? [:] { // keyTranslator를 이용해 키를 프로퍼티 이름으로 변환 let keyName = keyTranslator(key) // 프로퍼티 이름을 사용할 수 있는지 검사 if respondsToSelector(NSSelectorFromString(keyName)) { if let dictionary = value as? NSDictionary { // 밸류가 NSDictionary면 해당 프로퍼티의 타입에 대해 fromDictionary 메소드 호출 if let ecls = object_getElementTypeOfProperty(self, propertyName: keyName) as? NSObject.Type { setValue(ecls.fromDictionary(dictionary, keyTranslator: keyTranslator), forKey: keyName) } else { NSLog("NSObject.loadDictionary error: not found element type of property. (key: \(keyName), value: \(dictionary))") } continue } else if let array = value as? NSArray { var newArray = [NSObject]() // 밸류가 배열이면 각 요소별로 작업 수행 for object in array { if let dictionary = object as? NSDictionary { // 배열 요소가 NSDictionary면 프로퍼티의 배열 요소 타입에 대해 fromDictionary 메소드 호출한 뒤 배열에 추가 if let ecls = object_getElementTypeOfProperty(self, propertyName: keyName) as? NSObject.Type { newArray.append(ecls.fromDictionary(dictionary, keyTranslator: keyTranslator)) } else { NSLog("NSObject.loadDictionary error: not found element type of property. (key: \(keyName), value: \(dictionary))") } } else if let object = object as? NSObject { // NSDictionary가 아니면 그대로 배열에 추가 newArray.append(object) } else { NSLog("NSObject.loadDictionary error: can't cast element. (key: \(keyName), value: \(object))") } } setValue(newArray, forKey: keyName) continue } else if value is NSNull { continue } // NSDictionary, NSArray가 아니면서 null도 아니면 그대로 사용 setValue(value, forKey: keyName) } } } }주어진 dictionary에 존재하는 모든 키-밸류 쌍에 대해 밸류가 가진 타입과 이에 대응하는 프로퍼티의 타입에 따라 적절히 프로퍼티에 대응될 객체를 구한 다음 Cocoa 프레임워크에서 제공하는 KVC를 이용해 채워넣습니다.프로퍼티 타입 정보 가져오기모델 클래스가 반드시 Int, String, Float과 같은 기본적인 타입들로만 이루어져 있을 필요는 없고 다른 모델 클래스의 인스턴스나 배열을 포함하고 있어도 타입 정보를 런타임에 가져와 재귀적으로 데이터를 채워나가는 것이 가능합니다. 프로퍼티의 타입을 알아내는 과정은 다음과 같이 Swift에서 제공하는 Mirror 구조체를 통해 이루어지는데 이는 마치 (이름에서도 느낄 수 있듯이) Java의 리플렉션을 떠올리게 합니다.// 타입 이름에서 특정 접두어("Optional", "Array", "Dictionary" 등)를 찾아 제거 func encodeType_getUnwrappingType(encodeType: String, keyword: String) -> String { if encodeType.hasPrefix(keyword) { let removeRange = Range(start: encodeType.startIndex.advancedBy(keyword.length + 1), end: encodeType.endIndex.advancedBy(-1)) return encodeType.substringWithRange(removeRange) } else { return encodeType } } // object의 타입에서 propertyName의 이름을 갖는 프로퍼티의 타입 이름을 반환 func object_getEncodeType(object: AnyObject, propertyName name: String) -> String? { let mirror = Mirror(reflecting: object) let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)! // object의 타입 구조 children 중에서 propertyName을 찾음 for (label, value) in mirrorChildrenCollection { if label == name { // Optional 타입인 경우 "Optional" 접두어를 제외 return encodeType_getUnwrappingType("\(value.dynamicType)", keyword: "Optional") } } return nil } // object의 타입에서 propertyName의 이름을 갖는 프로퍼티의 타입 인스턴스를 반환 func object_getElementTypeOfProperty(object: AnyObject, propertyName name: String) -> AnyClass? { // 타입의 이름을 가져옴 if var encodeType = object_getEncodeType(object, propertyName: name) { let array = "Array" // "Array" 접두어로 시작할 경우 (배열인 경우) if encodeType.hasPrefix(array) { // "Array" 에서 "Array" 제외하고 T를 반환 return NSClassFromString(encodeType_getUnwrappingType(encodeType, keyword: array)) } let dictionary = "Dictionary" if encodeType.hasPrefix(dictionary) { // "Dictionary" 에서 "Dictionary", "K"를 제외하고 V를 반환 encodeType = encodeType_getUnwrappingType(encodeType, keyword: dictionary) encodeType = encodeType.substringWithRange(Range(start: encodeType.rangeOfString(", ")!.endIndex.advancedBy(1), end: encodeType.endIndex)) return NSClassFromString(encodeType) } // 커스텀 클래스 접두어를 가지고 있다면 그 타입 그대로 반환 if encodeType.hasPrefix(RidibooksClassPrefix) { return NSClassFromString(encodeType) } } return nil }RidibooksClassPrefix는 커스텀 클래스들의 접두어를 나타내는 상수이며(리디북스의 경우 앞서 이야기했듯 “RB”), 이 접두어가 붙어있는 경우에만 모델 클래스로 간주해 해당 타입 인스턴스가 반환됩니다.예시앞서 정의한 PropertyKeyTranslator를 사용했을 때, 위에 예시로 사용했던 /foo/bar API 요청의 JSON 응답과 모델 클래스 및 생성되는 인스턴스 형태의 예를 들면 다음과 같을 것입니다.(Int, Bool, Float과 같은 기존 NSNumber 기반의 타입을 가지는 프로퍼티들은 아직 정확한 원인은 알 수 없으나 nil 이외의 값으로 초기화 해주지 않으면 프로퍼티가 존재하는지 확인하기 위해 사용하는 respondsToSelector 메소드가 false를 뱉게 되어 사용할 수 없으므로 클래스 선언시 적절한 초기값을 주어야 합니다.{ "success": true, "int_value": 1, "string_value": "Hello!", "float_value": null, "baz_qux": { "array_value": [1, 2, 3] } }class RBFooBarResponse : NSObject { var success = false // true var intValue = 0 // 1 var stringValue: String! // "Hello!" var floatValue: Float! = 0.0 // nil var bazQux: RBBazQux! } class RBBazQux : NSObject { var arrayValue: [Int]! // [1, 2, 3] }맺음말이런 작업들을 통해 당초 목표했던 두 가지, API 통신 관련 중복 코드를 최소화 하면서 JSON 응답을 가독성이 더 좋고 실수할 확률이 적은 모델 클래스의 인스턴스로 자동 변환 하도록 하는 것 모두 달성하는 데에 성공했습니다.다만 모든 것이 뜻대로 될 수는 없었는데 Retrofit+GSON과 비교했을 때 플랫폼 혹은 언어의 특성에 기인하는 다음과 같은 한계들 또한 존재했습니다.Retrofit에서는 Java 어노테이션을 이용해 API 메소드의 인터페이스만 정의하면 됐지만 iOS 구현에서는 GET, POST 등의 실제 요청 생성 메소드를 호출 하는 것 까지는 직접 구현해줘야 함키->프로퍼티 이름 변환 규칙에 예외 사항이 필요할 때 GSON에서는 @SerializedName 어노테이션을 통해 손쉽게 지정할 수 있지만 iOS 구현에서는 예외 허용을 위한 깔끔한 방법을 찾기가 힘듬 (다만, 예외가 필요한 경우가 특별히 많지는 않기 때문에 큰 문제는 되지 않음)향후에는 HTTP 통신을 위해 사용 중인 AFNetworking(Objective-C로 작성됨)을 온전히 Swift로만 작성된 Alamofire로 교체하는 것을 검토 중이며 기존에 비해 좀 더 간결한 코드를 사용할 수 있을 것으로 기대하고 있습니다. 다만 Alamofire의 최신 버전이 iOS 8 이상을 지원하고 있어 iOS 7을 아직 지원 중인 리디북스인 관계로 언제 적용할 수 있을지는 아직 미지수입니다.#리디북스 #개발 #개발자 #iOS #iOS개발 #API #API클라이언트 #GSON #Retrofit #중복코드 #최소화 #API통신 #웹서버 
조회수 2315

DevOps, 그 문화에 대해서...

개발 방법론이나 소프트웨어 개발과 관련된 은빛 탄환과도 같은 뉘앙스를 풍기는 접근법은 수없이 많았다. 이제는 최고의 화두로 떠오른 DevOps에 대해서 삐딱한 아키텍트의 생각으로 끄적거려 보자.주변에 DevOps를 지향하는 개발회사들이 많다. 그리고, DevOps를 무슨 완전체인 것처럼 소개하는 칼럼이나 글들도 많다. 그렇다면, DevOps의 정체는 무엇이며, 우리 회사, 우리 개발팀이나 운영팀은 그런 준비가 되어 있는 것인지에 대해서 생각해봐야 한다.사람들은 정말 DevOps가 어떤 의미이기에 사람들이 궁금해하고 있는 것일까?, 그리고. 과연 정말 내가 속한 조직과 팀이 DevOps를 지향할 수 있을까? DevOps에 대해서 삐딱한 아키텍트가 생각해보는 것이 이번 칼럼의 목적이다.DevOps는 모든 팀, 모든 회사, 모든 곳에 사용되는 만병통치약이 아니다.DevOps는 새로운 개념인가?Culture와 movement에 대해서 먼저 이야기를 시작하는 것이 맞을 듯하다. Culture는 어떤 한 국가나 집단의 문화와 같은 것을 의미한다. 그리고, movement는 어떤 움직임을 의미하는 것으로 여기서 사용되는 의미로는 사람들이 조직적으로 어떤 것을 벌리는 운동을 의미한다.일반적으로 문화란 어떤 옷, 음악, 형태를 가진 조형물 등을 포괄하는 것으로 무형, 유형의 것을 모두 포함하는 것이 문화라고 할 수 있다.그리고, 이러한 문화는 해당 문명과 조직, 사회의 모든 것을 표현하고 있는 것이며, 그것에 대비하여 문화라는 형태를 통해서 표현한다. 그래서, 소프트웨어 개발의 조직이나 기업에서도 자체적인 개발자 문화라는 것이 존재하고 있다. 이는, 일반적으로 각 회사별로 그 형태나 상황, 사람들의 모습, 역사적인 배경과 발전과정을 통하고, 어떤 사람들이 그 조직을 거쳐갔느냐에 따라서 많은 부분에 있어서, 개발자들의 문화는 매우 다르다고 할 수 있다.이처럼, 개발자 문화의 영향으로 소프트웨어 개발 방법론과 같은 무형의 것부터, 실제 산출물, 개발 소스와 같은 실제 눈에 보이는 것까지 개발자 문화란 눈에 보이는 것과 눈에 보이지 않는 것을 모두 포함한다고 할 수 있다.이런 개발자 문화를 언급하기 전에, 개발자들의 운동과 운동을 위한 선언과 같은 것에 대해서 알아보자. 그중에서도 movement를 먼저 살펴보자. 개발자들 커뮤니티와 개발자들의 요즘 철학적인 움직임은 ‘요구사항’ 변동에 대해서 이제 관대한 생각을 가지기 시작했다고 볼 수 있다.어차피, 요동치는 요구사항에 대해서 ‘완결된 요구사항’이 나올 것이라고 기대하지 않고, 요구사항은 사랑하는 애인의 변덕스러운 마음이라는 생각을 가지기 시작한 것이 DevOps의 원칙적인 기본 생각의 변화라고 먼저 이야기를 하고 싶다.이제, 개발자들은 요동치는 사람들의 마음이나 사회적인 변덕을 소프트웨어로 반영하는 것을 매우 당연스럽고 자연스러운 과정이라고 인지하기 시작한 것이라고 볼 수 있다. 이처럼 기본적으로 요구사항이 변덕스러운 기획자나 고객의 마음이 당연한 것이라고 생각한다면, 오히려, 더 행복한 개발이 가능하도록 기준이나 계획을 잡을 수 있는 것 아닐까?이것이 DevOps의 개념 전환의 기본적인 개념이라고 볼 수 있다. 오히려. 처음부터 요구사항이 잘 정해졌고, 더 이상 변하지 않을 것이라고 거짓말을 하고 있는 기획자와 고객들의 마음속에 변덕스러운 변화에 대해서 이제는 관대한 개발자가 되려는 마음을 가진 것이라고 생각할 수 있다고 소프트웨어 개발자들은 이해하기 시작한 것이다.DevOps는 이러한 마음가짐의 변화와 movement가 먼저 필요하다. 기존의 개발 방법론이나 개발 문화에서 정의하려고 하였던, 뜬구름 잡는 ‘요구사항 명세’는 어차피 불가능한 것이니까, 그 부분을 매우 관대하게 받아들이고자 변화의 마음을 가지게 된 것이라고 생각한다. 그래서, 실제 고객을 만족시키는 요리사의 마음에다가 고객의 마음을 좀 더 가까이에서 이야기를 나눌 수 있는 웨이터의 마음을 가지고 시작해야 한다고 설명하는 것이 더 현명할 수 있다.이러한 변화의 요소에는 다음과 같은 개발자들이 두려워하는 몇 가지 요소들에 대해서 이제는 정말 명확하게 이야기할 수 있기 때문에 DevOps는 가능하다고 생각한다.DevOps의 내면에 깔려 있는 소프트웨어 개발자들의 두려움을 먼저 알아야 DevOps의 기본적인 원칙에 좀 더 접근할 수 있다. 그것은 다음에 나열된 내용들은 일반적으로 소프트웨어 개발자들이 어려워하는 것들이다.1.  소프트웨어를 솔루션 형태의 디자인으로 만드는 것은 정말 어렵다개발자들은 솔루션을 만들고 그것을 디자인하고 설계, 구현한다는 것은 정말 어려운 것이라고 인지하기 시작하였다. 솔루션을 만들고, 어떤 문제를 해결한다는 것은 정말 험난하고 고된 일이라고 이미 인지하였다.2.  테스트 케이스를 작성한다는 것은 정말 어렵다수많은 사용자의 환경을 인지하고, 그것에 대응하는 완벽한 테스트는 불가능하다는 것 또한 개발자들은 인지하였다. 그리고, 그 테스트를 만들기 위해서 쥐어뜯었던 머리카락과 수많은 시간들에 대해서 완전이란 불가능하다는 것을 인지한 것이다.3.  개발 관련 문서작성 또한 매우 어려운 것이다개발자들 간에 상호 소통하기 위한 문서의 작성과 다이어그램과 모델을 만든다는 것 또한 정말 어려운 일이다. 또한, 그것을 표준이나 변화해가는 기술적인 요청과 반영 내용을 모두 담는다는 것은 정말 어려운 일이라고 인지하였다.4.  개발자 자신이 동의하지 않는 기능 구현을 허구 헌 날 해야 한다는 것간혹이 아니라, 상당 부분 발생하는 동의하지 않는, 쓸모없다고 생각하는 기능 구현에 매달리고 있는 현실에 대해서 이제는 약간은 무덤덤하게 대응할 수 있는 개발자들의 마음가짐은 정말 관해하게 변화하였다.5.  다른 사람이 작성한 코드를 다루는 것인 매우 당연하다는 것생각 이상으로 다른 사람의 코드와 프레임워크에 가두어진 상태로 프로그래밍을 해야 한다는 것에 대해서 학교에서는 가르치지 않았다는 것을 매우 두려워하고, 원망한다. 타인이 만들어 놓은 코드에 대해서 읽는 방법에 대해서 가르쳐 주지 않은 교수님이 원망스러울 뿐이다.6.  고객과 같이 비전문가와 커뮤니케이션해야 한다는 것비전문가와 소통하는 방법에 대해서 아무도 가르쳐주지 않았다. 사실은 그들과 소통하고 그들을 설득하는 것이 최선의 방법인데, 왜? 그들과 소통하는 방법은 학교에서 가르치고 있지 않는가? 혹시. 교수님들도 그것을 포기한 것 아닌가 하는 의심이 든다? 그러한 마음이 생기기 시작하였고, 과거의 방법론이나 공학에 대해서 의심을 하기 시작하였다.7.  업무 완료에 필요한 시간 예측은 필수가 되었다는 것기능 단위의 시간 예측과 일정에 대해서 ‘감’이 필요하다는 것은 실제 현업에 나와서야 만 가능하다는 것을 이야기해준 선배와 교수가 없었다는 점도 실제 현업의 초기에 어려움을 느끼는 부분들이다.8.  업무의 우선순위와 작업 할당이 애매하다는 것도대체 누가 결정하는가? 그 순서에 대해서 아무도 모른다.9.  이름을 만들고, 이름과 의미를 부여한다는 것은 매우 어렵다는 것그냥, X, Y, I, j, k를 부여하면 안 된다고 하는데, 생각 이상으로 붙여야 할 이름과 규칙들이 너무도 많다.이처럼, 소프트웨어 개발이 어려워지고 두려워지는 개발자들보다 더 어려운 것도 있다는 사실을 소프트웨어 개발자들은 경험으로 터득한다. 그것은 다음과 같은 상황이다. 그리고, 해결책도 없다는 점이다.위의 두려운 상황은 ‘단단한 마음’으로 이겨낼 수 있지만, 정마로, 다음의 상황들은 가능하면 소프트웨어 개발자들이 피하고 싶어 진다. 하지만, 우리가 지금 당장, 어제, 그리고 내일도 만날 수 있는 상황이다.1.  무능력한 경영진의 삽질2.  멍청한 동료 개발자의 어설픈 코드3.  특정 기술이 무슨 이유에서 쓰이는지도 모르고 강제로 배우거나 사용해야 하는 것4.  재미있어 시작한 개발일이 정말 반복적인 작업에 의해서 재미없어졌을 때5.  이제 쏟아지는 버그를 만나게 되었을 때하지만 가장 두려운 상황의 최고봉은 역시, ‘개발자는 고객과 대화를 나누는 것이 가장 두렵다’라는 것이 정답일 것이다. 그리고, 두려운 것은 동료와의 커뮤니케이션과 소통이다. 아마도, 이러한 고객과 동료들 사이에 있다면, 개발자는 당연한 것이지만. ‘개발하는 것이 행복하지 않다’라고 느끼는 것은 매우 당연할 것이다.여기서. DevOps는 출발한다.이렇게 ‘개발하지 않는 것이 불행한 개발일’을 하지 않게 하기 위한 일종의 movement라고 생각하면 된다.아이러니 하지만, 이러한 불행을 해결할 가장 좋은 방법은 행복의 최소 조건이나 개발자가 원하는 개발환경의 최소 조건을 만족하면 된다. 그것은 바로 자원(resource)이 충분한 환경을 만들면 가능하다. ‘돈’이 넉넉하면 부수적으로 대부분 따라오는 것들이다.하지만, 실제 개발일을 이런 환경에서 할 수 있는 방법은, ‘취미’로 개발일을 하는 경우에만 100% 만족할 수 있을 것이다. 취미는 최종 개발완룐일을 언제든지 뒤로 미룰 수 있기 때문에 ‘무한정의 리소스’를 투입할 수 있는 유일한 방법일 것이다.DevOps는 개발자가 행복하게 소프트웨어를 개발할 수 있는 환경을 만드는 것이 목표이다. 과거의 개발 방법론이나 문화, 운동들이 대부분 ‘소프트웨어 품질’을 위해서 개개인의 시간과 개개인의 능력 차이를 무시하고 진행되었다면, DevOps는 그 우선순위의 가장 높은 개념으로 ‘개발자의 행복’을 우선순위 위에 둔다.결론적으로 ‘개발자가 행복’하다면,자연스럽게 소프트웨어의 ‘품질’은 올라간다는 개념이다.물론, ‘행복’이 아니라, ‘시간 낭비’라는 단어와 ‘물자와 자원 낭비’라는 결코, 개발자는 행복하지 않을 것이다. 대부분의 개발자들은 ‘시간과 자원의 낭비’를 가장 싫어한다. DevOps는 기본적으로 개발자들을 신뢰해야 형성된다.DevOps는 소프트웨어 개발과 운영, 서비스의 효율적인 환경을 만들기 위해서 노력하는 개발 문화로써 간단하게 줄여서 설명하자면. ‘소비자, 사용자들의 서비스의 요구사항을 가장 빠르고 단순화하여 대응할 수 있는 신속한 서비스 지원 형태. 그리고, 그것을 지원하고 유지시켜주는 소프트웨어 개발 문화’라고 이야기할 수 있다. 그래서 Development / Operations를 합친 말이라고 본다.물론, 이렇게 만들어진 환경은 당연하지만 개발자를 ‘행복’하게 할 것이다.DevOps는 빠르고, 단순화, 신속함이라는 서비스 형태를 지향한다. 그리고, 그것을 지원하고 유지시켜주는 소프트웨어 개발 문화를 지향하고 있다. 실제, DevOps를 구현했다고 평가를 받고 있는 Netflix와 Flickr 등의 개발 성과물들은 정말 놀라울 정도로 효과적이다.1만 개 이상의 AWS 인스턴스를 불과 10여 명의 DevOps팀이 운영하고, 초당 4만 장 이상의 업로드 부하를 버티고. 자동화된 상태에서 하루 10회 이상의 배포본이 반영되는 매우 효과적인 개발과 운영이 접목된 환경을 만들어 낸다는 사실에 개발자 문화의 최신화 경향을 만들어 냈다.이렇든 엄청난 효율과 고속의 처리를 만들어 낸 것은 어떤 이유 때문에 가능한 것이었을까? 그리고, 이러한 DevOps의 성과물들은 일반적인 IT기업에서도 얻을 수 있는 환경일까? 가장 먼저 DevOps의 장점을 몇 가지 정리하고 넘어가자.DevOps의 장점을 서술한다면 다음의 3가지로 선언할 수 있다.1.  최소 인원으로의 개발과 운영이 가능한 환경을 지향한다2.  서비스의 배포와 운영이 자유롭고, 서비스가 매우 신속하고 빠르게 운영된다.3.  개발의 배포가 자동화되며, 그에 따라 고품질 서비스를 지향한다.자, 그렇다면. 가장 중요한 것은 이러한 DevOps는 내가 속한 조직에서 만들 수 있는 문화와 개발형 태인가? 대부분의 개발 조직에서는 이러한 것에 대해서 가장 궁금할 것이다. 결론부터 이야기하자면 DevOps가 가동되고, 개발 조직의 문화가 되려면 다음의 두 가지가 필수이다.1.  소프트웨어를 잘 만들어내는 개발자2.  잘 동작하도록 운영하는 운영자그리고, 이러한 두 가지의 조건을 만족시키기 위한 기본적인 환경적인 구성이 필요하다. 그것은 가장 먼저 소프트웨어 품질을 관리하는 제대로 된 품질관리 조직이 있어야 하며, 개발 조직이 빠르게 소프트웨어를 개발, 빌드, 테스트, 배포, 운영하게 할 수 있는 사이클을 신속하게 진행할 수 있는 개발환경을 갖추고 있어야 하고 업무 프로세스를 정의하고, 각 조직 간의 역할을 조율하는 프로세스들이 매우 자연스럽게 자동화되어지고 효율적으로 운영되고 있어야 한다. 그래야, ‘소프트웨어를 잘 만들어내는 개발자’와 ‘잘 동작하도록 운영하는 운용자’가 만들어지게 되고, 그 역할과 방법론이 효율적으로 가동되는 DevOps는 가동된다.DevOps의 원칙그렇다면, 이러한 DevOps을 세팅하고 구입하기 위해서 조직이 필요로 하는 비용적인 측면은 어떤 것들이 있을 것인지 가볍게 살펴보자. DevOps는 매우 큰 비용을 요구하는 것은 아니다. 다만, 그 비용이라는 것이 전반적으로 투자된 비용을 의미하는 것이지, 단기간에 투입되어 얻어지는 효과는 아니라는 점에 주목해야 한다.가장 먼저, 개발자들은 기능 개발과 결함의 수정 등의 변화를 얼마나 자주 일으키고 있는지 체크하고 이를 관리하거나, 관리할 수 있는 포인트를 개발자들에게 제공하고 있는가? 하는 측면이 가장 먼저라고 할 수 있다.두 번째는 운영자가 실제 서비스의 안전성과 성능의 향상을 위하여 취해지는 시스템 아키텍처 적인 변화에 대해서 얼마나 두려워하고 있으며, 이를 얼마나 수치화하여 관리하고있는지, 그리고. 그 선택을 할 수 있는지가 DevOps에 가장 중요한 측면이기도 하다.세 번째는 이러한 개발집단과 운영 집단에서 선택과 운영, 개발의 우선순위 등을 고르고 선택할 수 있는 ‘권한과 책임’이 주어지고 있느냐 하는 점이다.네 번째는 큰 조직, 큰 기업, 큰 프로세스의 운영 시에는 이러한 DevOps와 같은 콘셉트는 운영하기 매우 어렵다. 그러므로, 개발과 운영환경의 구분과 절차. 권한과 릴리즈 절차와 규칙 등에 대해서 얼마나 세분화하고 있는지, 그리고. 일에 대해서 얼마나 작은 규모로 산정하고 산출하고 있는지에 대해서도 정의되어야 한다.아쉽게도 DevOps를 구현하고 싶지만, 착각하고 있는 개발자 조직의 경우의 사례를 살펴보면 다음과 같은 실제 일들이 벌어진다고 볼 수 있다.1.  사용하지도 않는 기능을 도출하고, 이를 위하여 시간과 비용을 낭비하고 있는 경우2.  개발 후 버그를 찾기 위해서 테스트를 하고 있다고 프로세스를 정형화하는 일이다. 실제 DevOps를 지향하는 개발 조직의 경우에는 내부적으로 개발 단계에서 충분하게 품질을 고려하여 디자인되고 개발을 진행하려 노력한다.3.  예측을 위한 투자를 많이 하고 있는가?라는 질문에 소극적인 경우이다. 대부분은 그나마. 사건 발생 시에 빠르게 대처할 수 있는 환경이라고 가능한 구축하라고 권하는 경우가 태반이다.4.  소프트웨어 공학을 잘 못 받아들여 정말 중요한 지표에 집중해야 하는데, 너무 많은 지표를 도출하기 위하여 삽질을 하는 경우가 대표적인 착각되어진 개발 조직의 경우라고 볼 수 있다.DevOps을 좁게 보는 진정한 장점DevOps는 ‘잦은 배포’를 수행하면서, 잦은 릴리즈를 수행하고, 잦은 릴리즈를 통해서 위험을 하향 균등화 시키는 것이 주목적이라고 작게 정의할 수 있기도 하다. 그래서, 애자일과도 아주 잘 맞는다. TimeBox를 2주로 맞추거나 1.5주로 맞추고 배포를 진행하는 경우도 빈번하게 필자는 상황을 참조한다.하지만, 이러한 DevOps를 구현하는 데 있어서는 다음과 같은 최소한의 필요충분 요건이 필요하다.1.  잦은 개발과 버그 픽스가 가능한 개발자 환경을 구현하라2.  공유 소스 코드 버전 관리시스템도 없다면, 이러한 환경을 구성한 다는 것은 거의 불가능하지 않겠는가?3.  빌드, 테스트, 배포 단계를 자동화하기 위하여 얼마나 노력하고 있는가?4.  수작업의 실수와 반복을 어떻게 최소화하기 위해서 노력하는가?5.  개발 조직과 운영조직의 협업을 위하여 빈번한 커뮤니케이션 소통 비용을 지불하고 있는가?이러한 최소한의 필요충분조건을 만족한다면, 개발 조직은 다음과 같은 최소한의 목표를 이루기 위해서 준비를 한다고 볼 수 있다.1.  개발과 품질관리, 운영을 교집합적으로 운영하기 위한 방법을 터득하였고, 그것을 개발 조직에 내재화하기 위하여 노력 중이다.2.  신뢰성, 보안성, 개발과 배포 사이클을 보다 더 빠르게 개선하기 위해서 배포, 테스트, 세부 기능 개발, 릴리즈 관리를 목표로 조직이 운영 중이다.3.  툴이 아니라, 문화와 일하는 방법에 대한 경험을 더 우선적으로 하고 있다.DevOps의 가장 중요한 원칙위에서 이야기한 필요조건과 환경에 대한 것이 준비가 된다면, 다음과 같은 DevOps의 원칙을 실현할 준비가 된 것이다. 그 원칙을 살펴보자1.  주요 기능에 집중하고 있는가?2.  품질을 내재화하기 위하여 노력하고 있는가?3.  개발에 필요한 지식을 창출하기 위해서 과학적으로 접근하고 있는가?4.  완벽한 명세서를 만들기 위한 비용보다, 명쾌한 협업을 중시하여 커뮤니케이션 비용을 지출하고 있는가?5.  가능한 한 빨리 개발하기 위해서 시도하고 있는가?6.  사람을 존중하는 개발자 문화를 만들고 있는가?7.  최적화를 위한 방안을 고안하는데 회의나 토론을 아까워하지 않고 있으며, 그것에 대해서 투자를 아낌없이 하고 있는가?이러한 과정은 DevOps에 대해서 실현하기 위해서 노력하는 행위와 절차라고 볼 수 있다. 가능하다면 DevOps의 성숙도 모델에 대한 설명과 실제 우리가 그러한 모델을 통해서 개발 조직에 DevOps의 사상을 표현할 수 있는지에 대해서 설명할 기회가 곧 다가올 것으로 기대해본다.물론, 기술적 부채에 대해서도 한 번 거론한 다음에 그 이야기를 이야기하도록 하겠다.DevOps는 애자일과 마찬가지로 선언이고 문화에 해당한다. 즐거운 개발을 지향하고 있다면 소프트웨어 품질은 매우 당연하게 좋아진다. 행복한 개발자가 훌륭한 소프트웨어를 만든다는 것을 잊지 말자. 그것이 DevOps의 시작이며, 출발이다.
조회수 1268

제니퍼에서 새로운 가능성을 실험하라

제니퍼는 기업 내부망에 설치되는 On-Premise 방식의 소프트웨어 제품이다. 12년 넘게 국내 점유율 1위를 지키고 있는 제품이다보니 그만큼 고객의 요구사항도 다양하다. 대부분의 솔루션 회사는 제품 개발 초기에 단일 소스코드를 유지하며 개발하는 것을 추구했을 것이다. 하지만 비즈니스를 하다보면 특정 고객을 위한 기능을 추가할 수 밖에 없는 상황이 오게 된다. 보통 이런 경우에는 숨겨진 기능으로 개발하거나 고객사 별로 소스코드를 다르게 가져가기도 한다.기존의 제니퍼를 사용하는 고객들은 애플리케이션 모니터링만이 아닌 브라우저나 스마트폰 같은 클라이언트 영역과 데이터베이스 관리 시스템까지 연계된 통합 모니터링을 하고자하는 요구사항을 오랫동안 요청했었다. 모니터링 제품 간의 연계를 생각하면 약간 생소하게 생각할 수 있는데, 특정 데이터를 수집하고, 이를 가공하여 사용자에게 보여주는 단순한 매커니즘의 하나라고 생각하면 이해가 쉬울 것 같다.즉, 다른 종류의 데이터를 하나의 화면에서 볼 수 있는 통합 환경을 제공해야 한다. 그래서 최근에는 오픈소스로 배포되고 있는 엘라스틱서치나 상용 제품인 스플렁크 같은 로그분석 솔루션이 주목받고 있다. 하지만 위와 같은 제품들을 사용하여 제니퍼 성능 데이터와 연계하여 통합 환경을 구축한다는 것은 말처럼 간단하지 않다. 제품을 구매하고 학습하는 비용이 생각보다 크고, 통합을 위한 별도의 시스템이 갖춰져야 한다는 것은 고객의 입장에서 큰 부담이 된다. 이러한 부담을 덜어주기 위해서 제니퍼는 실험실이라 불리우는 확장 기능을 제공한다. 실험실은 워드프레스의 플러그인과 비슷한 성격을 가지며 코드 레벨 영역에서 확장될 수 있다. 실험실은 처음부터 다른 모니터링 제품과의 연계를 위해 개발된 것은 아니었다. 기획 초기에는 방대한 제니퍼 데이터를 좀 더 다양한 형태의 화면으로 제공하기 위함이었는데, 아무래도 실험적인 요소가 강하다보니 기존의 대시보드나 분석 같은 범주로 들어가기에는 완성도 측면이나 제니퍼의 방향성에 영향을 미칠 수 있다는 판단에 별도의 범주로 만들게 되었다.  실험실이란 이름은 구글 메일의 실험실에서 따온 것인데, 아직 개발 중인 실험적 기능을 위한 테스트 공간이고, 언제든지 변경 또는 중단되거나 사라질 수 있다. 그리고 모든 실험실 소스코드는 깃허브를 통해 공개하는 것이 기본 정책이다. 제니퍼소프트 깃허브에 가보면 실제로 다수의 실험실 프로젝트가 존재한다는 것을 알 수 있다. 그 중 한가지만 간략하게 소개하자면 사용자 관점의 웹 서비스 모니터링 제품인 아르고스와 연계하여 브라우저나 스마트폰 같은 사용자 관점의 성능 데이터를 제니퍼 트랜잭션 데이터와 연계하여 분석할 수 있는 기능을 제공한다. 실은 그동안 고객들에게 사용자 관점의 성능 모니터링에 대한 요구사항이 많았지만 제니퍼 본연의 영역과 확연하게 다른 측면이 있어서 요구사항을 수용하는데 많은 고민이 필요했다. 그래서 우리는 관련된 솔루션 업체를 찾았고, 상호 간의 비즈니스 협력을 통해 서로의 부족한 부분을 보완하기로 결정했다. 실험실은 제니퍼가 시도하고 있는 새로운 기능을 미리 체험해 볼 수 있을 뿐만이 아니라 오픈소스나 관련된 솔루션과의 연계를 하기 위한 화면을 제공할 수 있다. 뿐만 아니라 코드 레벨 영역에서 확장을 하는 것이다보니 제품의 커스터마이징 범위가 넓어진다. 즉, 화면에 대한 고객의 요구사항이 제니퍼의 방향성과 크게 다르더라도 많은 고민을 하지 않고 충분히 원하는 것을 구현해줄 수 있다. 과거와 달리 동일한 데이터라도 좀 더 시각적인 화면을 요구하는 요즘같은 시기에 실험실은 이러한 시도를 하기에 좋은 방법이 된다.제니퍼는 화면 단위의 확장 기능인 실험실 뿐만이 아니라 트랜잭션 데이터가 수집되는 시점이나 특정 이슈가 발생할 때, 생성되는 이벤트 데이터를 어댑터를 통해 전달받을 수 있다. 어댑터도 실험실과 마찬가지로 코드 레벨 영역에서 확장할 수 있다. 실시간으로 전달받은 트랜잭션 데이터는 별도의 스토리지에 저장하여 목적에 맞게 조회해서 사용할 수 있다. 특히 이벤트 관련 어댑터는 가장 많이 사용되는 제니퍼 확장 기능이며, 고객사의 관제시스템 연동에 주로 사용된다.  실험실은 어댑터와 달리 제니퍼 서버에서 전달받은 데이터를 처리만 하는 단순한 구조가 아니었다. 제니퍼와 독립적인 화면 구성에 필요한 모든 요소들을 갖춰야했기 때문에 고려해야할 것들이 너무 많았다.  그럼에도 불구하고 만들게 된 이유는 단순히 필자의 편리함을 위해서였다. 평소에 데이터 시각화에 관심이 많았기 때문에 이미 존재하는 방대한 제니퍼 데이터를 다양한 방식으로 표현하기 위한 시도를 했었다.하지만 상용 솔루션인 제니퍼에 테스트 코드를 필자 임의로 추가해서 배포하거나 숨긴 기능으로 만들기에는 꽤 부담스러운 일이었다. 그렇다고 별도의 소스코드로 다르게 가지고 가기에는 관리 측면에서 어려움이 있다. 그렇기 때문에 기존의 제니퍼 소스코드를 참조만 하되 서로 독립적으로 개발하는 형태를 생각하게 되었다. 이렇게 필자의 편리함을 위해 시작한 실험실이지만 오픈소스나 다른 솔루션과의 연동을 위한 화면을 제공하고, 새로운 제니퍼 기능에 대한 비전을 시사하거나 고객의 피드백을 수용하는 용도로 확장되었다.소프트웨어 개발을 하다보면 제품이 추구하는 방향과 달라서, 또는 구현은 가능하지만 소모되는 리소스 비용이 부담이 될 경우, 그리고 특정 사용자를 위한 특화된 기능을 구현할 때, 모두가 만족할만한 기능이라는 확신이 없다면 제대로 진행하기가 어려운게 현실이다. 사실 새로 시도하는 기능은 시기와 때에 따라 앞에서 고려했던 것들과 다르게 평가되는 경우도 있다.그래서 아무리 작은 아이디어라도 시도를 해보는 것 자체만으로도 큰 의미가 있으며, 새로운 가능성을 발견하는 계기가 될 수 있다. 다만 현재는 제니퍼 기능 확장에 대한 기반 정도만 갖춰진 시작 단계라서 관련된 API 문서나 개발 도구에 대한 지원이 미흡한 것이 아쉬움으로 남는다. 다음 편에서는 자바 개발자 대상으로 실험실을 직접 구현하는 방법에 대해 알아볼 것이다.
조회수 1768

모니터링 하지 않는 DevOps 조직은 없다.

출처: https://www.pagerduty.com/blog/devops-monitoring-tools/DevOps 와 모니터링 사용자의 변화DevOps는 이제 너무나 익숙해진 용어입니다. 이미 아마존, 넷플릭스, 페이스북과 같은 우리가 사용하는 많은 서비스들이 DevOps 조직을 가지고 있으며 우리나라에서도 엔터프라이즈 IT 기업들의 운영 조직들은 DevOps로 조직이 변화해가고 있습니다. 그리고 이와 함께 모니터링 서비스에도 변화가 생기고 있습니다. DevOps 이전까지 모니터링 서비스들은 운영팀의 소유였습니다. 개발자들이 서비스를 개발하고 나면 서비스의 안정화까지 운영팀에서는 어플리케이션 성능 분석 모니터링을 위주로 사용하고 어플리케이션이 안정화 되고 나면 급박한 이상 상황에 대비하여 인프라 모니터링을 사용하게 됩니다. 이 모든것은 운영팀의 업무였습니다.  하지만 비지니스의 변화 속도가 빨라지고 서비스의 업데이트가 더이상 이벤트가 아닌 일상이 되어 가면서 기업의 운영팀은 모니터링을 통해 개발 내역을 확인하고 개발팀은 모니터링을 통해 피드백을 받아들이는 구조로 변화해 가고 있습니다. 결국 DevOps에서는 운영팀과 개발팀 모두가 모니터링에 관심을 가지게 됩니다.  DevOps ToolchainDveOps Toolchain은 PLAN - CREATE - VERIFY - PACKAGE - RELEASE - CONFGURE - MONITOR의 연속입니다. 그리고 MONITOR 는 다음번 PLAN을 위한 데이터를 제공해 줄 수 있어야 합니다. 기업이 DevOps를 구체화된 프로세스로 정립하기 위해서는 다양한 도구들을 도입해야 합니다. Toolchain의 모든 스테이지에는 개발과 운영이 의견을 나누고 자동화해나갈 수 있는 다양한 도구들이 제공 되고 있습니다. 이는 모니터링에서도 마찬가지 입니다. 출처: http://blog.launchdarkly.com/devops2/DevOps for MonitoringDevOps에서 모니터링은 인프라스트럭처에서 어플리케이션 뿐만 아니라 로깅과 비지니스까지 매우 넓은 범위를 모니터링 하게 됩니다. DevOps 팀은 인프라와 어플리케이션의 상관관계를 알 수 있어야 하며 지나간 데이터는 물론이고 현재의 데이터를 실시간으로 분석할 수 있어야 합니다. DevOps 조직에서 사용하는 모니터링은 크게 아래와 같이 나눌 수 있습니다.Infrastructure and Network Monitoring서버, 라우터, 스위치를 포함한 Infrastrucre와 Network 전반에 대한 모니터링을 제공합니다. Nagios, Zabbix 와 같은 오픈소스 기반의 솔루션이 많이 제공되고 있습니다. 해외 서비스로는 DataDog 이 유명하며 국내에서는 WhaTap 이 Infrastructure 모니터링을 제공하고 있습니다. DataDog은 대규모 서버를 한눈에 볼수 있는 벌집 구조의 데시보드로 유명합니다. Application Performance Monitoring어플리케이션  성능 모니터링은 고객의 트랜잭션을 분석하는 동적 분석 도구 입니다. 웹 서비스를 운영하는 과정에서 성능 이슈가 발생하는 경우 해당 지점을 찾는 용도로 사용됩니다. 신속한 버그 추적과 재발 방지를 위해서도 사용될 수 있으며 최소 응답시간을 지속적으로 유지하기 위한 필수 서비스입니다. 좀더 능동적으로 APM을 사용한다면 발생 빈도가 높은 메소드를 분석하여 코드 리팩토링에 사용 할 수도 있습니다. 오픈소스로는 네이버의 핀포인트 와 와탭의 CTO가 커미터로 참여하고 있는 스카우터 가 있습니다. 해외에서는 New Relic, AppDynamics 가 유명하며 국내에는  WhaTap 이 APM 서비스를 제공하고 있습니다. 와탭의 트랜잭션 분포도는 APM 서비스중 데이터 분석 간격이 가장 짧은 것으로 알려져 있습니다.Log Analysis로그 분석은 플랫폼에서 제공하는 시스템 로그를 분석하거나 커스터마이징된 로그 데이터를 분석하는 도구입니다. 로그 분석을 통해 시스템의 결함을 미리 알아낼 수도 있으며 비지니스 데이터를 분석할 수 도 있습니다. Splunk, Elastic, PaperTrail, Logstash,  Loggly,  Logentries,  SumoLogic 과 같은 벤더를 통해 서비스를 제공받을 수 있습니다. 결론DeveOps는 개발과 운영이 만들어 가는 문화이기도 하지만 많은 도구의 도움을 받아서 진행해야 하는 프로세스이기도 합니다. 모니터링 서비스는 개발과 운영이 함께 서비스를 개선할 수 있게 해주는 중요한 도구입니다. 많은 모니터링 도구들이 DevOps를 지원하고 있습니다. 다양한 모니터링 도구와 서비스를 잘 이용한다면 DevOps 조직을 더욱 탄탄하게 만들고 비지니스도 빠르게 성장시킬수 있을 것입니다. 출처: https://blog.appdynamics.com/engineering/5-challenges-for-a-successful-enterprise-devops-model/관련 글https://techbeacon.com/10-companies-killing-it-devops10 companies killing it at DevOpsTop companies have made the move to DevOps and serve as the framework for others ready to make the move. Is your company ready for a DevOps...techbeacon.com https://www.slideshare.net/jallspaw/10-deploys-per-day-dev-and-ops-cooperation-at-flickr10+ Deploys Per Day: Dev and Ops Cooperation at FlickrCommunications and cooperation between development and operations isn't optional, it's mandatory. Flickr takes the idea of "release early, release often" to an…www.slideshare.net https://en.wikipedia.org/wiki/DevOps_toolchainDevOps toolchain - Wikipediaen.wikipedia.org http://blog.launchdarkly.com/devops2/DevOps 2.0Decoupling feature rollout from code deployment and the rise of user-centered deploymentsblog.launchdarkly.com https://aws.amazon.com/ko/devops/what-is-devops/데브옵스란 무엇입니까? – Amazon Web Services(AWS)aws.amazon.com #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 4255

자바스크립트 기초 문법 정리 Part 2 - 객체

지난 Part 1 포스팅에 이어 자바스크립트 기초 문법에 대해 정리해보았습니다. 이번 포스팅에서는 여러 객체와 그 객체에서 제공하는 각 메서드에 대해 정리하였습니다. 다루는 객체의 여러 메서드에 대해 정리하였기 때문에 전 포스팅처럼 간략하지는 않지만 이번 포스팅을 저장해 두고 자바스크립트로 개발하면서 필요할 때마다 참고하여 보기에는 좋을 것 같습니다. 다만, 메서드 사용 예의 코드는 넣지 않았으니 예제 부분이 필요하다면 필히 공식 문서를 참고해주세요. 익히는 것 자체도 공식 문서를 통하여 보는 것이 가장 좋지만 혹여 영어에 취약하신 분이라면 이 포스팅을 참고하는 것도 괜찮을 것 같습니다. :)내장 객체브라우저의 자바스크립트 엔진에 내장된 객체. String/Date/Array/Nath/RegExp Object 등이 있음.날짜 객체 DateDate 객체 생성new Date()new Date(milliseconds)new Date(dateString)new Date(year, month, day, hours, minutes, seconds, milliseconds)Date Get 메서드getDate() - 일 정보를 가져옴.getDay() - 요일 정보를 가져옴. 0(일요일)-6(토요일)getFullYear - 연도 정보를 가져옴. (yyyy)getHours() - 시간 정보를 가져옴.getMilliseconds() - 밀리초 정보를 가져옴. 0-999 (1/1000 초의 단위)getMinutes() - 분 정보를 가져옴.getMonth() - 월 정보를 가져옴. 현재 월에서 -1한 값으로 옴.getSeconds() - 초 정보를 가져옴.getTime() - 1970년 1월 1일부터 경과된 시간을 밀리초로 가져옴.Date Set 메서드setDate() - 일 정보를 설정.setFullYear() - 연도 정보를 설정. 원한다면 월과 일 정보도 설정할 수 있다.setHours() - 시간 정보를 설정.setMillseconds() - 밀리초 정보를 설정.setMinutes() - 분 정보를 설정.setSeconds() - 초 정보를 설정.setTime() - 1970년 1월 1일부터 경과된 시간을 밀리초로 설정.기타 Date 메서드now() - 1970년 1월 1일부터 지금까지의 밀리초를 반환.parse() - 날짜 형태의 문자열을 변환하여 1970년 1월 1일부터 입력한 날짜까지의 밀리초를 반환.toString() - Date 객체를 문자열로 변환.toJSON() - Date 객체를 JSON 데이터로 변환.valueOf() - Date 객체를 밀리초로 반환.숫자 객체 NumberNumber 생성var num = 1;      var num2 = new Number(1);Number 객체의 속성MAX_VALUE - 표현 가능한 가장 큰 수.MIN_VALUE - 표현 가능한 가장 작은 수.POSITIVE_INFINITY - 무한대 수 표기.NEGATIVE_INFINITY - 음의 무한대 수 표기.NaN - 숫자가 아닌 경우 표기.Number 객체 메서드toExponential(n) - 자수 표기법으로 소수점 n자리만큼 문자형 데이터로 반환.toFixed(n) - 소수점 n자리만큼 반올림하여 문자형 데이터로 반환.toPrecision(n) - 유효 숫자 n의 개수만큼 반올림하여 문자형 데이터로 반환.toString() - 숫자형 데이터를 문자형 데이터로 반환.valueOf() - 객체의 원래 값을 반환.parseInt(값) - 데이터를 정수로 변환하여 반환.parseFloat(값) - 데이터를 실수로 변환하여 반환.수학 객체 MathMath 메서드 및 상수Math.abs(숫자) - 숫자의 절댓값을 반환.Math.max(숫자1, 숫자2, 숫자3) - 숫자 중 최댓값을 반환.Math.min(숫자1, 숫자2, 숫자3) - 숫자 중 최솟값을 반환.Math.pow(숫자, 제곱값) - 숫자의 거듭제곱한 값을 반환.Math.random() - 0~1 사이의 난수를 반환.Math.round(숫자) - 소수점 첫째 자리에서 반올림하여 정수를 반환.Math.ceil(숫자) - 소수점 첫째 자리에서 무조건 올림에서 정수를 반환.Math.floor(숫자) - 소수점 첫째 자리에서 무조건 내림해서 정수를 반환.Math.sqrt(숫자) - 숫자의 제곱근 값을 반환.Math.PI - 원주율 상수를 반환.배열 객체 ArrayArray 생성var array = new Array();array[0] = 1;array[1] = 2;var array2 = new Array(1, "temp", true);var array3 = [1, true, "문자열도 가능"];Array 객체의 메서드 및 속성join(연결문자) - 배열 객체에 데이터를 연결 문자 기준으로 1개의 문자형 데이터로 반환.reverse() - 배열 객체에 데이터의 순서를 거꾸로 바꾼 후 반환.sort() - 배열 객체에 데이터를 오름차순으로 정렬.slice(index1, index2) - 배열 객체에 데이터 중 원하는 인덱스 구간만큼 잘라서 배열 객체로 가져옴.splice() - 배열 객체에 지정 데이터를 삭제하고 그 구간에 새 데이터를 삽입할 수 있음.concat() - 2개의 배열 객체를 하나로 결합.pop() - 배열에 저장된 데이터 중 마지막 인덱스에 저장된 데이터 삭제.push(new data) - 배열 객체에 마지막 인덱스에 새 데이터를 삽입.shift() - 배열 객체에 저장된 데이터 중 첫 번째 인덱스에 저장된 데이터를 삭제.unshift(new data) - 배열 객체의 가장 앞의 인덱스에 새 데이터를 삽입.length - 배열에 저장된 총 데이터의 개수를 반환.문자 객체 StringString 생성var str = "hello";      var str2 = new String("hi");String 객체 메서드 및 속성charAt(index) - 문자열에서 인덱스 번호에 해당하는 문자 반환.indexOf("찾을 문자") - 문자열에서 왼쪽부터 찾을 문자와 일치하는 문자를 찾아 최초로 일치하는 문자의 인덱스 번호를 반환. 찾는 문자가 없으면 -1 반환.lastIndexOf("찾을 문자") - indexOf와 동일하나 문자열의 오른쪽부터 찾음.match("찾을 문자") - indexOf와 동일하나 찾는 문자가 없으면 null을 반환.replace("바꿀 문자", "새 문자") - 문자열에서 왼쪽부터 바꿀 문자와 일치하는 문자를 찾아 최초로 찾은 문자를 새 문자로 치환.search("찾을 문자") - 문자열 왼쪽부터 찾을 문자와 일치하는 문자를 찾아 최초로 일치하는 인덱스 번호를 반환.slice(a, b) - a개의 문자를 자르고 b번째 이후에 문자를 자른 후 남은 문자를 반환.substring(a, b) - a 인덱스부터 b 인덱스 이전 구간의 문자를 반환.substr(a, 문자 개수) - 문자열에 a 인덱스부터 지정한 문자 개수만큼 문자열을 반환.split("문자") - 지정한 문자를 기준으로 문자 데이터를 나누어 배열에 저장하여 반환.toLowerCase() - 문자열에서 영문 대문자를 모두 소문자로 바꿈.toUpperCase() - 문자열에서 영문 소문자를 모두 대문자로 바꿈.length - 문자열에서 문자의 개수를 반환.concat("새로운 문자") - 문자열에 새로운 문자열을 결합.charCodeAt("찾을 문자") - 찾을 문자의 아스키 코드 값을 반환.fromCharCode(아스키 코드 값) - 아스키 코드 값에 해당하는 문자를 반환.trim() - 문자의 앞 또는 뒤에 공백 문자열을 삭제.브라우저 객체 모델(BOM)브라우저에 내장된 객체. window 객체브라우저 객체의 최상위 객체.window 객체 메서드open("url 경로", "창 이름", "옵션 설정") - 새 창을 열 때 사용.- open() 메서드 옵션 설정: width/height/left/top/location/status/scrollbars/tollbarsalert("메세지") - 경고 창을 띄움.prompt("질의 내용", "기본 답변") - 질의응답 창을 띄움.confirm("질의 내용") - 확인/취소 창을 띄움.- 확인 클릭시 true 반환, 취소 클릭시 false 반환.moveTo(x 위치값, y 위치값) - 창의 위치를 이동시킬 때 사용.resizeTo(너빗값, 높잇값) - 창의 크기를 변경시킬 때 사용.setInterval("스크립트 실행문", 시간 간격) - 일정 간격으로 반복하여 실행문을 실행시킬 때 사용.clearIntervar(참조 변수) - 참조 변수에 참조되어 있는 setInterval() 삭제.setTimeout("스크립트 실행문", 시간 간격) - 일정 간격으로 한 번만 실행문을 실행시킬 때 사용.clearTimeout(참조 변수) - 참조 변수에 참조되어 있던 setTimeout() 삭제.screen 객체사용자의 모니터 정보를 제공하는 객체.screen 객체 속성width/height/availWidth/availHeight/colorDepth(사용자 모니터가 표현 가능한 컬러 bit)location 객체사용자 브라우저의 주소 창에 url에 대한 정보와 새로 고침 기능을 제공하는 객체.location 객체 속성 및 메서드href - 주소 영역에 참조 주소를 설정하거나 URL 반환.hash - URL의 해시값을 반환.hostname - URL의 호스트 이름을 설정하거나 반환.host - URL의 호스트 이름과 포트 번호를 반환.port - URL의 포트 번호를 반환.protocol - URL의 프로토콜을 반환.search - URL의 쿼리를 반환.reload() - 새로 고침.history 객체사용자가 방문한 사이트 중 이전에 방문한 사이트와 다음 방문한 사이트로 다시 돌아갈 수 있는 속성과 메서드를 제공하는 객체.history 메서드 및 속성back() - 이전 방문한 페이지로 이동.forward() - 다음 방문한 페이지로 이동.go(이동 숫자) - 이동 숫자만큼의 페이지로 이동. 음의 값이면 이전 페이지로 이동.length - 방문 기록에 저장된 목록의 개수 반환.navigator 객체현재 방문자가 사용하는 브라우저 정보와 운영체제의 정보를 제공하는 객체.navigator 속성appCodeName - 방문자의 브라우저 코드명을 반환.appName - 방문자의 브라우저 이름 반환.appVersion - 방문자의 브라우저 버전 정보를 반환.language - 방문자의 브라우저 사용 언어를 반환.product - 방문자의 브라우저 사용 엔진 이름을 반환.platform - 방문자의 브라우저를 실행하는 운영체제를 반환.userAgent - 방문자의 브라우저와 운영체제의 종합 정보를 제공.문자 객체 모델(DOM)HTML 문서의 구조.선택자직접 선택자직접 문서에서 요소를 선택함. (id/class/폼 명/요소 명 등)document.getElementById("아이디 명") - 아이디를 이용해 요소를 선택.document.getElmentsByTagName("요소 명") - 요소의 이름을 이용해 요소를 선택.document.formName.inputName - 폼 요소에 name 속성을 이용해 요소를 선택.인접 관계 선택자직접 선택자를 사용해 선택해 온 문서 객체를 기준으로 가까이에 있는 요소를 선택함. (parentNode/childeNodes 등)parentNode - 선택한 요소의 부모 요소를 선택.childNodes - 선택한 요소의 모든 자식 요소를 선택. 선택한 모든 요소가 저장됨.firstChild - 선택한 요소의 첫 번째 자식 요소만 선택.previousSibling - 선택한 요소의 이전에 오는 형제 요소만 선택.nextSibling - 선택한 요소의 다음에 오는 형제 요소만 선택.문서 객체 이벤트 핸들러 적용하기onclick - 선택한 요소를 클릭했을 때 이벤트 발생.onmousevoer - 선택한 요소에 마우스를 올렸을 때 이벤트 발생.onmouseout - 선택한 요소에 마우스가 벗어났을 때 이벤트 발생.submit - 선택한 폼에 전송이 일어났을 떄 이벤트 발생.버튼document.getElementById("btn").onclick = function() {    alert("welcome");}일단은 참고하는 책을 기준으로하여 정리해보았는데 후에 시간이 될 때마다 공식 문서를 참고하여 번역한다는 생각으로 보다 세부적인 사항을 정리해도 좋을 것 같다는 생각이 드네요. 우선적으로는 빠르게 함수와 이벤트에 대해 배우고 객체에 대한 더 자세한 사항을 정리하도록 하겠습니다. 다음 포스팅은 자바스크립트의 함수와 이벤트에 대해 다룰 예정입니다!참고문헌:Do it! 자바스크립트+제이쿼리 입문 - 정인용JavaScript 튜토리얼 문서 (http://www.w3schools.com/js/default.asp)티스토리 블로그와 동시에 포스팅을 진행하고 있습니다.http://madeitwantit.tistory.com#트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유
조회수 766

DevOps 문화 안에서의 APM의 역할 [1] (DevOps+JENNIFER)

 DevOps의 시작언제나 그랬듯이 소프트웨어 개발 트렌드는 계속 변화하고 있다. A부터 Z까지 모든 것을 새롭게 개발했던 것과 달리 아키텍처나 사용하는 용도에 따라 개방형 플랫폼이나 오픈소스 등을 활용하여 원하는 소프트웨어를 쉽게 개발할 수 있게 되었다. 또한 클라우드로 인해 애플리케이션과 서비스 개발에 대한 새로운 패러다임이 나타나고 있다. 기존의 온-프레미스 환경에서는 물리적 서버 준비, 운영체제 설치, 서비스 배포 등에 수많은 시간이 걸렸지만, 클라우드를 활용하면서 단시간에 원하는 자원을 준비하고 배포할 수 있게 되었다.이러한 변화로 개발자의 영역이 좀 더 넓어지는 계기가 되었다. 이는 전통적인 비즈니스 환경에서 개발, 빌드, 테스트, 배포, 운영에 이르는 프로세스를 효율적으로 운용할 수 있게 되어 고객의 요구사항을 빠르게 반영할 수 있게 되었다. 이것이 바로 DevOps의 시작이다. 하지만 다양한 오픈소스의 탄생과 클라우드 환경의 확산 등으로 인해 정말로 새로운 기능에 대한 개발이 빨라졌을까? 그렇다면 이에 따른 문제는 없을까? 개발 프로세스의 병목 구간DevOps의 필수 조건인 테스트 및 배포의 자동화가 이뤄지면 운영 단계에서는 반영된 사항들에 대해 주기적으로 모니터링을 해야 한다. 만약에 반영된 소스코드에 장애를 발생시킬 수 있는 잠재적 버그가 존재한다면 이를 어떻게 운영 단계에서 찾을 수 있을까? 예를 들어 특정 서비스의 피크타임에 부하가 급증한다면 앞서 말한 상황에 대한 버그가 발생할 확률이 상대적으로 높아진다. 하지만 장애의 원인이 될 수 있는 요소는 매우 다양하기 때문에 단순히 트래픽 문제로 속단할 수는 없다.직접 개발한 소프트웨어만의 문제가 아닐 수도 있으며, 제품 개발시 생산성 향상을 위해 도입된 다른 종류의 오픈소스에서 문제가 될 수도 있다. 실은 이런 류의 프로젝트들은 상용 제품이 아니므로 문제가 발생하면 상당히 곤란한 경우가 생기곤 한다. DevOps를 위한 환경이 구성되고, 고객의 요구사항을 빠르게 반영할 수 있는 시스템이 갖춰졌더라도 결국에는 앞서 말한 다양한 종류의 잠재적, 환경적인 문제들로 인해 병목이 발생할 수 있다.  모니터링 단계에서 APM의 역할개발 프로세스의 마지막 관문인 모니터링 단계는 DevOps에서 매우 중요한 역할을 한다. 하지만 안타깝게도 이미 반영된 실제 서비스에서 모니터링을 성공적으로 마치고 피드백 수집 단계로 넘어가기 위해서는 앞서 말했던 장애의 원인을 빠르게 진단해야 한다. 경우에 따라 많은 시간이 소모되기도 하기도 하며, 이는 바로 생산성 저하로 이어진다. 또한 새로운 프로세스 진행을 더욱더 보수적으로 만드는 원인이 된다.DevOps를 완벽하게 실현하기 위해서는 모니터링 단계에서 서비스 배포 이후의 서버에 들어오는 트랜잭션에 대한 상태를 배포 전과 비교할 수 있어야 하며, 응답을 지연시킬만한 요소들을 빠르게 인지할 수 있어야 한다. 그리고 배포된 소스코드로 인해 서비스 장애가 발생하는 상황이 온다면 이를 처리하기 전까지 어떻게든 서비스 장애를 지연시켜야만 한다. 이러한 이유로 DevOps 진영에서는 APM의 역할은 매우 중요한 이슈이다. 우리는 제니퍼를 통해 앞서 말한 기능들을 활용하는 방법에 대해 알아볼 것이다. 모니터링 프로세스모니터링 단계는 아래 그림과 같이 문제의 발견 및 조치, 문제해결시 재배포 단계로 나눌 수 있다.  제니퍼 대시보드를 통해 액티브서비스 상태와 트랜잭션 변화 추이를 모니터링 할 수 있는데, 만약에 새로 배포된 소스코드에 문제가 있다면 처리 중인 액티브서비스가 쌓이게 되고 , 트랜잭션 분포도 차트는 기존에 그려졌던 패턴과 다르게 보여지게 된다.이런 시점에 운영에서는 설정 여부에 따라 이벤트를 발생 시킬 수 있다. E-Mail이나 SMS, Slack과 같은 메신저 등으로 각각의 담당자들에게 서비스 상태를 알려줄 수 있으며, 담당자에게 이벤트 메시지가 전달되었다면 제니퍼를 통해 두가지 조치를 할 수 있게 된다. 먼저 개발자는 스마트 프로파일링 기능을 통해 원인분석을 하고, 운영에서는 서비스가 최악의 상태가 되기 전에 트랜잭션 유입을 차단하여 다른 화면으로 리다이렉트 시켜주는 PLC 기능을 사용할 수 있다.제니퍼에서는 서버에서 하나의 요청에 대한 처리가 끝나면 곧바로 수집되는 데이터를 트랜잭션이라하며, 현재 수행 중인 상태에 대한 실시간 데이터를 액티브서비스라고 정의한다.   모니터링 기준 값 설정서비스를 배포하기 전에 모니터링 단계를 원활하게 수행하기 위해서는 제니퍼 관리 화면에서 몇가지 설정을 해야한다. 먼저 서비스 장애 발생시 이벤트 알림 및 서비스 부하량 제어 설정의 기준이 되는 값인 전체 에이전트의 평균 액티브서비스 개수를 알아야 한다. 하지만 서비스가 운영되는 환경에 따라 기준 값이 너무 다르기 때문에 어느 정도 안정적으로 서비스가 운영되고 있다고 생각하는 시점에 대략적으로 기준 값을 정하면 된다.에이전트란 모니터링 대상 애플리케이션에 기생하여 성능 데이터를 수집하고, 이를 서버로 전송하는 역할을 하는 모듈을 말한다. 참고로 모니터링 대상 애플리케이션은 플랫폼 환경에 따라 차이가 있을 수 있는데, 일반적으로 WAS(Web Application Server)나 웹 서버를 말한다.  액티브서비스는 처리가 완료되지 않은 상태이므로 서비스 장애의 원인분석을 위한 데이터로는 적합하지 않다. 그렇기 때문에 액티브서비스 개수는 기준 값이 될 수 없으며, 개발자는 처리가 완료된 트랜잭션 데이터의 응답시간을 기준 값으로 제니퍼의 프로파일링 관련 설정을 해야 한다. 설정된 값을 기준으로 트랜잭션 분포도 차트에서 가상의 선을 긋고, 그 선 위에 있는 트랜잭션을 대상으로 스마트 프로파일링 기능을 수행할 수 있다.  본문에서는 모니터링 단계에서 직면하게 되는 문제점과 이를 해결하기 위한 APM의 역할과 필요성 대한 이야기를 했다. 다음 편에서는 본격적으로 제니퍼를 활용하여 모니터링 프로세스를 어떻게 수행하는지에 대해 알아볼 것이다.2편에서 계속...
조회수 4128

RESTful API를 설계하기 위한 디자인 팁

올라왔었던 REST 아키텍처를 훌륭하게 적용하기 위한 몇 가지 디자인 팁의 글에서 언급되지 않았던 추가적인 내용에 대해서 좀 더 얘기해보고자 합니다. 혹시 이전 포스팅을 읽지 않으셨다면 이전 포스팅을 먼저 읽으신 후 이 포스팅을 읽어주시기 바랍니다.Document?컬렉션에 관해서는 앞서 소개한 이전 글에서 자세히 설명해놓았으니 읽어보시기 바랍니다. 지금 제가 언급할 것을 도큐먼트인데요. 도큐먼트는 컬렉션과는 달리 단수명사나 명사의 조합으로 표현되어 URI에 나타납니다.http://api.soccer.restapi.org/leagues/seattle/teams/trebuchet/players/claudio 위의 예제에서 leauges라는 컬렉션 리소스가 있는 것을 알 수 있습니다. 그 컬렉션의 자식 리소스 중 하나가 seattle이라는 리소스인데요, 바로 이 리소스가 도큐먼트입니다. 도큐먼트는 하위 계층으로 또 컬렉션을 가질 수 있습니다. 이 예제에서의 teams가 seattle의 자식 컬렉션 리소스가 되겠지요. 즉, 단수 리소스는 도큐먼트라 칭하고 복수 리소스는 컬렉션으로 칭한다고 알아두시면 됩니다.이 URI는 또한 문서의 계층 구조를 표현하고 있습니다. 즉 슬래시 기호(/) 다음으로 나타내는 명사가 그 앞에 나오는 명사의 자식 계층이 되는 것이지요. 이러한 도큐먼트의 응답으로써, 요청에서 명시된 Content-Type 헤더에 1:1대응하는 응답을 주는 것이 의미 있을 때가 있습니다. 가령,URI : dogs/1 1) Content-Type: application/json 2) Content-Type: application/xml 3) Content-Type: application/png 이와 같은 URI에 3개의 요청이 주어졌고, 각각 Content-Type이 다음과 같을 때 어떤 응답이 보내져야 할까요? 물론, 그것은 응답을 설계한 사람의 맘이지만 일반적인 기준을 적용해본다면 1번과 2번 요청에는 각각 json, xml 형식으로 구조화된 데이터가 그리고 3번 요청에 대해서는 해당 강아지의 사진이 담긴 png 파일을 보낼 수 있을 것입니다. 또한, Content-Type에 대해서 명시하여 원하는 리소스를 선택할 수 있으므로 URI 내에는 파일 확장자를 포함하지 않는 것이 좋습니다.dogs/1.xml 위와 같은 URI를 만드는 것보다, dogs/1 위의 URI에 Content-Type: application/xml헤더를 포함하여 요청을 보내는 것이 더 적절한 선택입니다. 어째서 파일에 확장자를 붙이지 않는 것이 더 나은 선택일까요? URI는 고유한 리소스를 나타내는 데 쓰여야 합니다. 그런데 URI에 확장자를 붙이는 순간 마치 다른 리소스인 것처럼 느껴집니다. 확장자를 달리하여 같은 리소스에 대한 다른 표현 양식을 주문하는 것이지 해당 리소스가 달라지는 것은 아닙니다. 또한, URI에 직접 확장자가 붙게 되면 해당 리소스 URI가 응답으로 지원하는 확장자만큼 새로운 URI들이 생기게 되겠지요. 결코, 이것은 좋은 디자인이 아닙니다.Controller?기본으로 GET, PUT, POST, DELETE 요청에 1:1매치 되는 개념인 CRUD가 있습니다. CRUD의 앞글자들을 풀어보면 Create, Read, Update, Delete가 될 텐데, 각각 POST, GET, PUT, DELETE에 대응되는 개념입니다. 그런데 사실 URI를 디자인 하다 보면 이러한 방식으로 나타내기 참 어려운 경우를 많이 만나게 됩니다. 그 중 가장 많은 경우가 어떤 특정한 행위를 요청하는 경우입니다. 많은 분이 이럴 때 동사를 쓰는데, 앞선 포스팅에서 밝혔듯이 동사를 써서 URI를 디자인하는 것은 대체로 옳지 않은 방식으로 여겨집니다.이럴 때 컨트롤러 리소스를 정의하여 이 문제를 해결할 수 있습니다. 컨트롤러 리소스는 URI 경로의 제일 마지막 부분에 동사의 형태로 표시되어 해당 URI를 통해 접근했을 때 일어날 행위를 생성합니다. (개념적으로는 이렇게 받아들이시면 됩니다.) 생성과 관련된 요청이 POST이기 때문에 컨트롤러 리소스에 접근하려면 POST 요청을 보내야 합니다. 예제를 살펴보시면 이해하기 빠르실 겁니다.http://api.college.restapi.org/students/morgan/register 리소스 morgan을 등록 http://api.ognom.restapi.org/dbs/reindex 리소스 dbs를 재색인 http://api.build.restapi.org/qa/nightly/runTestSuite 리소스 nightly에 테스트를 수행 그리고 마치 프로그램의 함수처럼 컨트롤러 리소스에는 입력값을 전달할 수 있습니다. 그것은 POST 요청의 엔티티 바디에 포함되어야 합니다. 그리고 역시 함수에서 반환값을 돌려주듯이 컨트롤러 리소스에서는 해당 입력 값에 대한 응답 값을 돌려주면 되겠습니다.URI 뒤에 붙는 쿼리의 용도흔히 GET 요청을 보낼 때 뒤에 추가로 쿼리 스트링(?,=,& 기호를 이용하여)을 전달하곤 합니다. 여기서는 그 쿼리 스트링을 어떻게 디자인 하는 게 좋은지에 대한 논의와 함께 실제 서비스에서 사용되는 사례를 살펴봅니다.가령 특정 컬렉션 리소스에 대하여 질의를 보낼 때 그 컬렉션의 집합이 너무 거대할 수 있으므로 필요한 정도의 정보만을 요구하기 위해서 페이징 값 혹은 구분 값을 쿼리 스트링에 포함할 수 있습니다. 예를 들어 보면/resources?pageSize=10&pageStartIndex=0 페이징을 위한 정보 전달 /dogs?color=red&state=running&location=park 구체적인 검색 제약사항 전달 이런 식으로 써서 페이징을 한다든가 혹은 다른 파라메터(color=red)따위를 던져서 검색 범위를 제한할 수 있습니다. 흔히 쿼리 스트링을 저런 용도로 많이 사용하기 때문에 아마 관찰력이 좋으신 분들은 저런 종류의 쿼리 파라메터를 네이버, 구글 같은 포털사이트의 검색 서비스를 이용하시면서 본 적이 있으실 것입니다.이와는 약간 다르게 실제 DB에서 사용하는 SQL의 select 문과 같은 결과를 낼 수 있도록 돕는 쿼리 스트링을 URI에 나타내려는 시도도 많은 편인데요. 물론 SQL에서 제공하는 구문의 모든 의미를 다 제공할 필요는 없겠지만, 기본적으로 서비스에서 필요한 정도의 인터페이스를 적절히 제공한다면 사용자가 선택할 수 있는 옵션이 많아진다는 측면에서 좋은 방법이겠죠. 이와 관련된 예제를 몇 개 소개하겠습니다. 이것은 실제 서비스에서 API로 제공되었던 URI들입니다. 구조나 의미가 SQL 문과 상당히 유사합니다.LinkedIn /people:(id,first-name,last-name,industry) 이 경우 people 리소스를 요청하되 마치 SQL 쿼리에서 가져올 필드를 제한하는 것처럼 필요한 필드에 대해서만 괄호로 묶어서 지정한 것을 볼 수 있습니다. Facebook /joe.smith/friends?fields=id,name,picture 이 경우 이름(혹은 계정이름)이 joe.smith인 사람의 정보를 가져오되 LinkedIn의 예와 같이 필드를 제한(id,name,picture)해서 가져오도록 한 예입니다. Google ?fields=title,media:group(media:thumbnail) 구글도 마찬가지네요. 이쯤 오면 대략 저 URI가 무엇을 의미하는지 알아채셨으리라 생각합니다. URI 설계시에 주의해야 할 점URI에는 소문자를 사용해야 합니다. 왜냐하면, RFC 3986은 URI 스키마와 호스트를 제외하고는 대소문자를 구별하도록 규정하기 때문이지요.http://api.example.restapi.org/my-folder/my-doc HTTP://API.EXAMPLE.RESTAPI.ORG/my-folder/my-doc 위의 두 URI는 같은 URI입니다. 호스트에서는 대소문자를 구별하지 않기 때문이지요. http://api.example.restapi.org/my-folder/my-doc http://api.example.restapi.org/My-Folder/my-doc 하지만 위의 두 URI는 다른 URI입니다. 뒤에 붙는 path가 대소문자로 구분되기 때문입니다. 물론 소문자가 아닌, 대소문자를 섞어 쓰거나 혹은 대문자만 쓰는 것도 가능하지 않으냐는 반론이 나올 수 있습니다. 하지만 대소문자를 섞어 쓰면 URI를 기억하기 어려울 뿐만 아니라 실제 사용 시 실수하기 쉽다는 단점이 있습니다. 만약 대문자만 쓴다면 상관은 없겠으나 일반적으로는 URI에 대문자를 잘 쓰지 않기 때문에 소문자로 쓰는 것을 권장합니다.HTTP HEADERHTTP 요청과 응답을 보낼 때 특정 헤더를 포함해 요청, 응답 그리고 리소스에 대한 메타 정보를 전달할 수 있습니다. 요청 헤더와 응답 헤더에 포함되면 좋을 만한 헤더 정보들에 대하여 알아보겠습니다.요청 헤더Accept응답으로 받고 싶은 미디어 타입을 명시하기 위하여 사용됩니다. 예제를 들어 설명하겠습니다.GET /magna-opus HTTP/1.1 Host: example.org Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 이 요청은 mangna-opus 리소스에 대해서 기본적으로는 html이나 xhtml의 형식으로 응답을 받고 싶되, 만약 상황이 여의치 않으면 xml을 만약 그것도 여의치 않다면 모든 응답(*/*)을 받아들이겠다는 것을 말합니다. 옆에 붙은 q가 선호도를 나타내게 되지요. (q 생략 시 1값을 가짐) 만약 앞의 예에서 모든 응답에 대한 표시가 없다고 가정하고 서버에서 앞의 세 가지 미디어 타입을 모두 지원할 수 없는 상황이라면 응답으로 406 상태코드를 내보내야 합니다.Accept-Charset응답으로 받고 싶은 캐릭터셋에 대하여 명시하는 헤더입니다.Accept-Charset: iso-8859-5, unicode-1-1;q=0.8 가령 위의 예제는 일단 iso-8859-5를 선호하지만 unicode-1-1도 괜찮다는 메시지를 전달합니다.User-Agent현재 요청을 보낸 Agent의 정보를 표시하기 위해 사용됩니다.User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 파이어폭스 버전 21.0의 UA스트링, OS에 대한 정보도 담겨져있다. Referer해당 요청을 보내기 바로 직전에 참조하던 리소스 혹은 주소에 대한 정보를 나타내기 위해 사용합니다.Referer: http://en.wikipedia.org/wiki/Main_Page 응답 헤더Content-Length요청과 응답 메시지의 엔티티 바디가 얼마나 큰지에 대한 정보를 나타내기 위해 사용합니다. 단위는 바이트입니다.Content-Length: 348 Last-Modified해당 리소스가 마지막으로 갱신된 시간을 나타내기 위하여 사용됩니다. 캐싱 정책과 관련되어 중요한 헤더중 하나입니다.Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT 캐시나 쿠키정책과 관련된 헤더 정보는 글의 분량을 고려하여 생략하였지만, 매우 중요한 헤더 중 하나이므로 다른 관련 문서들을 검색하여 일독을 권합니다.HTTP 상태 코드의미에 잘 맞는 URI를 설계하는 것도 중요한 일이지만 그 리소스에 대한 응답을 잘 내어주는 것 또한 중요한 일입니다. 그런데 혹시 HTTP의 상태코드 중 200이나 404코드 정도만 알고 계시지 않으신가요? 그 코드의 정확한 의미를 얘기하실 수 있으신가요? 사실 저도 흔하게 볼 수 있는 상태코드 몇 개 정도만 알고 있고 나머지 상태코드의 정확한 의미라든지 쓰임새에는 관심이 별로 없었던 것이 사실이었습니다. 하지만 전문적으로 웹 개발의 길을 걸어갈 사람이라면 그보다는 좀 더 자세히, 많이 알고 있을 필요가 있겠지요. 사실 우리가 생각하는 것보다 훨씬 많은 상태코드가 존재하고 각각 그 쓰임이 다 다릅니다. 그 중 몇 개를 살펴보겠습니다.200 : OK일반적인 요청 성공을 나타내는 데 사용합니다. 단, 주의해야 할 점이 있다면 200코드를 에러 응답에 사용하면 안 된다는 것입니다. 가령 코드는 200인데 에러메시지를 포함한다든가 하면 의미에 맞지 않은 응답코드를 보낸 것이겠지요. 이런 적절치 못한 상황을 처리하는 경우에는 4XX대 코드를 사용하여야 합니다.201 : Created리소스 생성 성공에 대한 응답 코드입니다. CRUD 요청에서 Create 요청에 대한(즉, 컬렉션에 도큐먼트 추가 같은) 응답으로 내보낼 수 있는 응답코드입니다. 응답 헤더의 Location 필드에 생성된 리소스에 접근할 수 있는 URI를 포함할 수 있다면 브라우저에서 그 값을 참조하여 적절히 대응할 수 있겠습니다.202 : Accepted대체로 처리 시간이 오래 걸리는 비동기 요청에 대한 응답으로 사용됩니다. 즉, 이 요청에 대한 응답이 결과를 포함하지 않을 수 있다는 것이죠. 하지만 최소한 응답 헤더나 응답데이터에 해당 처리를 모니터링할 수 있는 리소스 페이지를 안내하거나 혹은 해당 리소스가 처리되기까지의 예상 경과 시간 따위를 안내하는 것이 더 좋은 설계라고 할 수 있겠습니다.301 : Moved Permanently리소스가 이동되었을 경우의 응답코드입니다. 새로 리소스가 이동된 URI를 응답 Location 헤더에 명시해야 합니다. 이 응답을 받은 클라이언트는 새 URI로 이동하든지 아니면 URI를 갱신하고 캐싱을 한다든지 하는 행위를 해야 되겠지요.400 : Bad Request일반적인 요청실패에 사용합니다. 대체로 서버가 이해할 수 없는 형식의 요청이 왔을 때 응답하기 위해 사용됩니다. 무턱대고 400에러를 응답으로 주지 말고, 다른 4XX대의 코드가 더 의미를 잘 설명할 수 있는지에 대하여 고민해야 합니다.401 : Unauthorized말 그대로 리소스 접근 권한을 가지고 있지 않다는 것을 의미하기 위한 응답코드입니다. 리소스를 획득하기 위하여 요청자는 인증에 필요한 헤더(가령 Authorization 헤더 같은)나 데이터를 첨부해야 할 것입니다. 필요한 헤더나 데이터는 서버 쪽에서 요구하는 스펙을 충실히 따라야겠지요.403 : Forbidden감춰진 리소스에 접근하려 할 때의 응답코드입니다. 401과 달리 인증의 여부와 관계없이 리소스를 보여주지 않습니다. 기본적으로 클라이언트 쪽에 정보를 공개하고 싶지 않은 리소스임을 나타내기 위해 사용합니다.404 : Not Found해당 URI와 매치되는 리소스가 없다는 의미를 전달합니다. 어지간한 사람들은 다 한 번씩(?) 마주치게 되는 응답코드이지요.405 : Method Not Allowed지원하지 않는 요청(예를 들어 POST 요청을 받는 컨트롤러 리소스에 GET 요청을 보낸다든가)을 하였을 때 사용합니다. 가능하다면 응답 메시지에 Allow 헤더를 추가하고 그곳에 지원하는 메서드를 명시하여 클라이언트 측에서 정확한 요청을 보낼 수 있도록 유도합니다.Allow: GET, POST 406 : Not Acceptable해당 미디어 타입(MIME 타입)에 대해서 지원하지 않을 때 사용합니다. 요청 Accept 헤더에 명기된 타입(가령 Application/xml)에 대해서 지원이 불가능할 경우에 돌려주면 되는 코드입니다.409 : Conflict요청의 형식에는 문제가 없지만 리소스 상태에 의하여 해당 요청 자체를 수행할 수 없는 경우의 응답코드입니다. 즉, 이미 삭제된 리소스를 또 삭제한다든가 비어있는 리스트에서 무언가를 요청한다든가 하는 모순된 상황을 생각해보면 되겠습니다. 응답으로는 그 방법을 어떻게 해결할 수 있을지에(혹은 문제가 무엇인지) 대한 힌트가 포함되면 좋을 것입니다.500 : Internal Server Error일반적인 서버 에러에 대한 응답코드입니다. 4XX대의 에러코드가 클라이언트 측 에러를 나타내기 위해 사용된다면, 5XX대의 에러코드는 서버 측 에러를 나타내기 위해 사용됩니다.503 : Service Unavailable가장 두려운(?) 응답코드 중 하나일 503입니다. 현재 서버에 과부하가 걸려있거나 유지보수를 위하여 잠시 접근이 거부될 때 필요한 응답코드입니다.그냥 맨 앞의 숫자별로 퉁쳐서 상태코드를 내보내지 않고, 이렇게 디테일한 의미까지 따져가면서 상태코드를 내보내는 것에 대해서 그 효용성에 의문을 제기하시는 분들이 있을 것 같습니다. 하지만 브라우저에서 혹은 서버 단에서 특정 상태코드에 대해서 내부 구현을 달리하거나 최적화를 통해 더 쾌적한 환경을 제공할 가능성이 있으므로 되도록 의미에 걸맞은 상태코드를 사용하는 것을 생활화하는 것이 중요합니다. 또한, 이렇게 디테일한 상황을 가정하고 만든 URI들이 다음에 서비스를 확장할 때 큰 도움이 될 것임은 의심할 여지가 없겠지요.위에서 소개한 응답 코드 말고 또 다른 응답 코드들에 대해서도 전부 소개해 놓은 링크를 밑에 달아두었으니 참고하시기 바랍니다.정리지금까지 소개한 내용이 조금은 두서없게 느껴졌을 수도 있겠다는 생각이 들어 한 번 전체 내용 정리를 해보려 합니다.컨트롤러의 정확한 쓰임을 알고 적절한 컨트롤러 URI를 구현하자.URI에 추가로 붙게 되는 쿼리 스트링의 형식을 잘 디자인하여 사용자로 하여금 적재적소에 쓸 수 있도록 하자.가능하다면 이용 가능한 HTTP 헤더를 적절하게 첨가하자.HTTP 상태코드의 의미에 대해서 생각해보고 상황에 맞는 적절한 상태 코드를 응답으로 보내줄 수 있도록 하자.이 글을 쓰면서 한빛 미디어의 일관성 있는 웹 서비스 인터페이스 설계를 위한 REST API 디자인 규칙과 apigee사의 web API design eBook을 참고하였습니다. 둘 다 내용이 좋은 서적이고 이 글에서 다루지 않은 심층 내용을 다루니 기회가 되시면 읽어보세요.referencesUniform resource identifierapigee api design best practicesrestful uri designHTTP status codesList of HTTP status codesURI schemeMIME typesMIMEfun and unusual http response headers#스포카 #디자인 #디자이너 #디자인팀 #개발 #개발자 #개발팀 #협업 #코워킹 #Co-working #업무프로세스 #꿀팁 #인사이트
조회수 1209

AWS 이사하는 날

오늘 8퍼센트의 AWS 인프라를 일본에서 한국으로 옮겼다. 기술적인 내용은 이사를 리드하신 세바님이 다뤄 주시기로 하셨고, 나는 그냥 오늘을 남겨두려고 한다.(세바님이 8퍼센트 서울살자에 기술적인 내용들을 다뤄주셨다.)올해 초 AWS 서울 리전이 열리면서 도쿄에서 옮겨 가야겠다 라고 생각한 것이 벌써 수개월이 지났다. 작업을 시작하면 얼마나 걸릴지 예상이 잘 되지도 않고, 인프라 전체를 예쁘게 정리해야 한다는 부담 때문에 쉽게 손이 가지 않았다. 하지만 새로 조인하신 세바님이 AWS 이사를 가지 못해 여러 가지 제약이 생기는 답답한 상황을 참지 못하시고 총대를 메셨다. 아마 나보다 좀 더 답답하셨나 보다. :)17시에 다 함께 모여 현재 작업 진행상황과 오늘 이전 계획을 검토한 후 바로 퇴근을 했다. 긴 밤이 되리라 생각해서 조금이라도 잠을 자려고 노력은 했지만 두 아이가 있는 집에서 쉬는 것은 역시 쉽지 않더라. 지하철을 타고 23시 30분에 이모작 근무를 위해 다시 회사에 들어섰다. 이미 몇몇 분이 모여서 오늘 작업에 대한 이야기를 나누고 있었다. 아침에 만나는 것보다 왠지 반갑다. 모두 모여서 파이팅을 외치고 기념사진을 하나 찍고 작업을 시작했다.(웃으면서 시작한 작업을 웃으면서 마칠 수 있을 것인가?)일단 서버 작업 공지를 띄우고 작업을 시작한다. 지난 회사에서는 모든 서비스가 24시간 운영되었어야 했기 때문에 서버 점검 시간을 따로 갖지 못해다. 그래서 큰 서버 업데이트 작업을 할 때마다 시간에 쫓기고, 장애 발생을 실시간으로 해결해 가며 작업을 했었다. 하지만 이번에는 시간을 확보해두고 작업을 하는 것이라 그래도 마음에 좀 여유가 있었다.이전 작업을 하기 위해 각 파트를 담당하는 시니어 개발자들만 있어도 충분한데, 서버 이전을 하는 것이 흔치 않은 경험이기 때문에 주니어들도 가능하면 참여를 요청했다.(꼬꼬마들이 세바님 뒤에 쪼르르 모여서 설명을 듣고 있다)코드를 이해하기에 가장 좋은 방법은 코드를 함께 짜면서 설명을 하는 것이고, 인프라를 이해하기에 가장 좋은 방법은 인프라를 설치하는 과정을 함께 하는 것이다. 하나하나 작업을 해가면서 이런저런 이야기들을 나누었다. 이전을 하는 서버의 역할, 더 나은 아키텍처,  AWS의 역사,  AWS의 여러 가지 서비스의 세부적인 옵션에 대해서도 이야기를 나누었다.  세바님이 꼼꼼하게 준비를 해주신 덕분에 1시 30분이 되니 기본적인 이전 작업이 끝났다. 야식을 먹고 맥주를 한잔 마시고 각각의 기능들에 대한 본격적인 테스트를 시작했다.(야식은 12시 전에는 치킨을 시켜야 하고 12시 후에는 족발을 시켜야 한다)드디어 세바님을 제외한 다른 잉여 인력들이 할 일이 생겼다. 체크리스트에 있는 항목들을 하나씩 테스트 하기 시작한다. 꼼꼼하게 준비를 했지만 역시나 예상하지 못했던 문제들이 드러난다. 다행히 이전 작업을 되돌려야 할 만큼 큰 문제는 아니었기에 적절히 대응을 하고 계속 테스트를 진행했다.어느덧 시간이 흘러 3시가 되었다. http://8percent.kr 의 도메인을 도쿄에 있는 서버에서 서울에 있는 서버로 변경했다. 이제 내부 시스템들을 추가적으로 점검해야 한다. 가능하면 끝까지 확인을 하고 자리를 뜨고 싶었지만 내일 오후 사무실을 지키면서 혹시 모를 장애에 대응을 해 줄 사람이 필요할 것 같아 먼저 퇴근을 했다.집에 돌아오면서 생각해 보니 오늘 내가 한 일이 거의 없었다. 기뻤다. 서버 이전 작업을 내가 해야만 하는 일로 생각하며 계속 들고 있었는데 세바님이 먼저 나서서 이 일을 진행해 주셨다. 중요한 작업 중에 자리를 뜨는데도 전혀 불안함 마음이 들지 않았다.아침에 일어나서 슬랙을 확인했다. 슬랙에 별다른 멘트가 없는 것을 보니 큰 문제는 없나 보다. 야호! 이전된 서버가 정상적으로 운영되고 있는지 확인을 위해 심사팀도 일찍부터 출근해서 테스트를 진행하고 있었다. 회사에 도착해 보니, 나를 제외하고는 모두 밤을 새워 일을 하고 계셨다. 다들 몽롱한 표정이다. 고맙다.하루에도 8시간씩 같이 일하는 동료들이지만 왠지 이렇게 같이 밤을 지새워서 작업을 하고 나면 동지애가 생긴다. 긴 밤을 고생해준 개발팀 멤버들에게 다시 한번 고마운 마음을 전한다. 앞으로도 잘 부탁드려요!(제가 따로 드릴것은 없어서 박수를!)#8퍼센트 #에잇퍼센트 #AWS #서버 #서버이전 #인프라 #개발팀 #팀워크 #조직문화
조회수 651

블록체인 진짜 하나도 모르는 디자이너의 독학일기(1)

독학을 시작했습니다. 스터디를 가려고 했는데 수많은 전문용어들이 제 영혼을 피폐하게 만드는 바람에 정신건강이 염려되었거든요. 포토샵도 혼자 배웠으니 이것도 못할까! 라고 자신있게 책을 폈는데 못할 것 같습니다.......그래도 산 책 값이 아까우니 읽고 공부한 내용들을 하나하나 정리해보고자 합니당! 블록체인 전문가님들이 혹시 이 글을 보신다면 노잼과 지루함내지는 유치함을 느끼실 수 있으니 엄빠미소로 지켜봐주시면 감사하겠습니다. 잘못된 부분이 있다면 바로 잡아주세요!!글을 쓰면서 5가지 원칙을 지킬겁니다.1. 꼭 써야하는 고유명사가 아닌 이상 어려운 단어는 쓰지 않습니다. 중학생 정도가 이해될 수준이길 제발 바랍니다...저는 블록체인을 이제 이틀 째 공부하고 있거든요.2. 가급적 팩트체크된 내용만 쓸겁니다.3. 제대로 공부하려면 경제사, IT기술, 코딩 등등..수많은 요소가 복잡하게 들어가지만 여기선 꼭 필요한 쏘옥 뽑아서 얘기할 겁니다. 4. 짧게 쓸 겁니다.5. 가끔 쓸 겁니다.(자주 쓰기 힘든 주제임..)시작합니당 :)블록체인이 왜 태어났는지 얜 뭔지부터 알아야 할 것 같아요. 그러자면 시간을 조금 되돌려서 우리는 어떻게 사고파는 경제활동을 해왔는지 살펴볼께요.1. 아주 오래전 = 기억하기종이란게 나타나기도 전 우리는 사과5개를 빨간집에서 해가 질 무렵에 씨앗10개와 교환했다. 는 걸 기억해야 했어요. 문제는 서로가 잘못 기억하거나 한 쪽이 다르게 우겨버리면 할 말이 없다는 거죠..철저히 신뢰와 기억에 의존한 거래였어요.2. 오래 전 = 나무나 가죽에 새기기원래 사람은 두 발로 직립보행 하기 전부터도 그림을 좋아했어요. 동굴에도 그리고 돌에도 그리고, 나무나 땅에도 곧잘 그림을 그렸죠. 뭔가 주고받는 물품이 많아지면서 기억하기가 힘들어지자, 이젠 가죽이나 나무 등등에 갯수를 남기기 시작했죠. 문제점은 그 가죽이나 나무가 훼손되거나 도난당하면 증명할 방법이 없다는 거에요.'동쪽 언덕 마을에서 온 또박이가 가죽3개를 사갔다.'3. 조금 오래전 = 종이에 적기(단식부기)종이가 발명되고 아라비아 숫자와 알파벳, 한글, 한자, 인도어 등등이 발달하기 시작하면서 문서를 남길 수 있게 되었어요!!! 문서를 남긴다는 건 굉장했죠!!!오랜 시간이 지나도 기록들을 잘 보관할 수 있었어요!! 거래를 할 때에도 수입과 지출을 한 번에 (가계부처럼) 적으면서 작은 종이에 많은 내용을 남길 수 있었답니다. 하지만..여전히 문제는 사람이었어요. 이를 위조하거나 없애버리면...? 또는 불에 다 타서 없어지면??4. 얼마 전 = 적은 걸 나눠가지기(복식부기)그래서 서로 함께 같은 내용을 공유하기로 했어요. 너 하나 나 하나. 그리고 그 과정을 감시하는 회계사. 이런 과정은 우리 조선시대에서도 아주 엄격했답니다. 특히 계문화가 발달했던 우리나라는 다양한 장부를 기록했는데 '용하기'라는 계의 장부기재는 정말 엄격한 원칙이 있었답니다!!1. 임시장부를 2부 작성해요. 이 때 회계담당자 이외 심지어 2명이 더 감시하고 있어요.2. 기재를 시작해요.3. 계원들이 다 모여야 하고 적은 내용을 크게 읽어요. 이 때 의심스러운게 있으면 이의제기나 수정을 해요.4. 계장과 두 명의 감시원이 있는 상태에서 최종수정해요. 그리고 계장이 서명해요.5. 중복된 장부가 있는지 확인하고 새 장부를 넣어 보관해요.엄청나죠???..놀라운 건 현재의 블록체인의 원리도 위와 비슷해요!! 다만 사람이 일일이 적고 감시하는 게 아니라 명령어에 의해 챡챡 처리되는 것 뿐이랄까요. 하지만 이것도 결국 '물질' 이다 보니....화재나 전쟁으로 인해 소실되어 버리면 그걸로 끝이었어요.5. 요즘 = 기관이나 중앙에 맡기기왕정체제가 아니라 민주주의와 시장경제가 도입되면서 은행이나 보험사, 카드사와 같이 경제활동을 담당하는 기업과 중앙기관이 생겨나기 시작했어요! 엄청나게 거대한 정보를 크으으은 서버나 금고에 보관할 수 있었어요. 그것은 영원해보이고 사람들은 오래도록 보관할 수 있다고 생각하니 관심을 끄기 시작했죠. 내 돈은 금고에 잘 있을 거니까요.하지만, 자본주의는 그런게 아니었어요. 은행은 내 돈을 다른 사람에게 대출로 빌려주고 그 이자로 돈을 벌어요. 그리고 다른 사람이 갚은 돈으로 다시 내 예금을 채우죠. 졸라 돌려막기인 거에요. 사람들이 끊임없이 돈을 빌리고 다시 갚을 수 있게 다양한 상품들을 만들어요. 이 방식은 굉장히 효율적이고 아무 문제가 없을 것 같이 보였어요.하지만, 해킹을 당했어요.은행을 털렸어요서브프라임 모지기론 사태처럼, 무리한 상품의 실패는 수백개의 기업을 무너뜨렸어요. 수많은 사람들의 돈이 한 순간에 날아갔어요.서버가 먹통이 되어 거래가 안되는 경우도 있어요.지진 등의 천재지변이 나면 내 기록은 사라지고 말아요.단순히 큰 사옥을 지닌 곳이니까 영원불멸할 것 같았던 중앙기관도 하루 아침에 무너질 수 있단 사실을 우린 수 차례 경험했어요. 그럼에도 우린 뭘 어떻게 해야할 지 몰랐어요. 우리가 할 수 있는 건 사고가 터지면 변호사를 써서 소송을 하는 것 뿐이었어요. 우린 은행의 상품이 정확히 어떤건지, 보험약관이 뭔지... 카드사는 어떤 원리로 움직이는지...내 세금은 어떻게 쓰이고 있는지...우리 돈이 어떻게 거래되고 내 돈을 가지고 그들이 무엇을 하는지 하나도 몰라요. 그냥 속수무책으로 그들만 믿고 있는 거예요. 6. 블록체인의 탄생 = 모두가 장부를 가질 수 있게그래서 생각해봤어요. 한 곳에 모여있으니 문제가 생긴다면, 쪼개면 되지 않을까? 은행 한 곳을 터는 것은 쉽지만 1,000여명을 한꺼번에 터는 것은 불가능할테니까. 계모임에서 쓰던 그 장부를 엄청나게 많이 만들어서 모두가 가지면 어떨까? 누굴 못 믿거나 위조하거나 털리거나 불나서 사라질 일이 없을 거 아냐?? 라는 생각을 말이죠. 그런데 친구가 질문을 하네요!!친구 : 그런데 어떻게??나 : 인터넷이 있잖아!! 내가 온라인상에서 거래하면 그 기록이 남잖아~ 그걸 모두가 공유하는거지! 친구 : 모두가 누군데?나 : 응 그건 이제부터 모아야해!!친구 : 그럼 어쨌든 모인 사람들에게 모두 공유하면 내가 어제 김치한포기 시킨것도 다른 사람이 알게 되는거야??나 : 아니지;;; 니가 뭘 시켰는지 그딴 건 관심없어..그냥 얼마 거래를 언제 몇시몇분몇초에 어떻게 했는가만 기록에 남는거야! 그리고 다른 사람은 그걸 직접 눈으로 볼 수 있는 게 아냐.생각해봐. 넌 브런치 로그인한 기록을 눈으로 다 볼 수 있어? 며칠 몇시에 얼마나 로그인했는지 알 방법이 없지? 하지만 그 기록이 있을까 없을까? 그렇지, 반드시 있다구. 범죄수사할때도 그러자나. 우리 화면에는 시간/내용밖엔 안뜨는 문자메시지지만, 실제로 서버에는 발신위치, 수신위치, 번호정보 등등이 모두 숨겨져 있잖아. 또 하나! 너가 네이버에서 틴트를 검색하면 나중에 페북에서 틴트광고가 뜨지 않아? 우리의 방문기록이나 클릭한 기록들이 모두 남아있기 때문이야.이렇게 우리가 눈으로 보는 화면 뒤에는 수많은 정보들이 컴퓨터만의 전기신호로 저장되어 있어. 우리가 말하는 장부도 이런 식으로 저장되어 있는거라구.  물론 필요하다면 그걸 화면으로 띄울 수 있는 명령어를 만들 수도 있겠지.친구 : 그건 이해했어, 내가 직접 볼 순 없지만 마치 사이트 방문기록처럼 어딘가에 거래내역이 다 남아있다는 얘기지?... 그런데 아까 지금부터 모아야 한다는 사람들은 어떻게 모으는거야??나 : 그건!!..바로!!!! 다음에 설명해줄께!!또 공부해서 돌아올께용!!
조회수 1972

Good Developer 4 | 학습하는 개발자 -고농축 학습 자료 꿀팁

더 이상의 설명은 필요 없다.지금까지 우리는 Good Developer 시리즈는 커뮤니케이션과 나쁜 개발자의 습관을 통해 좋은 개발자가 무엇인지 알아보았다. 이번에는 좋은 개발자가 되기 위한 가장 중요한 조건 바로 학습하는 개발자에 대해 알아볼 것이다.개발자가 새로운 것을 익히고 배우는 것은 너무도 당연하다. 이것에 대해 글을 쓰는 것은 의미가 없는 것 같아서 많은 고민을 했다. 그래서 실질적으로 학습에 도움을 줄 수 있는 아주 고농축 꿀팁들을 주면 좋은 개발자가 되는데 도움이 되지 않을까 생각했다. 이번 편은 학습하는 개발자 - 고농축 학습 꿀팁 편이다.학습은 천천히, 그러나 꾸준히너무나 당연한 말을 한 번 더 하고 시작할까 한다. 개발뿐만 아니라 모든 학습이 마찬가지겠지만, 꾸준히 학습해야 하는 개발자에게 중요한 것은 학습 습관이다. 이것저것 깨작깨작 찔러보고 공부하는 깊이로는 새로운 기술들을 자신의 것으로 만들 수 없으며 오히려 시간을 낭비하는 것일 수도 있다. 하나의 기술을 배우기 시작했으면 서두르지 말고 천천히 음미하면서 학습해야 한다. 그 대신 한두 달 공부하고 끝내는 것이 아니라 충분한 깊이를 가질 때까지 꾸준히 학습하라!직장을 다녀본 사람은 알 것이다. 직장을 다니면서 따로 자기개발을 하고 학습을 하는 것이 쉽지 않다는 것을 말이다. 하지만 좋은 개발자, 더 나은 개발자가 되기 위해서라면 학습을 멈춰 서는 안된다. 그것이 개발자의 숙명이다. 그래서 개발은 정말 개발을 좋아하는 사람만이 할 수 있는 직업인 것 같다. 혹시 자신이 학습을 하는데 있어 자꾸 포기하게 되고 중단하게 된다면 이전에 썼던 '글로 배우는 코딩 1 | 포기하지 않고 끝까지 공부하는 법'편을 참고해보길 바란다.아래의 학습 정보들은 많이 알 수도 있는 정보지만, 개발자가 되려는 사람들, 잘 모르는 사람들에게는 분명히 좋은 정보가 되리라 생각한다. 알고리즘 사이트 Top 31. 백준 온라인 저지백준 저지는 1만 개 이상의 알고리즘 문제를 보유한 사이트다. 타 사이트에 비해 홈페이지 구성도 잘 되어 있고 문제도 잘 나누어져 있다.그리고 사람들이 문제들을 골라서 자신만의 문제집을 만들어서 공유하기도 한다. 또한 알고리즘 지원 언어도 60개 이상이기 때문에 어떤 언어를 공부하든 웬만해서는 문제없이 풀 수 있다.(이런 언어도 있나 싶을 정도로 많은 언어들의 채점을 지원하고 있다.)기회가 되면 언어들을 직접 세어보는 것도......Baekjoon Online JudgeBaekjoon Online Judge 프로그래밍 문제를 풀고 온라인으로 채점받을 수 있는 곳입니다. 14264 전체 문제 11797 채점 가능한 문제 9316 풀린 문제 64 채점 가능한 언어www.acmicpc.net2. 코드워즈(codewars)코드워즈는 게임 형식의 알고리즘 학습 사이트다. 약 20여 개의 언어를 지원하며, C, C++ C#, Go PHP, JAVA, Python 등 주요 언어들은 모두 지원한다. UI/UX적으로도 굉장히 구성이 잘 되어 있고 인터페이스만 익숙해지면 정말 좋은 코딩 학습 사이트다.영어 사이트이긴 하지만 어느 정도의 독해 수준이면 충분히 학습할 수 있다. 게임 형식으로 알고리즘을 풀기 때문에 정말 재미있게 알고리즘을 학습할 수 있는 사이트! 알고리즘 사이트 중 가장 추천하는 사이트다. 태그도 잘 되어 있어서 function, array, data types 별로 자신이 약한 부분을 집중적으로 학습할 수도 있다.Codewars: Train your coding skillsCodewars is where developers achieve code mastery through challenge. Train on kata in the dojo and reach your highest potential.www.codewars.com3. 프로그래머스프로그래머스는 단계적으로 알고리즘 문제를 풀어볼 수 있는데 최적화된 사이트다. 레벨 1부터 레벨 8까지 정리된 프로그래밍 알고리즘을 풀 수 있다. 지원되는 언어가 C++ 자바 파이썬 자바스크립트로 가장 많이 쓰는 언어만 지원한다는 단점이 있다.모든 문제가 한글이라서 영어가 부담되시는 분들에게는 체계적으로 부담 없이 할 수 있다. 문제를 풀고 제출하는 환경도 잘 구성되어 있어 편리성이 좋은 알고리즘 학습 사이트다. 다만, 다른 사이트 들에 비해 문제의 수가 적다는 점! 영어가 부담되고 단계별로 알고리즘 문제를 풀고 싶다면 이 사이트를 추천한다.프로그래머스동영상과 실습으로 구성된 최고의 프로그래밍 강좌를 만나세요. 프로그래머스에서는 프로그래밍 강좌, 알고리즘 문제, 프로그래밍 대회, 블록체인 자료를 만날 수 있습니다.programmers.co.kr코딩 학습 사이트 Top 51. 유데미(Udemy)엄청나게 질 좋은 강의를 엄청나게 저렴한 가격으로 이용할 수 있는 곳! 1만 원대의 강좌에서 이 정도 퀄리티의 학습 콘텐츠를 얻기는 유데미 외에서는 불가능할 것이다.(광고 글이 아니다 정말이다.) 강의의 분야와 주제도 많고(개발 외에도 여러 가지가 있다) 짧게 짧게, 5~7시간 커리큘럼의 강의들이 많아서 부담 없이 학습할 수 있는 사이트. 강의 수준도 초급부터 고급까지 다양해서 수준 있는 개발자들도 들을 강의가 많다.대부분이 영어 강의이기는 하지만 요즘 한국 강사들의 유입도 늘어서 한국 강의도 늘고 있는 추세다. 영어 자막도 제공하니 영어를 읽을 수만 있다면 강력 추천하는 학습 사이트다.글을 클릭하면 유데미 사이트로 이동합니다.2. 코드카데미(codecademy)체계적으로 코딩을 배우고 싶은데 무료로 배우고 싶다면?! 바로 코드카데미다. 동영상은 보기 귀찮고 읽으면서 단계적으로 코딩을 배우고 싶다면 코드카데미가 적격이다. 자바스크립트를 주축으로 하는 개발 도구 위주만 배울 수 있다는 단점이 있지만, 코딩을 직접 하면서 배울 수 있다는 큰 장점이 있기 때문에 동영상 강의만 보고 그냥 넘길 수 있는 다른 사이트와는 다르게 바로바로 코딩을 쓰면서 배울 수 있다. 역시 영어 학습 사이트지만 개발자가 되기 위해서 필요한 영어 실력만 가지고 있어도 충분히 학습해 나갈 수 있다.Codecademy - learn to code, interactively, for freeCodecademy is the world's most popular way to learn over 12 coding languages including HTML, CSS, JavaScript, Python, SQL, and Ruby. Sign up today and start learning to code in minutes.www.codecademy.com3. 코드스테이츠한국 최초의 코딩 부트 캠프 코드스테이츠. 코드스테이츠 입장에서 코드스테이츠를 추천하는 것이 민망해해 보일 수 있어도, 그만큼 자부심이 있다. 온/오프라인 교육이기 때문에 다른 온라인 교육 사이트보다 저렴하지는 않지만, 개발자를 꿈꾼다면 일정 금액을 투자하고 개발자가 확실히 될 수 있다는 장점이 있다.  온/오프라인에서 직접 멘토링을 받아 가면서 학습을 하고 싶다면 코드스테이츠를 강력 추천한다.온라인 학습과 오프라인 코칭으로 온라인 콘텐츠와 오프라인 교육을 둘 다 가져갈 수 있다는 장점이 있다. 코스 중간에 미니 해커톤과 실제 기업과 협업 프로젝트를 진행해 볼 수 있다는 것도 큰 장점! 단점은 다른 온라인 학습 사이트에 비해서는 가격이 어느 정도 있다.코드스테이츠 | 혁신적인 코딩 교육 부트캠프코드스테이츠(Code States)는 프로그래밍을 배우고 싶은 사람들을 위한 최상의 코딩 교육 프로그램을 제공합니다. 자바스크립트 HTML CSS를 기초로 탄탄한 이론과 실무에 최적화된 기술 스택들을 학습합니다. 주입식이 아닌 자기주도적 학습 방식으로 기존과는 차별화된 혁신적인 교육 시스템을 경험해보세요.goo.gl4. 유다시티유다시티는 가격대는 있지만 탄탄하고 검증된 커리큘럼의 온라인 학습 사이트다. 프로젝트 베이스에 과제도 탄탄하고 동영상 학습 중간중간 텍스트 자료와 퀴즈까지 적절하게 섞여 있어서 충분히 제값을 한다. 다른 온라인 학습 사이트에 비해 가격대가 있지만 그만큼 퀄리티는 훌륭하다. 가장 핫한 트렌드의 기술들도 배울 수 있고 난이도도 초급부터 고급까지 다양한 과정들이 있다.유다시티에서는 학습하기가 굉장히 편하다. 학습 시간을 적절히 쪼개서 부담 없이 학습이 가능하고 자료 또한 탄탄하다는 것이 장점! 하지만 역시 온라인 학습치고 가격은 부담이 된다.Udacity - Free Online Classes & Nanodegrees | UdacityJoin Udacity to learn the latest in Deep Learning, Machine Learning, Web Development & more, with Nanodegree programs & free online courses.www.udacity.com5. 인프런영어가 유데미가 있다면 한국어는 인프런이 있다! 다양한 수준의 프로그래밍 강의를 한국어로 들을 수 있는 온라인 학습 사이트다. 탄탄한 커리큘럼에 강좌 구성까지. 필요한 강의를 골라 들을 수 있다는 장점이 있다. 한국어 강좌다 보니 강사들과의 소통도 원활하다. 영어가 아직은 부담스럽다면 인프런에서 먼저 시작해보자!퀄리티 높은 무료 강좌도 존재하니 처음에는 무료 강좌들을 보면서 나에게 맞는지 확인해 보고 학습을 시작하면 된다. 단점은 유데미 보다는 가격이 비싸다는 점! 하지만 한국어 강의가 많다는 것 자체가 엄청난 장점이라 할 수 있겠다.인프런 - IT, 개발의 좋은 지식을 공유합니다개발, CG, 디자인 등 IT 분야의 고급 지식들을 편하고 경제적으로 학습할수 있는 공간입니다. 배우는 사람에겐 기회를, 지식공유자에겐 보상을 주는 문화를 만들어요.www.inflearn.com코딩 관련 질문을 하고 싶다면스택오버플로우(stackoverflow)스택오버플로우는 개발과 관련된 질문과 답변을 하는 사이트다. 코딩을 하다가 중간에 막혔는가? 괜찮다. 당신의 문제는 이미 선배 개발자들도 했던 고민이니 말이다. 스택오버플로우에서 how to 라는 말과 함께 당신이 궁금한 점을 물어보라 마법과 같은 일이 펼쳐질 것이다. 당신이 알고 싶어 하는 거의 모든 개발 관련된 문제들에 대한 답이 이곳에 있다.영어라서 부담스러워하지 말고 익숙해져보라. 스택오버플로우만 잘 이용해도 현재 당신이 안고 있는 개발 문제의 대부분이 해결될 것이다. 단, 이곳에서의 코드를 너무 복붙 했다가는 오히려 실력 저하가 온다는 것을 명심하기를...Stack Overflow - Where Developers Learn, Share, & Build CareersStack Overflow | The World’s Largest Online Community for Developersstackoverflow.com블로그&커뮤니티JS서울js서울은 자바스크립트에 대해 넓고 얕은 지식을 서울 사용자들에게 보급하려는 지역기반 커뮤니티다. 슬랙방을 만들어서 운영되고 있으며 자바스크립트를 이용하는 이용자라면 활동을 해보면 좋을 것이다.seoul.jsMeetups 2017.08.18(1st) Meeting Notes 2017.07.10 2017.07.19(kickoff) 2017.10.11(conference Staff Offline Meeting 1st) 2017.10.31(conference Staff Online Meeting 1) Seoul.js About Code Of Conduct Sponsors Why We Started Seoul.js Logo Call For Speaker Plan 2018 이제 폭넓게 사용되는 자바스크립트의 매력과 인사이트를 대한민국, 서울에seoul.js.orgOkky개발자들의 커뮤니티, 페이스북이 아니라 다른 페이지를 만들어서 활동하고 있는 개발 커뮤니티 중 가장 큰 규모가 아닌가 생각한다. 개발 관련된 질문도 하고 개발자와 관련된 생활, 진로, 일상들을 이야기하는 개발자 커뮤니티다. 질문을 올리면 선배 개발자들의 따끔한 조언을 얻을 수 있다.OKKY - All That DeveloperEditor's Choice 실리콘밸리를 그리다 - 24. 애자일 방법론으로 프로젝트 진행하기 Karen 10k 6일 전 [OKKY 세미나] 대용량 서비스 성능 개선 노하우 Karen 10k 6일 전 'IT업계 포괄임금제 미적용 특례지정'을 요청합니다. Good Luck 484 8일 전 [OKKY 취준 세미나] 국비 지원 학원 선택의 노하우와 효과적 학습법에 대하여 형 439 13일 전 OKKY 스팸 단어로 인한 글 등록 불가 문제 관련 공지사항 OKKY 475 29일 전 Q&A 자동로그인 코드 구현 할okky.kr조대협님 블로그이미 알만한 사람은 다 안다는 조대협님의 블로그. 개발자 블로깅은 이렇게 하느거야라는 정수를 느낄 수 있고 실제 유익한 정보들이 많이 올라온다. 유명한 개발자 블로거들이 많지만 나열하자면 지면이 길어지기에 대표적인 조대협님의 블로그를 추천한다. 개발 관련된 글, 정보를 얻고 싶다면 이곳에 들어가 보라!조대협의 블로그평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴.bcho.tistory.com
조회수 766

비트윈의 HBase 스키마 해부 - VCNC Engineering Blog

비트윈에서는 HBase를 메인 데이터베이스로 이용하고 있습니다. 유저 및 커플에 대한 정보와 커플들이 주고받은 메시지, 업로드한 사진 정보, 메모, 기념일, 캘린더 등 서비스에서 만들어지는 다양한 데이터를 HBase에 저장합니다. HBase는 일반적인 NoSQL과 마찬가지로 스키마를 미리 정의하지 않습니다. 대신 주어진 API를 이용해 데이터를 넣기만 하면 그대로 저장되는 성질을 가지고 있습니다. 이런 점은 데이터의 구조가 바뀔 때 별다른 스키마 변경이 필요 없다는 등의 장점으로 설명되곤 하지만, 개발을 쉽게 하기 위해서는 데이터를 저장하는데 어느 정도의 규칙이 필요합니다. 이 글에서는 비트윈이 데이터를 어떤 구조로 HBase에 저장하고 있는지에 대해서 이야기해 보고자 합니다.비트윈에서 HBase에 데이터를 저장하는 방법Thrift를 이용해 데이터 저장: Apache Thrift는 자체적으로 정의된 문법을 통해 데이터 구조를 정의하고 이를 직렬화/역직렬화 시킬 수 있는 기능을 제공합니다. 비트윈에서는 서버와 클라이언트가 통신하기 위해 Thrift를 이용할 뿐만 아니라 HBase에 저장할 데이터를 정의하고 데이터 저장 시 직렬화를 위해 Thrift를 이용합니다.하나의 Row에 여러 Column을 트리 형태로 저장: HBase는 Column-Oriented NoSQL로 분류되며 하나의 Row에 많은 수의 Column을 저장할 수 있습니다. 비트윈에서는 Column Qualifier를 잘 정의하여 한 Row에 여러 Column을 논리적으로 트리 형태로 저장하고 있습니다.추상화된 라이브러리를 통해 데이터에 접근: 비트윈에서는 HBase 클라이언트 라이브러리를 직접 사용하는 것이 아니라 이를 래핑한 Datastore라는 라이브러리를 구현하여 이를 이용해 HBase의 데이터에 접근합니다. GAE의 Datastore와 인터페이스가 유사하며 실제 저장된 데이터들을 부모-자식 관계로 접근할 수 있게 해줍니다.트랜잭션을 걸고 데이터에 접근: HBase는 일반적인 NoSQL과 마찬가지로 트랜잭션을 제공하지 않지만 비트윈에서는 자체적으로 제작한 트랜잭션 라이브러리인 Haeinsa를 이용하여 Multi-Row ACID 트랜잭션을 걸고 있습니다. Haeinsa 덕분에 성능 하락 없이도 데이터 무결성을 유지하고 있습니다.Secondary Index를 직접 구현: HBase에서는 데이터를 Row Key와 Column Qualifier를 사전식 순서(lexicographical order)로 정렬하여 저장하며 정렬 순서대로 Scan을 하거나 바로 임의 접근할 수 있습니다. 하지만 비트윈의 어떤 데이터들은 하나의 Key로 정렬되는 것으로는 충분하지 않고 Secondary Index가 필요한 경우가 있는데, HBase는 이런 기능을 제공하지 않고 있습니다. 비트윈에서는 Datastore 라이브러리에 구현한 Trigger을 이용하여 매우 간단한 형태의 Secondary Index를 만들었습니다.비트윈 HBase 데이터 구조 해부페이스북의 메시징 시스템에 관해 소개된 글이나, GAE의 Datastore에 저장되는 구조를 설명한 글을 통해 HBase에 어떤 구조로 데이터를 저장할지 아이디어를 얻을 수 있습니다. 비트윈에서는 이 글과는 약간 다른 방법으로 HBase에 데이터를 저장합니다. 이에 대해 자세히 알아보겠습니다.전반적인 구조비트윈에서는 데이터를 종류별로 테이블에 나누어 저장하고 있습니다. 커플과 관련된 정보는 커플 테이블에, 유저에 대한 정보는 유저 테이블에 나누어 저장합니다.각 객체와 관련된 정보는 각각의 HBase 테이블에 저장됩니다.또한, 관련된 데이터를 하나의 Row에 모아 저장합니다. 특정 커플과 관련된 사진, 메모, 사진과 메모에 달린 댓글, 기념일 등의 데이터는 해당 커플과 관련된 하나의 Row에 저장됩니다. Haeinsa를 위한 Lock Column Family를 제외하면, 데이터를 저장하기 위한 용도로는 단 하나의 Column Family만 만들어 사용하고 있습니다.각 객체의 정보와 자식 객체들은 같은 Row에 저장됩니다.또한, 데이터는 기본적으로 하나의 Column Family에 저장됩니다.이렇게 한 테이블에 같은 종류의 데이터를 모아 저장하게 되면 Region Split하는 것이 쉬워집니다. HBase는 특정 테이블을 연속된 Row들의 집합인 Region으로 나누고 이 Region들을 여러 Region 서버에 할당하는 방식으로 부하를 분산합니다. 테이블을 Region으로 나눌 때 각 Region이 받는 부하를 고려해야 하므로 각 Row가 받는 부하가 전체적으로 공평해야 Region Split 정책을 세우기가 쉽습니다. 비트윈의 경우 커플과 관련된 데이터인 사진이나 메모를 올리는 것보다는 유저와 관련된 데이터인 메시지를 추가하는 트래픽이 훨씬 많은데, 한 테이블에 커플 Row와 유저 Row가 섞여 있다면 각 Row가 받는 부하가 천차만별이 되어 Region Split 정책을 세우기가 복잡해집니다. RegionSplitPolicy를 구현하여 Region Split 정책을 잘 정의한다면 가능은 하지만 좀 더 쉬운 방법을 택했습니다.또한, 한 Row에 관련된 정보를 모아서 저장하면 성능상 이점이 있습니다. 기본적으로 한 커플에 대한 데이터들은 하나의 클라이언트 요청을 처리하는 동안 함께 접근되는 경우가 많습니다. HBase는 같은 Row에 대한 연산을 묶어 한 번에 실행시킬 수 있으므로 이 점을 잘 이용하면 성능상 이득을 얻을 수 있습니다. 비트윈의 데이터 구조처럼 특정 Row에 수많은 Column이 저장되고 같은 Row의 Column들에 함께 접근하는 경우가 많도록 설계되어 있다면 성능 향상을 기대할 수 있습니다. 특히 Haeinsa는 한 트랜잭션에 같은 Row에 대한 연산은 커밋시 한 번의 RPC로 묶어 처리하므로 RPC에 드는 비용을 최소화합니다. 실제 비트윈에서 가장 많이 일어나는 연산인 메시지 추가 연산은 그냥 HBase API를 이용하여 구현하는 것보다 Haeinsa Transaction API를 이용해 구현하는 것이 오히려 성능이 좋습니다.Column Qualifier의 구조비트윈은 커플들이 올린 사진 정보들을 저장하며, 또 사진들에 달리는 댓글 정보들도 저장합니다. 한 커플을 Root라고 생각하고 커플 밑에 달린 사진들을 커플의 자식 데이터, 또 사진 밑에 달린 댓글들을 사진의 자식 데이터라고 생각한다면, 비트윈의 데이터들을 논리적으로 트리 형태로 생각할 수 있습니다. 비트윈 개발팀은 Column Qualifier를 잘 정의하여 실제로 HBase에 저장할 때에도 데이터가 트리 형태로 저장되도록 설계하였습니다. 이렇게 트리 형태로 저장하기 위한 Key구조에 대해 자세히 알아보겠습니다.Column Qualifier를 설계할 때 성능을 위해 몇 가지 사항들을 고려해야 합니다. HBase에서는 한 Row에 여러 Column이 들어갈 수 있으며 Column들은 Column Qualifier로 정렬되어 저장됩니다. ColumnRangeFilter를 이용하면 Column에 대해 정렬 순서로 Scan연산이 가능합니다. 이 때 원하는 데이터를 순서대로 읽어야 하는 경우가 있는데 이를 위해 Scan시, 최대한 Sequential Read를 할 수 있도록 설계해야 합니다. 또한, HBase에서 데이터를 읽어올 때, 실제로 데이터를 읽어오는 단위인 Block에 대해 캐시를 하는데 이를 Block Cache라고 합니다. 실제로 같이 접근하는 경우가 빈번한 데이터들이 최대한 근접한 곳에 저장되도록 설계해야 Block Cache의 도움을 받을 수 있습니다.비트윈에서는 특정 커플의 사진이나 이벤트를 가져오는 등의 특정 타입으로 자식 데이터를 Scan해야하는 경우가 많습니다. 따라서 특정 타입의 데이터를 연속하게 저장하여 최대한 Sequential Read가 일어나도록 해야 합니다. 이 때문에 Column Qualifier가 가리키는 데이터의 타입을 맨 앞에 배치하여 같은 타입의 자식 데이터들끼리 연속하여 저장되도록 하였습니다. 만약 가리키는 데이터의 타입과 아이디가 Parent 정보 이후에 붙게 되면 사진 사이사이에 각 사진의 댓글 데이터가 끼어 저장됩니다. 이렇게 되면 사진들에 대한 데이터를 Scan시, 중간중간 저장된 댓글 데이터들 때문에 완벽한 Sequential Read가 일어나지 않게 되어 비효율적입니다.이렇게 특정 타입의 자식들을 연속하게 모아 저장하는 묶음을 컬렉션이라고 합니다. 컬렉션에는 컬렉션에 저장된 자식들의 개수나 새로운 자식을 추가할 때 발급할 아이디 등을 저장하는 Metadata가 있습니다. 이 Metadata도 특정 Column에 저장되므로 Metadata를 위한 Column Qualifier가 존재합니다. 이를 위해 Column Qualifier에는 Column Qualifier가 자칭하는 데이터가 Metadata인지 표현하는 필드가 있는데, 특이하게도 메타데이터임을 나타내는 값이 1이 아니라 0입니다. 이는 Metadata가 컬렉션의 맨 앞쪽에 위치하도록 하기 위함입니다. 컬렉션을 읽을 때 보통 맨 앞에서부터 읽는 경우가 많고, 동시에 Metadata에도 접근하는 경우가 많은데, 이 데이터가 인접하게 저장되어 있도록 하여 Block Cache 적중이 최대한 일어나도록 한 것입니다.Datastore 인터페이스비트윈에서는 이와 같은 데이터 구조에 접근하기 위해 Datastore라는 라이브러리를 구현하여 이를 이용하고 있습니다. HBase API를 그대로 이용하는 것보다 좀 더 쉽게 데이터에 접근할 수 있습니다. GAE의 Datastore와 같은 이름인데, 실제 인터페이스도 매우 유사합니다. 이 라이브러리의 인터페이스에 대해 간단히 알아보겠습니다.Key는 Datastore에서 HBase에 저장된 특정 데이터를 지칭하기 위한 클래스입니다. 논리적으로 트리 형태로 저장된 데이터 구조를 위해 부모 자식 관계를 이용하여 만들어 집니다.Key parentKey = new Key(MType.T_RELATIONSHIP, relId); Key photoKey = new Key(parentKey, MType.T_PHOTO, photoId); // 특정 커플 밑에 달린 사진에 대한 키 Datastore는 Key를 이용해 Row Key와 Column Qualifier를 만들어 낼 수 있습니다. Datastore는 이 정보를 바탕으로 HBase에 새로운 데이터를 저장하거나 저장된 데이터에 접근할 수 있는 메서드를 제공합니다. 아래 코드에서 MUser 클래스는 Thrift로 정의하여 자동 생성된 클래스이며, Datastore에서는 이 객체를 직렬화 하여 HBase에 저장합니다.MUser user = new MUser(); user.setNickname("Alice"); user.setGender(Gender.FEMALE); user.setStatus("Hello World!"); Key userKey = new Key(MType.T_USER, userId); getDatastore().put(userKey, user); user = getDatastore().get(userKey); getDatastore().delete(userKey); 또한, Datastore는 Key를 범위로 하여 Scan연산이 할 수 있도록 인터페이스를 제공합니다. Java에서 제공하는 Try-with-resource문을 이용하여 ResultScanner를 반드시 닫을 수 있도록 하고 있습니다. 내부적으로 일단 특정 크기만큼 배치로 가져오고 더 필요한 경우 더 가져오는 식으로 구현되어 있습니다.try (CloseableIterable> entries = getDatastore().subSibling(fromKey, fromInclusive, toKey, toInclusive)) { for (KeyValue entry : entries) { // do something } } Secondary Index 구현 방법HBase는 데이터를 Row Key나 Column Qualifier로 정렬하여 저장합니다. 이 순서로만 Sequential Read를 할 수 있으며 Key값을 통해 특정 데이터를 바로 임의 접근할 수 있습니다. 비트윈에서는 특정 달에 해당하는 이벤트들을 읽어오거나 특정 날짜의 사진들의 리스트를 조회하는 등 id 순서가 아니라 특정 값을 가지는 데이터를 순서대로 접근해야 하는 경우가 있습니다. 이럴 때에도 효율적으로 데이터에 접근하기 위해서는 id로 정렬된 것 외에 특정 값으로 데이터를 정렬할 수 있어야 합니다. 하지만 HBase에서는 이와 같은 Secondary Index 같은 기능을 제공하지 않습니다. 비트윈 개발팀은 이에 굴하지 않고 Secondary Index를 간단한 방법으로 구현하여 사용하고 있습니다.구현을 간단히 하기 위해 Secondary Index를 다른 데이터들과 마찬가지로 특정 타입의 데이터로 취급하여 구현하였습니다. 따라서 Index에 대해서도 Column Qualifier가 발급되며, 이때, Index에 해당하는 id를 잘 정의하여 원하는 순서의 Index를 만듭니다. 이런 식으로 원하는 순서로 데이터를 정렬하여 저장할 수 있으며 이 인덱스를 통해 특정 필드의 값의 순서대로 데이터를 조회하거나 특정 값을 가지는 데이터에 바로 임의 접근할 수 있습니다. 또한, Index에 실제 데이터를 그대로 복사하여 저장하여 Clustered Index처럼 동작하도록 하거나, Reference만 저장하여 Non-Clustered Index와 같이 동작하게 할 수도 있습니다. Datastore 라이브러리에는 특정 데이터가 추가, 삭제, 수정할 때 특정 코드를 실행할 수 있도록 Trigger 기능이 구현되어 있는데, 이를 통해 Index를 업데이트합니다. 데이터의 변경하는 연산과 Index를 업데이트하는 연산이 하나의 Haeinsa 트랜잭션을 통해 원자적으로 일어나므로 데이터의 무결성이 보장됩니다.못다 한 이야기각 테이블의 특정 Row의 Column들에 대한 Column Qualifier외에도 Row에 대한 Row Key를 정의 해야 합니다. 비트윈에서는 각 Row가 표현하는 Root객체에 대한 아이디를 그대로 Row Key로 이용합니다. 새로운 Root객체가 추가될 때 발급되는 아이디는 랜덤하게 생성하여 객체가 여러 Region 서버에 잘 분산될 수 있도록 하였습니다. 만약 Row Key를 연속하게 발급한다면 특정 Region 서버로 연산이 몰리게 되어 성능 확장에 어려움이 생길 수 있습니다.데이터를 저장할 때 Thrift를 이용하고 있는데, Thrift 때문에 생기는 문제가 있습니다. 비트윈에서 서버를 업데이트할 때 서비스 중지 시간을 최소화하기 위해 롤링 업데이트를 합니다. Thrift 객체에 새로운 필드가 생기는 경우, 롤링 업데이트 중간에는 일부 서버에만 새로운 Thift가 적용되어 있을 수 있습니다. 업데이트된 서버가 새로운 필드에 값을 넣어 저장했는데, 아직 업데이트가 안 된 서버가 이 데이터를 읽은 후 데이터를 다시 저장한다면 새로운 필드에 저장된 값이 사라지게 됩니다. Google Protocol Buffer의 경우, 다시 직렬화 할 때 정의되지 않은 필드도 처리해주기 때문에 문제가 없지만, Thrift의 경우에는 그렇지 않습니다. 비트윈에서는 새로운 Thrift를 적용한 과거 버전의 서버를 먼저 배포한 후, 업데이트된 서버를 다시 롤링 업데이트를 하는 식으로 이 문제를 해결하고 있습니다.
조회수 4600

AWS Instance Scheduler Bot 적용기

이 포스팅은 총 2부로 이어지며 현재는 2부입니다.1부 : AWS 비용 얼마까지 줄여봤니?2부 : AWS Instance Scheduler Bot 적용기1부에서 AWS 비용을 절감하기 위한 Instance Scheduler에 대한 소개를 하였습니다. 2부에서는 Instance Scheduler의 설정을 손쉽게 변경하기 위한 Bot을 적용한 사례에 대해서 소개합니다.Bot의 필요성Instance Scheduler의 설정을 변경하기 위해서는 정보를 담고 있는 Dynamo DB 의 데이터를 변경해야 합니다. AWS Console을 이용하여 직접 수정할 수도 있지만 여전히 불편하고 느립니다. 더군다나 이를 이용하는 사용자가 DB Table의 구조와 AWS Console 사용법을 알고 있어야 하고 비 개발자라면 더 쉽지 않은 문제입니다. 하지만 Bot을 이용하면 사용자는 어려운 DB Query나 구조를 알아야 할 필요도 없고 손쉽게 채팅 메시지를 통해 Bot에게 질의하고 처리 결과를 응답받을 수 있습니다.Outgoing WebhookJANDI에서는 Incoming Webhook과 반대되는 개념으로 Outgoing Webhook을 제공합니다. 특정 키워드로 시작하는 메시지가 있을 경우 내용을 설정된 URL Endpoint에 POST로 Webhook을 보내줍니다. Webhook을 수신한 곳에서는 일련의 처리 후 메시지 데이터 형식을 맞춰 응답하게 되면 채팅창에 메시지를 표시하게 됩니다. 이를 통해 다른 외부 시스템과 연동할 수 있습니다.POST Data예를 들어 날씨 키워드로 Outgoing Webhook을 생성했다면 /날씨 메시지가 시작될 때 다음과 같은 데이터가 Webhook으로 발송됩니다.{ "token": "YE1ronbbuoZkq7h3J5KMI4Tn", "teamName": "Toss Lab, Inc.", "roomName": "토스랩 코리아", "writerName": "Gloria", "text": "/날씨 서울", "keyword": "날씨", "createdAt": "2017-07-19T14:49:11.266Z" } token을 이용하여 요청의 유효성 체크를 할 수 있고 text를 적절히 파싱 하여 요청에 부합하는 처리를 할 수 있습니다.ResponsePOST Data를 적절히 처리 후 결과를 채팅창에 응답 메시지를 표시하고 싶다면 아래와 같은 JSON Data를 Response body에 넣어주면 됩니다.{ "body" : "서울의 현재 날씨", "connectColor" : "#FAC11B", "connectInfo" : [{ "title" : "온도", "description" : "최고:28.00, 최저:24.00, 현재: 24.30" }, { "title": "날씨", "description": "흐리고 비" }] } 이를 이용하여 Instance Scheduler에도 적용해봤습니다.Schedule BotSchedule Bot은 Instance Scheduler의 Lambda 함수에 함께 포함되어 작동하며 스케쥴 조회 / 예외 설정, 서버 강제 시작/중지, 서버 상태 조회 등의 기능을 수행합니다.API Gateway와 Lambda 함수를 연결하여 Endpoint URL을 생성하고 Outgoing Webhook URL로 설정하여 Webhook으로 Lambda 함수가 실행될 수 있도록 하였습니다. Lambda 함수는 Cloudwatch를 통해서 실행되면 Scheduler가 작동되고 API Gateway를 통해 실행되면 Schedule Bot이 작동됩니다.Schedule Bot 명령어Schedule Bot은 다음과 같은 명령어를 수행합니다./서버 help : 도움말 /서버 [스케쥴명] status : 현재 서버 상태 조회 /서버 [스케쥴명] info : 오늘의 스케쥴 조회 /서버 [스케쥴명] info [YYYY-MM-DD] : 특정일 스케쥴 조회 /서버 [스케쥴명] exception info : 오늘의 스케쥴 예외 조회 /서버 [스케쥴명] exception info [YYYY-MM-DD] : 특정일 스케쥴 예외 조회 /서버 [스케쥴명] exception set [YYYY-MM-DD] [start|stop] [h:m] : 예외 설정 /서버 [스케쥴명] exception del [YYYY-MM-DD] [start|stop] : 예외 삭제 /서버 [스케쥴명] force_start : 서버 강제 실행 /서버 [스케쥴명] force_stop : 서버 강제 중지 Schedule Bot 작동 화면Schedule Bot은 서버병이라는 컨셉으로 인격화(?)에 힘썼습니다.스케쥴 정보 조회서버 상태 조회서버 강제 시작/중지명령어 오류마무리AWS 기반의 서비스를 운영하는 스타트업이라면 더욱더 현실적으로 부딪히는 비용 문제에 대해서 저희가 고민한 내용과 솔루션에 대해서 공유하였습니다.아직 적용기간이 길지 않아 절감비용에 대해 수치적인 데이터를 언급하기는 힘들지만 많은 금액이 절감될 거라 예상하고 있습니다.저희와 같은 고민을 하고 있다면 Instance Scheduler를 적극 권장합니다.#토스랩 #잔디 #JANDI #개발 #개발자 #AWS #도입후기 #일지 #인사이트 #경험공유

기업문화 엿볼 때, 더팀스

로그인

/