스토리 홈

인터뷰

피드

뉴스

조회수 1074

레진 기술 블로그 - AWS Auto Scalinging Group 을 이용한 배포

레진코믹스의 서버 시스템은 잘 알려진대로 Google AppEngine에서 서비스되고 있지만, 이런저런 이유로 인해 최근에는 일부 컴포넌트가 Amazon Web Service에서 서비스되고 있습니다. AWS 에 새로운 시스템을 셋업하면서, 기존에 사용하던 PaaS인 GAE에서는 전혀 고민할 필요 없었던, 배포시스템에 대한 고민이 필요했습니다. 좋은 배포전략과 시스템은 안정적으로 서비스를 개발하고 운영하는데 있어서 필수적이죠.초기에는 Beanstalk을 이용한 운영에서, Fabric 을 이용한 배포 등의 시행착오 과정을 거쳤으나, 현재는 (스케일링을 위해 어차피 사용할 수밖에 없는) Auto Scaling Group을 이용해서 Blue-green deployment로 운영 중입니다. ASG는 여러 특징 덕분에 배포에도 유용하게 사용할 수 있습니다.ASG를 이용한 가장 간단한 배포는, Instance termination policy 를 응용할 수 있습니다. 기본적으로 ASG가 어떤 인스턴스를 종료할지는 AWS Documentation 에 정리되어 있으며, 추가적으로 다음과 같은 방식을 선택할 수 있습니다.OldestInstanceNewestInstanceOldestLaunchConfigurationClosestToNextInstanceHour여기서 주목할 건 OldestInstance 입니다. ASG가 항상 최신 버전의 어플리케이션으로 스케일아웃되게 구성되어 있다면, 단순히 인스턴스의 수를 두배로 늘린 뒤 Termination policy 를 OldestInstance 로 바꾸고 원래대로 돌리면 구버전 인스턴스들부터 종료되면서 배포가 끝납니다. 그러나 이 경우, 배포 직후 모니터링 과정에서 문제가 발생할 경우 기존의 인스턴스들이 이미 종료된 상태이기 때문에 롤백을 위해서는 (인스턴스를 다시 생성하면서) 배포를 다시 한번 해야 하는 반큼 빠른 롤백이 어렵습니다.Auto scaling lifecycle 을 이용하면, 이를 해결하기 위한 다른 방법도 있습니다. Lifecycle 은 다음과 같은 상태 변화를 가집니다.기본적으로,ASG의 인스턴스는 InService 상태로 진입하면서 (설정이 되어 있다면) ELB에 추가됩니다.ASG의 인스턴스는 InService 상태에서 빠져나오면서 (설정이 되어 있다면) ELB에서 제거됩니다.이를 이용하면, 다음과 같은 시나리오로 배포를 할 수 있습니다.똑같은 ASG 두 개를 구성(Group B / Group G)하고, 그 중 하나의 그룹으로만 서비스를 운영합니다.Group B가 라이브 중이면 Group G의 인스턴스는 0개입니다.새로운 버전을 배포한다면, Group G의 인스턴스 숫자를 Group B와 동일하게 맞춰줍니다.Group G가 InService로 들어가고 ELB healthy 상태가 되면, Group B의 인스턴스를 전부 Standby로 전환합니다.롤백이 필요하면 Standby 상태인 Group B를 InService 로 전환하고 Group G의 인스턴스를 종료하거나 Standby로 전환합니다.문제가 없다면 Standby 상태인 Group B의 인스턴스를 종료합니다.이제 훨씬 빠르고 안전하게 배포 및 롤백이 가능합니다. 물론 실제로는 생각보다 손이 많이 가는 관계로(특히 PaaS인 GAE에 비하면), 이를 한번에 해주는 스크립트를 작성해서 사용중입니다. 대략 간략하게는 다음과 같습니다. 실제 사용중인 스크립트에는 dry run 등의 잡다한 기능이 많이 들어가 있어서 걷어낸 pseudo code 입니다. 스크립트는 사내 PyPI 저장소를 통해 공유해서 사용 중입니다.def deploy(prefix, image_name, image_version): '''Deploy specified Docker image name and version into Auto Scaling Group''' asg_names = get_asg_names_from_tag(prefix, 'docker:image:name', image_name) groups = get_auto_scaling_groups(asg_names) # Find deployment target set future_set = set(map(lambda g: g['AutoScalingGroupName'].split('-')[-1], filter(lambda g: not g['DesiredCapacity'], groups))) if len(future_set) != 1: raise ValueError('Cannot specify target auto scaling group') future_set = next(iter(future_set)) if future_set == 'green': current_set = 'blue' elif future_set == 'blue': current_set = 'green' else: raise ValueError('Set name shoud be green or blue') # Deploy to future group future_groups = filter(lambda g: g['AutoScalingGroupName'].endswith(future_set), groups) for group in future_groups: asg_client.create_or_update_tags(Tags=[ { 'ResourceId': group['AutoScalingGroupName'], 'ResourceType': 'auto-scaling-group', 'PropagateAtLaunch': True, 'Key': 'docker:image:version', 'Value': image_version, } ]) # Set capacity, scaling policy, scheduled actions same as current group set_desired_capacity_from(current_set, group) move_scheduled_actions_from(current_set, group) move_scaling_policies(current_set, group) # Await ELB healthy of instances in group await_elb_healthy(future_groups) # Entering standby for current group for group in filter(lambda g: g['AutoScalingGroupName'].endswith(current_set), groups): asg_client.enter_standby( AutoScalingGroupName=group['AutoScalingGroupName'], InstanceIds=list(map(lambda i: i['InstanceId'], group['Instances'])), ShouldDecrementDesiredCapacity=True ) def rollback(prefix, image_name, image_version): '''Rollback standby Auto Scaling Group to service''' asg_names = get_asg_names_from_tag(prefix, 'docker:image:name', image_name) groups = get_auto_scaling_groups(asg_names) def filter_group_by_instance_state(groups, state): return filter( lambda g: len(filter(lambda i: i['LifecycleState'] == state, g['Instances'])) == g['DesiredCapacity'] and g['DesiredCapacity'], groups ) standby_groups = filter_group_by_instance_state(groups, 'Standby') inservice_groups = filter_group_by_instance_state(groups, 'InService') # Entering in-service for standby group for group in standby_groups: asg_client.exit_standby( AutoScalingGroupName=group['AutoScalingGroupName'], InstanceIds=list(map(lambda i: i['InstanceId'], group['Instances'])) ) # Await ELB healthy of instances in standby group await_elb_healthy(standby_groups) # Terminate instances to rollback for group in inservice_groups: asg_client.set_desired_capacity(AutoScalingGroupName=group['AutoScalingGroupName'], DesiredCapacity=0) current_set = group['AutoScalingGroupName'].split('-')[-1] move_scheduled_actions_from(current_set, group) move_scaling_policies(current_set, group) 몇 가지 더…Standby 로 돌리는 것 이외에 Detached 상태로 바꾸는 것도 방법입니다만, 인스턴스가 ASG에서 제거될 경우, 자신이 소속된 ASG를 알려주는 값인 aws:autoscaling:groupName 태그가 제거되므로 인스턴스나 ASG가 많아질 경우 번거롭습니다.cloud-init 를 어느 정도 최적화해두고 ELB healthcheck 를 좀 더 민감하게 설정하면, ELB 에 투입될 때까지 걸리는 시간을 상당히 줄일 수 있긴 하므로, 단일 ASG로 배포를 하더라도 롤백에 걸리는 시간을 줄일 수 있습니다. 저희는 scaleout 시작부터 ELB에서 healthy 로 찍힐 때까지 70초 가량 걸리는데, 그럼에도 불구하고 아래의 이유 때문에 현재의 방식으로 운영중입니다.같은 방식으로 단일 ASG로 배포를 할 수도 있지만, 배포중에 혹은 롤백 중에 scaleout이 돌면서 구버전 혹은 롤백 버전의 인스턴스가 투입되어버리면 매우 귀찮아집니다. 이를 방지하기 위해서라도 (Blue-green 방식의) ASG 두 개를 운영하는게 안전합니다.같은 이유로, 배포 대상의 버전을 S3나 github 등에 기록하는 대신 ASG의 태그에 버전을 써 두고 cloud-init 의 user-data에서 그 버전으로 어플리케이션을 띄우게 구성해 두었습니다. 이 경우 인스턴스의 태그만 확인해도 현재 어떤 버전이 서비스되고 있는지 확인할 수 있다는 장점도 있습니다.다만 ASG의 태그에 Tag on instance 를 체크해 두더라도, cloud-init 안에서 이를 조회하는 경우는 주의해야 합니다. ASG의 태그가 인스턴스로 복사되는 시점은 명확하지 않습니다. 스크립트 실행 중에 인스턴스에는 ASG의 태그가 있을 수도, 없을 수도 있습니다.굳이 인스턴스의 Lifecycle 을 Standby / InService 로 전환하지 않고도 ELB 를 두 개 운영하고 route 53 에서의 CNAME/ALIAS swap 도 방법이지만, DNS TTL은 아무리 짧아도 60초는 걸리고, JVM처럼 골치아픈 동작 사례도 있는만큼 선택하지 않았습니다.물론 이 방법이 최선은 절대 아니며(심지어 배포할때마다 돈이 들어갑니다!), 현재는 자원의 활용 등 다른 측면에서의 고민 때문에 새로운 구성을 고민하고 있습니다. 이건 언젠가 나중에 다시 공유하겠습니다. :)
조회수 1414

스타일쉐어에서 이미지 분류하기 (시작 편) feat.ML

안녕하세요.스타일쉐어에서 백엔드 개발을 하고 있는 김동현입니다.작년 11월 스타일쉐어에서 뷰티에 관련된 사진들을 따로 모아서 보여줄 피드.바로 뷰티피드 라는 것을 만들었습니다. 하지만 피드를 만드는 과정이 순탄치 만은 않았는데요.그간의 과정과 얻었던 경험들을 공유하고자 합니다.들어가기에 앞서혹시 설명을 하다 보면 스타일쉐어에서만 사용되는 단어가 있을 수 있다는 생각이 들어 단어에 대한 공유를 먼저 드리고자 합니다.스타일쉐어에서는 이를 “피드”라 칭합니다.스타일쉐어에서는 이를 “스타일”이라 칭합니다.여러 가지 카테고리 중에서 왜 뷰티인가요?기존의 서비스에서는 유저들이 올리는 스타일에 대한 카테고리가 없어서 유저들이 보고 싶어 하는 스타일들을 쏙쏙 뽑아서 보여줄 수 없는 상황이었지만 “내가 보고 싶은 것들만 볼 수 있었으면 좋겠다”라는 유저들의 니즈는 계속 올라가고 있었습니다.서비스 특성상 1020 유저들이 많이 있었고 하루 동안 올라오는 스타일에 대해서 사람이 직접 카테고리를 하나하나 나눠봤을 때 가장 활발하게 대화가 이루어지고 반응이 좋고 충성도도 높은 카테고리가 바로 뷰티였습니다.뷰티만이라도 따로 보여줄 수 있도록 해보자그럼 어떻게 뷰티에 관련된 게시물들을 뽑아낼 건가요?올라오는 스타일들 중에서 뷰티라는 속성을 찾아내어 분류하는 방법으로 두 가지의 제안이 나왔습니다.1. 사람이 직접 뽑아낸다.2. 요즘 뜨고 있는 딥러닝을 이용해서 뽑아낸다.처음엔 사람이 직접 모니터링 해볼까? 라는 이야기가 나왔었습니다.당장이라도 시작 할 수 있다는 점과 높은 정확도를 가졌다는 장점이 있기 때문이였죠.하지만 주말 관계없이 4000~6000개씩 올라오는 스타일들을 상시 모니터링하고 모두 검토해야 하는 상황이 너무 막막하게 느껴졌습니다. 관련 업무를 하시는 분의 업무 만족도는 낮을 것이 당연하기도 했지만 그럴만한 인적자원이 충분하지 않았습니다.그래서 요즘 뜨고 있는 딥러닝을 이용해보자는 방향으로 일이 진행되었습니다. 게다가 요즘 딥러닝으로 Image Classification 하는데에 있어서 정확도가 사람을 넘어섰다는 이야기도 결정에 한몫을 했답니다.딥러닝으로 분류하기로 결정했다! 근데 트레이닝 셋은?딥러닝을 하시는 분들이 애용하는 사이트인 캐글만 가보아도 문제와 트레이닝 셋이 잘 정리되어있기에 개발자는 어떻게 하면 잘 예측할 수 있을까에 대한 고민만 했으면 되었었습니다. 하지만 당연하게도 실제 필드에서 처리해야 하는 문제와 그에 대한 트레이닝 셋은 존재하지 않았습니다.우선 딥러닝으로 분류하기로 결정을 하였으니 서비스에서 뷰티라는 카테고리 안에 넣을 소카테고리를 나누었고 다음과 같았습니다.* 눈 화장 관련* 입술 화장 관련* 얼굴 화장 관련* 헤어* 화장품* 발색* 네일그래도 태양 아래 새로운 것은 없다 라는 말처럼 비슷한 것들이 존재할까 하고 찾아보았으나…https://www.kaggle.com/openfoodfacts/openbeautyfactshttp://www.antitza.com/makeup-datasets.htmlㅇ…없잖아?!그렇습니다. 공개된 것은 없던 새로운 것이었습니다. 위의 소카테고리들을 모으는 방법을 모색해야 했습니다.위에서 언급했듯이 잉여 인적자원이 없었기 때문에 몇만 개의 데이터를 모을만한 데이터를 모으는 일은 저를 포함해서 개발자 2명이서 진행을 했었습니다.그래서 결국 뷰티 피드는…성.공.적.다행히도 잘 마무리되었습니다. 화자 되고 있는 딥러닝 기술을 실제로 사용해볼 수 있어서 좋았고 팀원들도 이게 되는구나, 다른 것도 해볼 수 있겠다 라는 피드백을 많이 받았고 저 또한 개발을 하면서도 이게 된다고? 하는 반응이 제일 많았던 것 같습니다. 물론 앞으로 모델을 계속 개선해나가야겠지만요.사실 딥러닝을 거의 처음 공부하는 수준에 가까웠고 초반에 우왕좌왕 하기도 많이 했었는데 믿고 기다려줬던 스타일쉐어 팀원 분들 덕분에 잘 마무리될 수 있었던 것 같습니다.분류와 트레이닝 셋에 대한 좀 더 자세한 글은 다음 포스팅 (분류 편)에서 찾아뵙겠습니다.#스타일쉐어 #개발팀 #개발자 #개발후기 #경험공유 #인사이트
조회수 643

HBase상 트랜잭션 라이브러리 Haeinsa를 소개합니다

비트윈에서는 서비스 초기부터 HBase를 주요 데이터베이스로 사용하였습니다. HBase에서도 일반적인 다른 NoSQL처럼 트랜잭션을 제공하지 않습니다. HBase, Cassandra와 MongoDB는 하나의 행 혹은 하나의 Document에 대한 원자적 연산만 제공합니다. 하지만 여러 행에 대한 연산들을 원자적으로 실행할 수 있게 해주는 추상화된 트랜잭션 기능이 없다면 보통의 서비스 개발에 어려움을 겪게 됩니다. 비트윈 개발팀은 이런 문제를 해결하기 위해 노력했으며, 결국 HBase에서 트랜잭션을 제공해주는 라이브러리인 Haeinsa를 구현하여 실제 서비스에 적용하여 성공적으로 운영하고 있습니다. VCNC에서는 Haeinsa를 오픈소스로 공개하고 이번 글에서 이를 소개하고자 합니다.Haeinsa란 무엇인가?¶Haeinsa는 Percolator에서 영감을 받아 만들어진 트랜잭션 라이브러리입니다. HAcid, HBaseSI 등 HBase상에서 구현된 트랜잭션 프로젝트는 몇 개 있었지만, 성능상 큰 문제가 있었습니다. 실제로 서비스에 적용할 수 없었기 때문에 Haeinsa를 구현하게 되었습니다. Haeinsa를 이용하면 다음과 같은 코드를 통해 여러 행에 대한 트랜잭션을 쉽게 사용할 수 있습니다. 아래 예시에는 Put연산만 나와 있지만, 해인사는 Put외에도 Get, Delete, Scan 등 HBase에서 제공하는 일반적인 연산들을 모두 제공합니다.HaeinsaTransaction tx = tm.begin(); HaeinsaPut put1 = new HaeinsaPut(rowKey1);put1.add(family, qualifier, value1);table.put(tx, put1); HaeinsaPut put2 = new HaeinsaPut(rowKey2);put2.add(family, qualifier, value2);table.put(tx, put2); tx.commit();Haeinsa의 특징¶Haeinsa의 특징을 간략하게 정리하면 다음과 같습니다. 좀 더 자세한 사항들은 Haeinsa 위키를 참고해 주시기 바랍니다.ACID: Multi-Row, Multi-Table에 대해 ACID 속성을 모두 만족하는 트랜잭션을 제공합니다.Linear Scalability: 트래픽이 늘어나더라도 HBase 노드들만 늘려주면 처리량을 늘릴 수 있습니다.Serializability: Snapshot Isolation보다 강력한 Isolation Level인 Serializability를 제공합니다.Low Overhead: NoSQL상에서의 트랜잭션을 위한 다른 프로젝트에 비해 오버헤드가 적습니다.Fault Tolerant: 서버나 클라이언트가 갑자기 죽더라도 트렌젝션의 무결성에는 아무 영향을 미치지 않습니다.Easy Migration: Haeinsa는 HBase를 전혀 건드리지 않고 클라이언트 라이브러리만 이용하여 트랜잭션을 구현합니다. 각 테이블에 Haeinsa 내부적으로 사용하는 Lock Column Family만 추가해주면 기존에 사용하던 HBase 클러스터에도 Haeinsa를 쉽게 적용할 수 있습니다.Used in practice: 비트윈에서는 Haeinsa를 이용하여 하루에 3억 건 이상의 트랜잭션을 처리하고 있습니다.Haeinsa는 오픈소스입니다. 고칠 점이 있다면 언제든지 GitHub에 리포지터리에서 개선에 참여하실 수 있습니다.Haeinsa의 성능¶Haeinsa는 같은 수의 연산을 처리하는 트랜잭션이라도 소수의 Row에 연산이 여러 번 일어나는 경우가 성능상 유리합니다. 다음 몇 가지 성능 테스트 그래프를 통해 Haeinsa의 성능에 대해 알아보겠습니다.아래 그래프는 3개의 Row에 총 6개의 Write, 3개의 Read연산을 수행한 트랜잭션의 테스트 결과입니다. 두 개의 Row에 3Write, 1Read 연산을 하고, 한 개의 Row에 1Read 연산을 한 것으로, 비트윈에서 가장 많이 일어나는 요청인 메시지 전송에 대해 시뮬레이션한 것입니다. 실제 서비스에서 가장 많이 일어나는 종류의 트랜잭션이라고 생각할 수 있습니다. 그런데 그냥 HBase를 사용하는 것보다 Haeinsa를 이용하는 것이 더 오히려 좋은 성능을 내는 것을 알 수 있습니다. 이는 Haeinsa에서는 커밋 시에만 모든 변경사항을 묶어서 한 번에 반영하기 때문에, 매번 RPC가 일어나는 일반 HBase보다 더 좋은 성능을 내는 것입니다.HBase 클러스터가 커질수록 트랜잭션 처리량이 늘어납니다. HBase와 마찬가지입니다.HBase 클러스터의 크기에 따른 응답시간 입니다. HBase와 다르지 않습니다..아래 그래프는 2개의 Row에 각각 한 개의 Write, 나머지 한 개의 Row에는 한 개의 Read 연산을 하는 트랜잭션에 대해 테스트한 것입니다. 각 Row에 하나의 연산만이 일어나기 때문에 최악의 경우라고 할 수 있습니다. 처리량과 응답시간 모두 그냥 HBase를 사용하는 것보다 2배에서 3배 정도 좋지 않은 것을 알 수 있습니다. 하지만 이 수치는 DynamoDB 상의 트랜잭션과 같은 다른 트랜잭션 라이브러리와 비교한다면 상당히 좋은 수준입니다.HBase보다 처리량이 떨어지긴 하지만, 클러스터가 커질수록 처리량이 늘어납니다.HBase보다 응답시간이 크긴 하지만 클러스터 크기에 따른 변화가 HBase와 크게 다르지 않습니다.저희는 언제나 타다 및 비트윈 서비스를 함께 만들며 기술적인 문제를 함께 풀어나갈 능력있는 개발자를 모시고 있습니다. 언제든 부담없이 [email protected]로 이메일을 주시기 바랍니다!
조회수 2085

스포카 서버의 구조

안녕하세요. 스포카 개발팀에서 서버 관련 개발 업무를 담당하고 있는 문성원입니다. 오늘은 스포카 서버의 구조와 사용된 기술들에 대해서 함께 살펴보겠습니다.스택이란?먼저 스택(Stack)이란 용어에 대해서 함께 생각해보죠. 컴퓨터 과학을 공부하신 분들이라면 선입후출(FILO)이나 스택 오버플로우(Stack Overflow)등의 개념으로 익숙하실만한 용어기도 합니다. 그런데 서버 구조를 설명한다면서 왠 스택이냐구요? 다행히(?)도 지금부터 살펴 볼 스택은 솔루션 스택(Solution Stack)입니다. 스포카 서버라는 큰 솔루션이 원활히 동작하기 위해서 쓰이고 있는 각종 서브 시스템과 컴포넌트들의 묶음을 이야기하는 것으로 바꿔말하자면 이 글에서 다룰 기술 이야기는 모두 이 스택에 관한 이야기입니다.2011년 12월 현재 스포카 서버를 구성하고 있는 스택은 다음과 같습니다.DotcloudLinux 2.6.38.2nginx 0.8.53uwsgi 0.9.8.5Python 2.6.5Redis 2.2.2Celery 2.2.7Amazon Relational Database ServiceMySQL 5.5.12Amazon Simple Storage ServiceDotcloudDotcloud는 지금부터 설명드릴 스택을 묶어서 제공해주는 PaaS(Platform as a Service)의 일종입니다. Amazon Elastic Cloud Computing(Amazon EC2) 기반으로 동작하며 거기에 더해 손쉬운 확장과 배포가 장점입니다. 스포카 서버는 데이터베이스(Amazon RDS)와 업로드되는 데이터(Amazon S3) 이외의 모든 서비스를 Dotcloud를 통하여 제공하고 있습니다.nginx, uwsgi. 그리고 WSGI기본적으로 스포카 서버는 HTTP 형식의 요청을 받아 응답을 돌려주는 웹 어플리케이션입니다. 이러한 처리는 1차적으로 nginx를 통해 이뤄지는데, 이 중 서버사이드에서 처리가 필요한 경우에는 uwsgi라는 데몬이 이 처리를 담당합니다. (구버젼의 Apache Tomcat을 사용하시던 Java개발자분들은 Apache Tomcat과 Apache httpd와의 관계를 떠올리시면 편합니다.)이 경우 uwsgi는 일종의 어플리케이션 컨테이너(Application Container)로 동작하게 됩니다. 적재한 어플리케이션을 실행만 시켜주는 역할이죠. 이러한 uwsgi에 적재할 어플리케이션(스포카 서버)에는 일종의 규격이 존재하는데, 이걸 WSGI라고 합니다.(정확히는 WSGI에 의해 정의된 어플리케이션을 돌릴 수 있게 설계된 컨테이너가 uwsgi라고 봐야겠지만요.) WSGI는 Python표준(PEP-033)으로 HTTP를 통해 요청을 받아 응답하는 어플리케이션에 대한 명세로 이러한 명세를 만족시키는 클래스나 함수, (__call__을 통해 부를 수 있는)객체를 WSGI 어플리케이션이라고 합니다.정리하자면 스포카 서버는 WSGI에 맞게 작성된 프로그램을 nginx와 uwsgi를 통해 운용하여 요청을 처리하는 웹 어플리케이션이라고 할 수 있습니다.RedisRedis란 키-값(Key-Value) 저장 서버로 확장이 용이하며 속도가 우수합니다. 스포카 서버에선 이를 내부적인 임시 데이터 관리와 Celery의 작업(Task) 분배에 사용하고 있습니다.CeleryCelery는 Python으로 작성된 비동기 작업 큐(Asynchronous task queue/job queue)입니다. 앞서 소개한 작업(Task)를 브로커(Broker, 스포카 서버는 Redis를 사용)를 통해 전달하면 하나 이상의 워커(Worker)가 이를 처리하는 구조입니다. 포인트 적립-공유에 따른 분배처리, 포스팅 기능, 페이스북/트위터 공유등의 비동기 처리가 필요한 작업을 Celery에 위임하여 처리하고 있습니다.Amazon Relational Database Service대부분의 웹 어플리케이션과 마찬가지로 스포카 서버는 영속적으로 저장되어야하는 정보(회원 목록, 구매 내역)들을 디스크 기반의 데이터베이스(Database)에 저장합니다. Amazon Relational Database Service(Amazon RDS)는 Amazon EC2를 기반으로 그러한 데이터베이스를 간편하게 관리(모니터링, 백업, 접근제어)할 수 있게 도와주는 웹서비스입니다. Oracle과 MySQL을 지원하는데 스포카 서버는 그 중 MySQL을 사용하고 있습니다.Amazon Simple Storage ServiceAmazon Simple Storage Service(Amazon S3)는 Amazon RDS와 마찬가지로 Amazon EC2를 기반으로 한 데이터 저장 관리 서비스입니다. 스포카 서버에 업로드 되는 사진이나 문서등의 파일들을 통합하여 관리하여 서버의 인스턴스를 늘려 확장하는 경우에도 문제없이 대처할 수 있도록 하는 것이 주 목적입니다.#스포카 #스택 #개발 #개발자 #개발팀 #인사이트 #조언 #스킬스택 #스택설명
조회수 1524

HBase 설정 최적화하기 - VCNC Engineering Blog

커플 필수 앱 비트윈은 여러 종류의 오픈 소스를 기반으로 이루어져 있습니다. 그 중 하나는 HBase라는 NoSQL 데이터베이스입니다. VCNC에서는 HBase를 비트윈 서비스의 메인 데이터베이스로써 사용하고 있으며, 또한 데이터 분석을 위한 DW 서버로도 사용하고 있습니다.그동안 두 개의 HBase Cluster 모두 최적화를 위해서 여러 가지 설정을 테스트했고 노하우를 공유해 보고자 합니다. 아랫은 저희가 HBase를 실제로 저희 서비스에 적용하여 운영하면서 최적화한 시스템 구성과 설정들을 정리한 것입니다. HBase를 OLTP/OLAP 목적으로 사용하고자 하는 분들에게 도움이 되었으면 좋겠습니다. 아래 구성을 최적화하기 위해서 했던 오랜 기간의 삽질기는 언젠가 따로 포스팅 하도록 하겠습니다.HBaseHBase는 Google이 2006년에 발표한 BigTable이라는 NoSQL 데이터베이스의 아키텍처를 그대로 따르고 있습니다. HBase는 뛰어난 Horizontal Scalability를 가지는 Distributed DB로써, Column-oriented store model을 가지고 있습니다. 사용량이 늘어남에 따라서 Regionserver만 추가해주면 자연스럽게 Scale-out이 되는 구조를 가지고 있습니다. 또한, Hadoop 특유의 Sequential read/write를 최대한 활용해서 Random access를 줄임으로 Disk를 효율적으로 사용한다는 점을 특징으로 합니다. 이 때문에 HBase는 보통의 RDBMS와는 다르게 Disk IO가 병목이 되기보다는 CPU나 RAM 용량이 병목이 되는 경우가 많습니다.HBase는 많은 회사가 데이터 분석을 하는 데 활용하고 있으며, NHN Line과 Facebook messenger 등의 메신저 서비스에서 Storage로 사용하고 있습니다.시스템 구성저희는 Cloudera에서 제공하는 HBase 0.92.1-cdh4.1.2 release를 사용하고 있으며, Storage layer로 Hadoop 2.0.0-cdh4.1.2를 사용하고 있습니다. 또한, Between의 데이터베이스로 사용하기 위해서 여러 대의 AWS EC2의 m2.4xlarge 인스턴스에 HDFS Datanode / HBase Regionserver를 deploy 하였습니다. 이는 m2.4xlarge의 큰 메모리(68.4GB)를 최대한 활용해서 Disk IO를 회피하고 많은 Cache hit이 나게 하기 위함입니다.또한 Highly-Available를 위해서 Quorum Journaling node를 활용한 Active-standby namenode를 구성했으며, Zookeeper Cluster와 HBase Master도 여러 대로 구성하여 Datastore layer에서 SPOF를 전부 제거하였습니다. HA cluster를 구성하는 과정도 후에 포스팅 하도록 하겠습니다.HDFS 최적화 설정dfs.datanode.handler.countHDFS에서 외부 요청을 처리하는 데 사용할 Thread의 개수를 정하기 위한 설정입니다. 기본값은 3인데 저희는 100으로 해 놓고 사용하고 있습니다.dfs.replicationHDFS 레벨에서 각각의 데이터가 몇 개의 독립된 인스턴스에 복사될 것 인가를 나타내는 값입니다. 저희는 이 값을 기본값인 3으로 해 놓고 있습니다. 이 값을 높이면 Redundancy가 높아져서 데이터 손실에 대해서 더 안전해지지만, Write 속도가 떨어지게 됩니다.dfs.datanode.max.transfer.threads하나의 Datanode에서 동시에 서비스 가능한 block 개수 제한을 나타냅니다.과거에는 dfs.datanode.max.xcievers라는 이름의 설정이었습니다.기본값은 256인데, 저희는 4096으로 바꿨습니다.ipc.server.tcpnodelay / ipc.client.tcpnodelaytcpnodelay 설정입니다. tcp no delay 설정은 TCP/IP network에서 작은 크기의 패킷들을 모아서 보냄으로써 TCP 패킷의 overhead를 절약하고자 하는 Nagle's algorithm을 끄는 것을 의미합니다. 기본으로 두 값이 모두 false로 설정되어 있어 Nagle's algorithm이 활성화되어 있습니다. Latency가 중요한 OLTP 용도로 HBase를 사용하시면 true로 바꿔서 tcpnodelay 설정을 켜는 것이 유리합니다.HBase 최적화 설정hbase.regionserver.handler.countRegionserver에서 외부로부터 오는 요청을 처리하기 위해서 사용할 Thread의 개수를 정의하기 위한 설정입니다. 기본값은 10인데 보통 너무 작은 값입니다. HBase 설정 사이트에서는 너무 큰 값이면 좋지 않다고 얘기하고 있지만, 테스트 결과 m2.4xlarge (26ECU) 에서 200개 Thread까지는 성능 하락이 없는 것으로 나타났습니다. (더 큰 값에 관해서 확인해 보지는 않았습니다.)저희는 이 값을 10에서 100으로 올린 후에 약 2배의 Throughput 향상을 얻을 수 있었습니다.hfile.block.cache.sizeHBase 의 block 들을 cache 하는데 전체 Heap 영역의 얼마를 할당한 것인지를 나타냅니다. 저희 서비스는 Read가 Write보다 훨씬 많아서 (Write가 전체의 약 3%) Cache hit ratio가 전체 성능에 큰 영향을 미칩니다.HBase 에서는 5분에 한 번 log 파일에 LruBlockCache (HBase 의 Read Cache) 가 얼마 만큼의 메모리를 사용하고 있고, Cache hit ratio가 얼마인지 표시를 해줍니다. 이 값을 참조하셔서 최적화에 사용하실 수 있습니다.저희는 이 값을 0.5로 설정해 놓고 사용하고 있습니다. (50%)hbase.regionserver.global.memstore.lowerLimit / hbase.regionserver.global.memstore.upperLimit이 두 개의 설정은 HBase에서 Write 한 값들을 메모리에 캐쉬하고 있는 memstore가 Heap 영역의 얼마만큼을 할당받을지를 나타냅니다. 이 값이 너무 작으면 메모리에 들고 있을 수 있는 Write의 양이 한정되기 때문에 디스크로 잦은 flush가 일어나게 됩니다. 반대로 너무 크면 GC에 문제가 있을 수 있으며 Read Cache로 할당할 수 있는 메모리를 낭비하는 것이기 때문에 좋지 않습니다.lowerLimit와 upperLimit의 두 가지 설정이 있는데, 두 개의 설정이 약간 다른 뜻입니다.만약 memstore 크기의 합이 lowerLimit에 도달하게 되면, Regionserver에서는 memstore들에 대해서 'soft'하게 flush 명령을 내리게 됩니다. 크기가 큰 memstore 부터 디스크에 쓰이게 되며, 이 작업이 일어나는 동안 새로운 Write가 memstore에 쓰일 수 있습니다.하지만 memstore 크기의 합이 upperLimit에 도달하게 되면, Regionserver는 memstore들에 대한 추가적인 Write를 막는 'hard'한 flush 명령을 내리게 됩니다. 즉, 해당 Regionserver이 잠시 동안 Write 요청을 거부하게 되는 것입니다. 보통 lowerLimit에 도달하면 memstore의 크기가 줄어들기 때문에 upperLimit까지 도달하는 경우는 잘 없지만, write-heavy 환경에서 Regionserver가 OOM으로 죽는 경우를 방지하기 위해서 hard limit가 존재하는 것으로 보입니다.hfile.block.cache.size와 hbase.regionserver.global.memstore.upperLimit의 합이 0.8 (80%)를 넘을 수 없게 되어 있습니다. 이는 아마 read cache 와 memstore의 크기의 합이 전체 Heap 영역 중 대부분을 차지해 버리면 HBase의 다른 구성 요소들이 충분한 메모리를 할당받을 수 없기 때문인 듯합니다.저희는 이 두 개의 설정 값을 각각 0.2, 0.3으로 해 놓았습니다. (20%, 30%)ipc.client.tcpnodelay / ipc.server.tcpnodelay / hbase.ipc.client.tcpnodelayHDFS의 tcpnodelay 와 비슷한 설정입니다. 기본값은 전부 false입니다.이 설정을 true로 하기 전에는 Get/Put 99%, 99.9% Latency가 40ms 와 80ms 근처에 모이는 현상을 발견할 수 있었습니다. 전체 요청의 매우 작은 부분이었지만, 평균 Get Latency가 1~2ms 내외이기 때문에 99%, 99.9% tail이 평균 Latency에 큰 영향을 미쳤습니다.이 설정을 전부 true로 바꾼 후에 평균 Latency가 절반으로 하락했습니다.Heap memory / GC 설정저희는 m2.4xlarge가 제공하는 메모리 (68.4GB)의 상당 부분을 HBase의 Read/Write cache에 할당하였습니다. 이는 보통 사용하는 Java Heap 공간보다 훨씬 큰 크기이며 심각한 Stop-the-world GC 문제를 일으킬 수 있기 때문에, 저희는 이 문제를 피하고자 여러 가지 설정을 실험하였습니다.STW GC time을 줄이기 위해서 Concurrent-Mark-and-sweep GC를 사용했습니다.HBase 0.92에서부터 기본값으로 설정된 Memstore-Local Allocation Buffer (MSLAB) 을 사용했습니다. hbase.hregion.memstore.mslab.enabled = true #(default)hbase-env.sh 파일을 다음과 같이 설정했습니다. HBASE_HEAPSIZE = 61440 #(60GB) HBASE_OPTS = "-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps"GC log를 Python script로 Parsing해서 STW GC 시간을 관찰하고 있습니다. 지금까지 0.2초 이상의 STW GC는 한 번도 발생하지 않았습니다.그 밖에 도움이 될 만한 설정들hbase.hregion.majorcompactionHBase는 하나의 Region에 대해서 여러 개의 StoreFile을 가질 수 있습니다. 그리고 주기적으로 성능 향상을 위해서 이 파일들을 모아서 하나의 더 큰 파일로 합치는 과정을 진행하게 됩니다. 그리고 이 과정은 많은 CPU usage와 Disk IO를 동반합니다. 그리고 이때 반응 속도가 다소 떨어지게 됩니다. 따라서 반응 속도가 중요한 경우에는, 이 Major compaction을 off-peak 시간대를 정해서 manual 하게 진행하시는 것이 좋습니다.저희는 사용자의 수가 상대적으로 적은 새벽 시간대에 crontab 이 실행시키는 script가 돌면서 전체 Region에 대해서 하나하나 Major Compaction이 진행되도록 하였습니다.기본값은 86,400,000 (ms)로 되어 있는데, 이 값을 0으로 바꾸시면 주기적인 Major Compaction이 돌지 않게 할 수 있습니다.hbase.hregion.max.filesizeHBase는 하나의 Region이 크기가 특정 값 이상이 되면 자동으로 2개의 Region으로 split을 시킵니다. Region의 개수가 많지 않을 때는 큰 문제가 없지만, 계속해서 데이터가 쌓이게 되면 필요 이상으로 Region 수가 많아지는 문제를 나을 수 있습니다. Region 수가 너무 많아지면 지나친 Disk IO가 생기는 문제를 비롯한 여러 가지 안 좋은 점이 있을 수 있기 때문에, split 역시 manual 하게 하는 것이 좋습니다. 그렇다고 Table의 Region 수가 너무 적으면 Write 속도가 떨어지거나 Hot Region 문제가 생길 수 있기 때문에 좋지 않습니다.HBase 0.92.1 에서는 기본값이 1073741824(1GB)로 되어 있는데, 저희는 이 값을 10737418240(10GB)로 늘인 후에 manual 하게 split을 하여 Region의 개수를 조정하고 있습니다.hbase.hregion.memstore.block.multipliermemstore의 전체 크기가 multiplier * flush size보다 크면 추가적인 Write를 막고 flush가 끝날때까지 해당 memstore는 block 됩니다.기본값은 2인데, 저희는 8로 늘려놓고 사용하고 있습니다.dfs.datanode.balance.bandwidthPerSec부수적인 설정이지만, HDFS의 Datanode간의 load balancing이 일어나는 속도를 제한하는 설정입니다. 기본값은 1MB/sec로 되어 있지만, 계속해서 Datanode를 추가하거나 제거하는 경우에는 기본값으로는 너무 느릴 때가 있습니다. 저희는 10MB/sec 정도로 늘려서 사용하고 있습니다.dfs.namenode.heartbeat.recheck-intervalHDFS namenode에만 해당되는 설정입니다.Datanode가 응답이 없는 경우에 얼마 후에 Hadoop cluster로부터 제거할 것인지를 나타내는 값입니다.실제로 응답이 없는 Datanode가 떨어져 나가기까지는 10번의 heartbeat가 연속해서 실패하고 2번의 recheck역시 실패해야 합니다. Heartbeat interval이 기본값인 3초라고 하면, 30초 + 2 * recheck-interval 후에 문제가 있는 Datanode가 제거되는 것입니다.기본값이 5분으로 되어 있는데, fail-over가 늦어지기 때문에 사용하기에는 너무 큰 값입니다. 저희는 문제가 있는 Datanode가 1분 후에 떨어져 나갈 수 있도록 이 값을 15,000 (ms) 으로 잡았습니다.Read short-circuitRegionServer가 로컬 Datanode로부터 block을 읽어올 때 Datanode를 통하지 않고 Disk로부터 바로 읽어올 수 있게 하는 설정입니다.데이터의 양이 많아서 Cache hit이 낮아 데이터 대부분을 디스크에서 읽어와야 할 때 효율적입니다. Cache hit에 실패하는 Read의 Throughput이 대략 2배로 좋아지는 것을 확인할 수 있습니다. OLAP용 HBase에는 매우 중요한 설정이 될 수 있습니다.하지만 HBase 0.92.1-cdh4.0.1까지는 일부 Region이 checksum에 실패하면서 Major compaction이 되지 않는 버그가 있었습니다. 현재 이 문제가 해결되었는지 확실하지 않기 때문에 확인되기 전에는 쓰는 것을 추천하지는 않습니다.설정하는 방법은 다음과 같습니다. dfs.client.read.shortcircuit = true #(hdfs-site.xml) dfs.block.local-path-access.user = hbase #(hdfs-site.xml) dfs.datanode.data.dir.perm = 775 #(hdfs-site.xml) dfs.client.read.shortcircuit = true #(hbase-site.xml)Bloom filterBloom filter의 작동방식에 대해 시각적으로 잘 표현된 데모 페이지HBase는 Log-structured-merge tree를 사용하는데, 하나의 Region에 대해서 여러 개의 파일에 서로 다른 version의 값들이 저장되어 있을 수 있습니다. Bloom filter는 이때 모든 파일을 디스크에서 읽어들이지 않고 원하는 값이 저장된 파일만 읽어들일 수 있게 함으로써 Read 속도를 빠르게 만들 수 있습니다.Table 단위로 Bloom filter를 설정해줄 수 있습니다.ROW와 ROWCOL의 두 가지 옵션이 있는데, 전자는 Row key로만 filter를 만드는 것이고, 후자는 Row+Column key로 filter를 만드는 것입니다. Table Schema에 따라 더 적합한 설정이 다를 수 있습니다.저희는 데이터 대부분이 메모리에 Cache 되고 하나의 Region에 대해서 여러 개의 StoreFile이 생기기 전에 compaction을 통해서 하나의 큰 파일로 합치는 작업을 진행하기 때문에, 해당 설정을 사용하지 않고 있습니다.결론지금까지 저희가 비트윈을 운영하면서 얻은 경험을 토대로 HBase 최적화 설정법을 정리하였습니다. 하지만 위의 구성은 어디까지나 비트윈 서비스에 최적화되어 있는 설정이며, HBase의 사용 목적에 따라서 달라질 수 있음을 말씀드리고 싶습니다. 그래서 단순히 설정값을 나열하기보다는 해당 설정이 어떤 기능을 하는 것인지 저희가 아는 한도 내에서 설명드리려고 하였습니다. 위의 글에서 궁금한 점이나 잘못된 부분이 있으면 언제든지 답글로 달아주시길 바랍니다. 감사합니다.
조회수 3987

왜 SVG로 갈아탔는가?

이 글에서는 데일리호텔이 왜 png에서 svg로 갈아탔는지, 그리고 간단한 svg 실무 적용 팁에 대해 알려드리고자 합니다.01 SVG란 무엇인가?SVG는 “ Scalable Vector Graphics”의 약자입니다.JPEG, PNG 처럼 SVG도 그래픽 포맷(Graphic format) 중 하나입니다. SVG는 벡터 기반이기 때문에 리사이징이 되어도 전혀 깨지지 않습니다. 모든 해상도에서 자유자재로 활용할 수 있기 때문에 특정 해상도에 제한되어있지 않다는 게 핵심 포인트라고 할 수 있습니다.02 SVG가 왜 좋은가?다른 그래픽 포맷보다 SVG가 좋은 이유는 참으로도 다양합니다. 필자가 생각했을 때의 핵심 장점들은 이러합니다.1. 특정 사이즈에 구애를 받지 않습니다.즉 어느 해상도에서든 pixelate 되지 않습니다. 요새 디자이너들이 자주 사용하는 디자인 프로그램인 스케치로 따지면 아트보드와 비슷한 것 같습니다. 아트보드 안에 만든 레이어, 요소들은 다 벡터 기반입니다. 아트보드를 리사이징 해도 안에 요소들은 깨지지 않고 그 모습 그대로를 가지고 있습니다. 같은 원리로 SVG도 어떤 사이즈로든 그 모습 그대로가 유지됩니다. 그렇기 때문에 사이즈별로 아이콘을 일일이 생성해서 개발자에게 넘겨줄 필요가 없습니다. SVG 파일 하나면 모든 해상도를 대응할 수 있습니다.2. 작은 파일 사이즈비트맵 이미지들(PNG, JPEG) 같은 경우 파일 크기를 결정하는 주요 요소는 바로 ‘해상도’입니다. 예를 들어 5000x5000 픽셀 이미지는 항상 500x500보다 파일 사이즈가 큽니다.반면, SVG 그래픽 같은 경우 파일 크기를 결정하는 주요 요소는 바로 ‘복잡도’입니다. Path가 비교적 적은 간단한 이미지는 PNG, JPEG 보다 파일 사이즈가 적을 수도 있지만 이미지를 구성하는 요소의 복잡도(레이어가 많다든지 특정 효과가 많다든지)에 따라 파일 사이즈가 커집니다.하지만 이런 용량 문제는 SVG Optimizing을 하게 되면 나름 해결됩니다. 필자 같은 경우 업무적으로 스케치를 사용하고 있기 때문에 스케치에서 제공해주는 SVGO Compressor 플러그인을 활용하고 있습니다.https://github.com/BohemianCoding/svgo-compressorBohemianCoding/svgo-compressorsvgo-compressor - A Plugin that compresses SVG assets using SVGO, right when you export them. This Plugin requires Sketch 3.8.github.com 작은 파일 사이즈로 인해 로딩 시간도 훨씬 더 줄어든다는 장점 또한 있습니다.여기서 잠깐!혹시나 Bitmap과 SVG의 구성요소에 대해 잘 모르실 분들을 위하여 간단한 비교 해드리겠습니다.비트맵 그래픽: Raster Graphics (픽셀 기반)대표적인 포맷은 JPEG, PNG입니다. 이들은 픽셀로 구성되어 있습니다. 예를 들어 2x2 픽셀인 비트맵 이미지는 총 4px로 구성되어 있습니다. 개개인에 대한 픽셀들은 자유자재로 바꿀 수가 없고 움직일 수도 없습니다. 그렇기 때문에 100% 이상으로 이미지를 확대하면 Pixelate가 됩니다.SVG 그래픽: 벡터 기반픽셀로 구성되어 있지 않고 작업하고 있는 그래픽에 대한 정보로 구성되어 있습니다. 그렇기 때문에 어떤 사이즈로든 자유자재로 늘어나는 것이 가능합니다. 이러한 이유들로 인해 코드로 쉽게 적용된 스타일을 수정할 수 있습니다. 예를 들어 동그라미의 보더 값을 6에서 8로 바꾼다 / 색상을 그레이에서 블랙으로 바꾼다 / 사이즈를 40x40에서 80x80을 바꾼다 등스케치로 작업할 때도 쉽게 두 개의 차이점을 확인해볼 수 있습니다. 스케치에서 Export를 할 경우 비트맵 이미지는 하나의 압축된 레이어로 Export 됩니다. 반면 SVG는 레이어 그대로 눈에 보이지 않는 그래픽을 구성하는 정보들이 같이 저장된 채 Export가 됩니다.SVG를 구성하는 눈에 보이지 않는 정보들03 스케치가 SVG 이미지를 Export하는 방식다른 그래픽 포맷보다 SVG가 좋은 이유는 참으로도 다양합니다. 제가 생각했을 때의 핵심 장점들은 이러합니다.Sketch Export 기능스케치 하단 오른쪽 패널을 보면 Export 버튼이 있습니다. 여기서 Format을 SVG로 바꾸고 Export하면 금방 쉽게 끝나겠지 라고 생각할 수 있는데 여기서 조심해야 할 점은 본인이 어떻게 이미지를 작업했냐에 따라 옳지 않게 SVG가 내보내질 수 있습니다. 옳지 않게 SVG가 내보내 지게 되면 나중에 두 번 일을 작업하는 일이 발생할 수도 있습니다.쉽게 이해하실 수 있도록 이미지를 제작해 보았습니다. 아래 이미지는 같은 디자인인데 만들어진 방식이 각각 다릅니다.같은 아이콘이지만 구성하는 방식이 다름1. Two Shape2. One Shape3. Border and Shape Mix위 3가지 방법들은 옳고 그름이 없습니다. 다만 어떻게 이 아이콘을 나중에 활용할 것인가에 따라 만드는 방법이 달라지겠죠. 만약에 자동차 아이콘 안에 헤드라이트 색상을 바꾸고 싶다고 하면 위 방법 중 1번을 선택하면 될 것이고 선의 두께를 따로 조정하고 싶다 하면 3번 방식을 택하면 됩니다.SVG에 대해 잘 알지 못할 때는 프로그램 탓을 했었습니다. ‘왜 프로그램이 알아서 잘 못해주지?’라는 질문을 던졌지만… 슬프게도 이건 프로그램 잘못이 아닌 작업자 잘못입니다 �스케치 프로그램이든 아도비 일러스트레이터든 이 프로그램들은 디자이너가 만든 그래픽을 있는 그대로 svg 레이어로 번역하도록 프로그램이 되어 있습니다. 디자이너가 어떻게 작업했냐에 따라 그 정보 그대로 인식해서 svg로 만들어줍니다.04 SVG 아이콘이 제대로 적용 안될 경우다른 그래픽 포맷보다 SVG가 좋은 이유는 참으로도 다양합니다. 필자가 생각했을 때의 핵심 장점들은 이러합니다.헐 이건 도대체 왜….?!!!어느 날 SVG를 적용하기로 마음먹고 데일리호텔 앱 내 편의시설 아이콘 중 수영장 SVG 파일을 개발자에게 넘겼습니다. 근데 구멍이 뚫려야 할 곳이 채워져서 나오는데 원인을 모르고 헤매던 시절이 있었습니다. 미디엄에서 이 문제를 해결해줄 좋은 글을 발견하게 되었는데 난생처음 보는 단어가 2개 있었습니다.Even-Odd, Non-Zero…여기서 Even-Odd, Non-Zero의 차이점을 자세히 언급하기에는 너무 길어서 제가 참고한 미디엄 블로그 링크를 공유해드릴 테니 가서 보시면 이해하실 수 있을 것 같습니다. 작업하기에 앞서 꼭 읽어보시기를 권장합니다.https://medium.com/sketch-app-sources/preparing-and-exporting-svg-icons-in-sketch-1a3d65b239bbPreparing and Exporting SVG Icons in Sketch – Design + Sketch – MediumThis article is going to assume that you already understand the fundamentals of icon design. And focus on how to prepare and export them…medium.com 그래도 가볍게 필요한 내용만 공유드리자면 안드로이드에서는 fill-rule:evenodd를 제대로 지원하지 않고 fill-rule:nonzero만 지원한다고 보시면 됩니다. Even Odd는 특정 앱에서 호환이 안된다는 뜻입니다. (안드로이드 API 24 이상에서만 evenodd가 지원됨)근데 우리가 사용하고 있는 스케치 프로그램에서는 default값이 fill-rule:evenodd로 설정이 되어있고 여러 Path가 겹치는 아이콘 같은 경우 그대로 svg export를 하게 되면 위에서 제가 경험하였던 아이콘이 다 채워진 현상을 겪을 수 있게 되는 것입니다.1. Fills 섹션에서 Even-Odd를 Non-Zero로Fills 섹션에 가면 설정 아이콘이 있습니다. 클릭 시 Even-Odd가 디폴트 값인 것을 확인할 수 있습니다.스케치 Fill Default 값 = Even-OddNon-Zero로 설정값을 바꾸면 수영장 사다리 부분이 가득 채워진 채로 나오게 되는 것을 확인할 수 있습니다. 실제로 이 파일을 개발자에게 넘기게 되면 이렇게 채워진 채로 아이콘이 노출이 됩니다.Non-Zero 설정 / 모든 shape이 다 칠해짐이렇게 나가면 안 될 테니 수정하는 법을 알려드리겠습니다.2. Paths > Reverse Order 적용원래 뚫려 있어야 하는 Path를 Layer 패널에서 찾으면 됩니다. 빨간색으로 칠한 부분이 뚫려있어야 하는 부분들입니다.레이어 패널에서 path 확인하기Path가 선택된 채로 Layers > Paths > Reverse Order을 클릭합니다.Paths > Reverse OrderReverse Order을 클릭한 후 원래 뚫려있어야 하는 부분이 뚫리게 됩니다. 이 상태로 svg로 export하시고 개발자에게 전달을 하면 됩니다.마치며개인적으로 SVG에 대한 장점이 너무나도 크다고 생각하여 굳이 갈아타지 않을 이유가 없다고 생각합니다. 특히 Web 디자인을 할 때도 SVG를 저는 적극적으로 사용하시라고 권장하고 싶습니다. � 안드로이드 개발자에게 넘기기 전에 SVG 파일이 문제가 있는지 가볍게 확인하고 싶은 경우 아래와 같은 사이트를 추천해드립니다.http://inloop.github.io/svg2android/위에 문제가 되었던 수영장 아이콘을 이 사이트에 올려서 보게 되면 이런 화면이 뜹니다. Warning하고 노란색 경고 박스가 뜨게 되는데 fills-rule:evenodd에 대해서 언급을 하더라구요. 정말 유용한 사이트인 것 같습니다.아울러..많은 디자이너들이 SVG 적용을 해보시길 바라며 주변에 이 글도 많이 공유해주시면 감사하겠습니다. (ㅎㅎ)또한 데일리호텔 Tech, UI/UX 등의 정보를 얻어보고자 하시는 분은 https://dailyhotel.io/ 를 읽어 보시길 권장합니다.그럼 다음에도 좋은 정보로 찾아뵙겠습니다!원문 링크 : https://dailyhotel.io/디자인-안드로이드-앱-svg-아이콘-적용기-왜-svg로-갈아탔는가-99c57cd84240작성자 : Product팀 Rachel Kim#데일리 #데일리호텔 #개발자 #개발팀 #업무환경 #개발환경 #SVN
조회수 2668

Good Developer 2 | 커뮤니케이션 잘하는 개발자가 되는 방법

프로그래머와 개발자는 다르다.커뮤니케이션에 대한 이야기를 하기 전에 프로그래머와 개발자의 차이에 대해 명확히 하려 한다. 먼저 프로그래머는 컴퓨터를 이용해서 프로그램을 만들거나 수정하는 일을 하는 사람이다. 프리랜서로 일하면서 외주 프로젝트를 맡거나 학교 과제를 하면서 프로그래밍을 하는 사람들 모두 프로그래머라 할 수 있겠다.반면, 개발자는 회사나 조직에 소속이 돼서 다른 사람들과 함께 일하면서 개발을 사는 사람이다. 즉 어딘가에 소속이 돼서 규칙이나 규율 혹은 그 조직의 원칙을 가지고 일을 한다면 개발자로 볼 수 있는 것이다. 정리해 보자면 모든 개발자는 프로그래머지만 모든 프로그래머는 개발자는 아니다. 프로그래머와 개발자를 굳이 나누어서 말하는 이유는 개발자에게는 커뮤니케이션 능력이 절대적으로 필요하기 때문이다. 이와 관련해 아주 적절한 비유를 소개하려고 한다. 이 비유는 칼럼니스트 임백준 님의 '개발자의 생명은 커뮤니케이션 능력'에서 가져왔다.(이 글도 아주 좋으니 읽어보는 것을 추천)비유를 해보자면 이렇다. 프로그래머나 해커는 강호를 떠돌면서 혼자서 행동하는 무사라고 한다면 개발자는 군대에 소속되어 있는 정규군이다. 칼럼에서는 정확이 이렇게 표현한다.외톨이 무사에게 생명은 칼 솜씨고 정규군의 생명은 규칙과 규율이다.칼 솜씨는 코딩 실력이 되겠고, 규칙과 규율은 다른 사람과의 커뮤니케이션 능력이라 볼 수 있겠다. 이것이 개발자에게 있어 코딩 실력이 중요하지 않다는 것은 아니다. 코딩 실력은 기본이요. 커뮤니케이션 능력도 반드시 필수적이라는 뜻이다. 군대에 속해서 전투를 치르기 위해서는 기본적인 전투능력이 필요하다. 즉, 개발자는 자기가 맡은 프로그래밍 업무를 성공적으로 수행할 수 있는 능력을 가져야 하고 이것 은 기본이다!좋은 개발자가 되기 위한 첫 번째 방법, '소통'많은 시니어 개발자들이나 개발 관련된 직종에서 오래 근무한 사람들이 가장 많이 하는 말 중 하나가 바로 커뮤니케이션에 대한 이야기다.  개발자를 뽑을 때 중요한 것이 커뮤니케이션 능력이라고 한다. 커뮤니케이션이 원활하지 않아 개발 업무에 차질이 생기는 일이 다반사며 원활한 커뮤니케이션은 막혔던 문제를 훨씬 더 빠른 속도로 풀릴 수 있게끔 만든다.그럼 구체적으로 좋은 커뮤니케이션을 하기 위해 어떻게 해야 하는지 알아보자. 한 번쯤 들어봤을 이야기들이긴 하지만 구체적인 실행방안들을 추가해서 실제 기업이나 조직에서 바로 적용할 수 있도록 했다.건설적인 대화를 하라!너무나 당연한 말이지만, 이 말이 얼마나 업무 현장에서 지켜지고 있는지는 의문이다. 먼저 건설적인 대화의 방법들을 살펴보기 전에 어떤 대화들이 건설적인 대화가 아닌지를 살펴보자. 그리고 그것을 어떻게 건설적인 대화로 바꿀 것인지 말할 것이다.(1) 대화가 끝났어도 명확한 합의점이나 결과, action item, 해결책이 나오지 않았다.- > 이 문제는 두 가지 이유에서 비롯된다. 첫 번째는 대화의 목적(대화를 하는 이유)이나 목표(해결하고자 하는 것)가 불문명해서 대화가 어느 방향을 전개되야 하는지 갈피를 못 잡기 때문이다. 그리고 두 번째는 대화가 끝난 후 테스크로 전환하는 일을 하지 않은 것이다.==> 대화의 목적과 목표를 분명히 하라! 이야기를 시작할 때는 목적과 목표를 분명히 하라. '우리 지금 이 문제를 해결하기 위해 이야기하는 거죠?' '이 문제를 어떻게 처리할지에 대해 이야기해 봐요.' 일차원적일 수도 있겠지만 이렇게 직접적으로 이야기하는 것이 원활한 커뮤니케이션을 하는데 더 효과적이다. 목적과 목표를 정하지 않고 이야기를 하게 되면 이야기가 중간에 표류할 공산이 크다.==> 대화가 끝난 후에는 반드시 대화에서 얻어낸 결과물들을 테스크로 전환하고 각자에게 배분하라! 업무적 성격의 대화인 경우 문제 해결에 대한 이야기일 가능성이 크다. 이때 액션 아이템이나 합의점이 도출되지 않았다면 건설적인 대화가 이루어지지 않은 것이다. 업무 관련 일이 아닐 경우, 단순 아이디어 회의일 경우에는 대화하면서 나온 아이디어를 적고 문서화시켜야 한다. 그래야 나중에 '너 그때 이렇게 말했잖아!' 하면서 싸우는 일이 없다. 결론이나 결과가 없는 대화는 나중에 그 문제로 인해 다시 대화하게 될 가능성이 크다. 그리고 그것은 곧 리소스의 낭비다.(2) 논쟁을 하다 삼천포로 빠지고, 논쟁이 논쟁을 위한 논쟁으로 변질된다.-> 대화를 하다 보면 항상 좋게 좋게만 흘러가는 것이 아니다. 또 원활한 커뮤니케이션이 의견 충돌이 없는 소통을 의미하는 것도 아니다. 의견이 충돌하되 그것을 건설적이고 긍정적인 방향으로 풀어내는 것이 커뮤니케이션 능력이다. 이 케이스는 목적과 목표의 설정이 제대로 이루어지지 않아서이기도 하지만 대화를 하는데 있어서 서로가 명확히 해야 할 부분을 하지 않아서이기도 하다.==> 논쟁의 지점을 분명히 하라! 특히, 논쟁 지점이 여러 가지라면 뒤죽박죽 이 얘기 저 얘기 다 하면서 시간 소모를 할 공산이 크다. 건설적인 논쟁을 위해서는 우리가 어떤 포인트 때문에 논쟁을 하는지 서로 동의하는 부분은 무엇이고 동의하지 않는 부분은 무엇인지 명확히 해야 한다. ==> 용어를 분명히 하라! 서로 쓰는 용어의 의미가 달라서 논쟁이 되는 경우도 많다. 같은 문제를 바라봐도 다르게 말할 수 있고, 다른 문제를 이야기하는데 같은 용어를 통해 이야기할 수 있다. 원활한 커뮤니케이션의 기본은 용어 통일, 논의의 통일이다. 같은 수준에서 이야기할 때 비로소 원활한 소통을 할 수 있다.커뮤니케이션에 있어서 핵심은 '당신'이다.물론, 커뮤니케이션은 쌍방의 문제다. 내가 문제일수도 있고 상대방이 문제일 수도 있다. 하지만, 원활한 커뮤니케이션에 있어서 상대방을 바꾸는 것은 매우 어렵지만, 나를 바꾸는 것은 상대방을 바꾸는 것보다는 수월하다. 그리고 진정으로 커뮤니케이션을 잘하는 사람은 커뮤니케이션을 못하는 사람과도 '잘' 하는 사람이다. 커뮤니케이션을 잘하는 개발자로 인정받고 싶다면 그 누구와도 잘 할 준비가 되어 있어야 한다.그럼 어떻게 바뀌어야 커뮤니케이션을 잘 할 수 있게 되는지 세 가지 조건을 통해서 알아보자.(1) 자신과 상대방의 커뮤니케이션 스타일을 파악한다.서로 누구의 잘못이라기보다는 방식의 차이 때문에 싸우는 경우가 다반사다. 말투, 어투, 말하는 방식, 시기 등 자신의 스타일을 모르고 상대방의 스타일을 이해하지 못할 때 커뮤니케이션은 막혀버린다. 가장 좋은 것은 글로 적어보는 것이다. 나는 이렇고 상대방은 이렇다. 직접적으로 적어본다면 보다 커뮤니케이션 스타일을 이해하기 쉬워진다. 그리고 커뮤니케이션 스타일을 이해하는 것만으로도 커뮤니케이션을 할 때 많은 도움이 된다.(2) 상대방이 당신에게 망설임 없이 커뮤니케이션할 수 있게 하라!어떤 사람과는 커뮤니케이션 시작 자체를 하기가 어려운 사람들이 있다. 바쁘거나 시작하면 논의가 이루어지지 않거나 많은 조건들이 있을 것이다. 특히, 이 부분에 대해서는 스스로를 돌아보기가 매우 힘들다. 이때는 딱 두 가지의 것을 확인하면 된다.첫 번째로는 주변에 커뮤니케이션하기 망설여지는 상대를 찾아보라. 그리고 그 사람과는 왜 커뮤니케이션이 망설여지는지 생각해 보고 나를 돌아보면 된다. 타산지석(他山之石)이라 했던가. 혹시 내가 커뮤니케이션이 망설여지는 사람이 아닌지 다른 사람을 통해 되돌아보자.두 번째로는 다른 사람에게 솔직하게 물어보는 것이다. 이 방법이 사실 제일 중요하다. 내가 커뮤니케이션에 있어서 부족한 점은 없는지 상대방에게 물어보는 것이 가장 효과적인 방법이다. 물론, 솔직한 말을 듣는 것이 처음에는 두렵고 상처가 될 수도 있다. 하지만, 이것은 당신을 가장 성장시켜줄 대화 중 하나다. 동료만큼 당신과 커뮤니케이션을 많이 하는 사람도 없을 테니 바로 옆자리의 동료에게 자신의 커뮤니케이션에 있어서 부족한 점을 솔직히 말해달라고 부탁하라!(3) 동료와 친밀한 관계를 형성하고 공감하는 것은 중요하다.여기 회사 동료와 친할수록 일의 효율이 올라간다는 연구결과가 있다. 커뮤니케이션의 기본은 열린 마음이다. 그리고 마음은 상대방에게 호의가 있을 때 더 쉽게 열린다. 좋은 커뮤니케이션을 위해서라면 사전에 좋은 관계를 형성하는 것도 중요하다. 좋은 관계와 좋은 커뮤니케이션은 서로 밀접한 상관관계가 있다.대화가 커뮤니케이션의 전부가 아니다.대화만으로 모든 커뮤니케이션을 할 수는 없다. 효율적이지도 않고 물리적으로 불가능한 상황이 있을 수도 있다. 원활한 커뮤니케이션을 위해서라면 적절한 도구의 사용이 필요하다. 즉, 협업 툴을 효과적으로 사용하여 자신이 하고 있는 일들을 상대방에게 알려주고 상대방의 업무를 파악하려고 노력하라!도구의 사용은 커뮤니케이션에 사용하는 비용을 엄청나게 절감해 준다. 자신이 커뮤니케이션에 자신이 없고 언변이 부족하다 생각한다면 도구를 잘 쓰는 방식으로 커뮤니케이션 능력을 향상시킬 수 있다. 지금까지 위에서 언급한 것들은 쉽게 바뀔 수 있는 것들이 아니다. 왜냐하면 지금까지 몸에 체화된 자신만의 대화 방식을 바꾸는 것이기 때문이다. 하지만 커뮤니케이션 도구의 사용은 프로그램이다. 프로그램은 사용법을 배우면 된다.예를 들어, ASANA라는 협업 툴로 자신과 동료의 업무를 리스트화하고 체크할 수 있다. 또는, 구글 캘린더에 자신의 스케줄을 올려서 일정을 공유할 수 있다. 협업 툴을 이용하면 일의 진행사항들을 쉽게 공유하고 상대방의 일정을 파악할 수 있다. 그리고 이런 정보의 공유는 원활한 커뮤니케이션의 기본이다. 이런 도구들을 통해 커뮤니케이션이 부족한 사람들도 충분히 좋은 '커뮤니케이터'가 될 수 있다.커뮤니케이션도 실력이다.다시 처음으로 돌아가 커뮤니케이션의 필요성에 대해 다시 강조하려고 한다. 어떤 사람은 개발자의 핵심은 개발 능력이고 커뮤니케이션은 잘하면 좋은 것이라 생각한다. 위에서도 언급했지만, 개발자는 떠돌이 무사나 용병이 아니다. 조직에 소속되어 있는 개발자라면 소통하고 커뮤니케이션을 하는 능력이 핵심이다.그래서 개발자가 되려는 사람들에 항상 하는 말 중 하나가 다른 사람과 함께한 협업 프로젝트를 해보라는 것이다. 함께 프로젝트를 하는 경험은 프로그래밍 능력을 향상시킬 뿐만 아니라 어떻게 함께 개발하는지에 대해 많은 고민을 할 수 있게 해준다. 단순히 프로그래머가 되려면 코딩 실력에만 집중하라! 그러나, 다른 사람들과 함께 개발을 하는 개발자를 지향한다면 반드시 커뮤니케이션 역량도 향상시켜라!Good Developer 두 번째는 커뮤니케이션에 대해서 다루었다. 다음 Good Developer 는 나쁜 개발자에 대해서 알아볼 것이다.
조회수 4245

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
조회수 1105

EOS Smart Contract 를 위한 준비

EOS Smart Contract 를 위한 준비와 토큰 발행 그리고 C++를 활용해 토큰의 간단한 기능을 개발해 보겠습니다.환경 구성 및 지갑 생성은 SAM 님의 아래 2글을 참고해 주시기 바립니다.EOS — 설치 및 실행 (1/2)EOS — 동작구조 및 환경설정(2/2)지갑 생성하기SAM 님의 포스트를 참고 하셨다면 아마 다음과 같이 ‘default’ (별도의 이름을 지정하지 않았을 시) 지갑을 생성 하셨을 겁니다.이 지갑을 사용하여 계정을 Create 한 후 Key 를 Import 하겠습니다.Key 생성하기$ cleos create key위 명령을 실행 하시면 다음과 같은 화면을 얻을 수 있습니다.create key 명령의 결과**주의 : Private Key는 Public Key의 소유를 증명하는 중요한 개념으로 절대 타인에게 노출하면 안됩니다.AdditionalKey 생성 후 지갑에 import 하기 귀찮으시다면 생성된 지갑에서 바로 Key 를 생성하셔도 됩니다.$ cleos wallet create_key위와같이 key가 생성 됩니다. 하지만 public key 만 보이기 때문에 하단 명령 입력 후 지갑 key를 입력하면 private key를 확인할 수 있습니다.$ cleos wallet private_keys지갑에 Key import하기지갑은 Public Key — Private Key를 저장하는 저장소 입니다. 생성된 키를 지갑에 저장하기 위해 다음과 같은 명령어를 입력합니다.$ cleos wallet import-n : 옵션을 사용하면 지갑의 이름을 지정합니다. 지정하지 않는다면 기본 생성된 default 지갑으로 지정됩니다.위 명령을 입력 하면 key 가 임포트 되었다는 결과를 확인 할 수 있습니다.** 만약 지갑을 Unlock 한 상태가 아니라면 ‘private key: Error 3120003: Locked wallet’ Exception 이 나옵니다.unlock 을 위해 다음 명령을 실행한 후 wallet 생성시 저장했던 Key를 입력하여 Unlocked 상태로 만들어 줍니다.$ cleos wallet unlock password: Unlocked: default(Optional) 지갑에 저장된 Key 리스트 확인다음 명령어를 입력하여 지갑에 key 가 잘 import 됐는지 확인합니다.$ cleos wallet keys계정 생성eosio.token 이라는 이름으로 계정을 생성하도록 하겠습니다.** 지갑과 Key 그리고 계정에 관해서는 Hexlant 미디움에 게재될 예정입니다.$ cleos create account eosio eosio.token EOS63kstp8kthzJY3rAotp1LAxUDbWk4MywReG578R2ddbktrDHYKcreator : eosioaccount name : eosio.tokenowner key : 지갑에 import 된 keyAdditional본 포스팅은 local 환경에서 빌드 후 System Contract 들이 적용되지 않은 상황을 가정하였습니다. 만약 Public Network 환경에서 접속 시 eosio 와 eosio.token을 사용할 수 없습니다.또한 계정이름은 다음과 같은 규칙을 따릅니다.- 12문자- 12345abcdefghijklmnopqrstuvwxyz 만 사용 가능** 만약 ‘Error 3090003: provided keys, permissions, and delays do not satisfy declared authorizations’ 에러 발생 시 eosio 에 대한 key 를 지갑에 import 해야 합니다.eosio 에 대한 정보는 다음과 같습니다.public key: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CVprivate key: 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3위 과정을 모두 마쳤다면, EOS 지갑과 키 그리고 계정에 대한 권한을 모두 가지고 있는 상태가 됩니다. 다음 포스팅에서는 이 계정을 사용 하여 Token 을 발행하는 방법을 알아보도록 하겠습니다.감사합니다#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심
조회수 1618

[앵커리어랩]연구보고서 개발자 '노선빈'

오늘 만나본 앵커리어의 팀원은!바로 앵커리어의 숨겨진 하드캐리어 개발자 노선빈(a.k.a 메-쓰:수학)군 입니당!컴퓨터와 대화하는 것을 가장 좋아하는 줄 알았던 그와인터뷰를 빌미로 오랜시간 이야기를 나누었습니다!그 결과... 의외로 수다떨기를 좋아하는 타입의 선빈씨!(나 촉 되게 좋아~~~ 선빈씨 다 들켰어~~~)그럼 앵커리어랩 네번째 인터뷰 시작합니다!(ps. 내일(10월 13일)이면 예비군을 떠나시는 선빈씨에게 이 포스팅을 바칩니다! 예비군 화이팅!)INTRO. 인사밍케터) 간단한 자기소개 부탁드려요^^메-쓰) 자기소개? 이럴 수가.어려워요. 아무렇게나 하면 되나요? 양식이..? 음….팀에서 개발을 하고 있으며, Front-end를 맡고 있고, 아주 약간의 Back-end를 맡고 있습니다.밍케터) 임하는 각오는요?메-쓰) 각오는… 각오라기보다는 지금의 마음 상태가 무섭네요.밍케터)) 마케팅팀이 무서우신 건가요?!메-쓰) 아니 아니 아닙니다.인터뷰가 무서운 거죠.아시다시피 저는 말을 많이 하는 사람이 아니라서. 이런 자리가 익숙하지 않죠. 제1장. 안경_★po코딩wer★밍케터) 하시는 일 소개 부탁드려요.메-쓰) 앵커리어에서의 저의 업무!위에서 한 것 같지만, 다시 하죠. 조금 더 구체적으로.웹 사이트를 만들고, 페이지를 구성하는 일을 합니다.디자이너께서 결과물을 만들어 냈을 때,실제로 웹 사이트를 그렇게 보이게 만드는 일을 하고 있습니다. 밍케터) 선빈 씨는 코딩 언제부터 시작하셨나요?메-쓰) 음. 그게 초 6 때죠.대표께서 초 5 때부터 혼자 코딩을 했었어서, 근데 제가 친구 였어서, 저를 꼬셔서,'같이 해보지 않을래?' 해서 하게 됐죠?(선빈씨 음성지원 中)6학년 때 정보 올림피아드 나가고 하면서 컴공과에 가서 프로그래머를 해야겠다라고 생각했었는데, 고등학생때 수학이 재미있어서 물리학과로 진학했습니다.그런데 올해 초에 새해를 맞아 오랜 친구인 대표께 전화를 했는데, ‘새해 복 많이 받아라’라는 말씀과 함께 '개발자 필요한데 같이할래?' 이러셨죠.그래서 올해부터 갑자기 다시 직업으로 삼게 되었습니다.   밍케터) 공백기가 길었는데 감을 잃지는 않으셨나요?메-쓰) 예전이랑 컴퓨터 언어도 다르고 분야도 달라서 “완전 능숙해” 이 정도는 아닙니다.밍케터) 코딩이 가장 즐거운 순간은 언제인가요?메-쓰) 1. 버그 없이 잘 돌아갈 때.2. 어떻게 해야 할지 바로 떠올랐을 때.3. 알 수 없는 버그가 날 괴롭히지 않을 때.밍케터) 물리 vs 코딩. 어떤 것이 더 재미있나요?메-쓰) 물리가 재밌습니다…아 각각의 재미가 다르죠(다급).물리는…..네이쳐를 알아가는 것은 흥미롭잖아요? 안 그렇습니까?밍케터) (.....전 아닌 것 같습니다만….)메-쓰) 개발은 문제를 해결해 나가는 재미가 있습니다. 이런 방식으로 하고 싶은데 이럴 때는 어떻게 해야 할까 같은 문제들이요. 둘 다 재미있습니다. 흥미롭죠. 밍케터) 게임 좋아하신다고 하던데, 인생게임 있나요?메-쓰) 인생게임이라…. 도타라는 게임이 있는데, warcraft3 라는 게임의 mod 같은 건데…밍케터) 그럼 이 인터뷰는 어떠세요? 핵심을 찔러서 평가해주세요.메-쓰) 분위기는 좋습니다. 굉장히 자유롭네요. 자유분방. 편한 분위기. 밍케터) 모드요? 모드…???메-쓰) 스타크래프트를 해봤으면 유즈맵이라고 하면 딱 아실텐데… 뭐 어쨌든.DOTA(‘디-오-티-에이’라고 친절하게 풀어서 읽어주는 선빈 씨)라는 게임이 있습니다. 지금 롤과 같은 장르의 AOS의 시초가 된 게임입니다. 밍케터) 가장 자랑하고 싶은 코딩 결과물 알려주세요.메-쓰) 없으면 안 되는 거죠?밍케터) 웬만하면 있으시면 좋겠습니다.메-쓰) 제가 솔로로 만든 것 중에 ‘오 굉장해. 오 훌륭해’ 이런 것은 없고, 참여한 것 중에 훌륭한 것은 지금 만든 자소설닷컴이죠?개인 작업물 중에 생각나는 것은…예-엣날에 코딩 처음할 때. 야구게임이라고 아시나요?야구게임 프로그램 예시(feat.다른 개발자).jpg고1 때 굉장히 유행했는데 옛날에 하던 코딩이 생각나 심심할 때 만들어서 학교에서 풀었죠.사실 코딩하는 사람들이 보면 굉장히 별거 아닌데 학생들이 “오 신기해. 쩔어. 있어보여” 이랬던 기억이 나네요. 밍케터) 대표님과 친구인지 얼마나 되셨죠?메-쓰) 18년 이죠.밍케터) 네 그리고 초, 중, 고 계속 같이 다니셨고 한 공간에서 코딩하시고...혹시 라이벌 의식…?메-쓰) 아, 전혀 없습니다.저는 예전부터 라이벌 의식 뭐 이런 것을 가져본 적이 없어서. 좀 있었어야 할 것 같은데.밍케터) 근데 굳이 필요가 없어 보이네요. 다 잘하시니까…알아서 좋은 학교에 입학하시고...(...조용히 먼 산을 바라보며 반성하는 마케터들)제2장. 입_난 핵심만 찌른다밍케터) 팀원들이 선빈 씨에 대해 핵심을 찌른다는 평가를 했습니다. 혹시 본인만의 남다른 기준이 있으신가요?메-쓰) 팀에 합류했을 때, 대표님이 저에게 날카로운 평가를 많이 기대하는 것 같았는데... 합류하고 보니 이미 굉장히 잘하는 분이 있으셔서. (조용히 pm님을 쳐다본다.) 서로 뿌듯해하는 하드캐리어들.jpg메-쓰) 하드캐리어 이십니다. 굉장히 날카로운 분이세요.상위 호환이 가능합니다.밍케터) …...네? 상위호환이요? 그게 뭐죠?메-쓰) 음…..밍케터)(상위호환을 이해하지 못하는 것은 나의 잘못일까, 선빈씨의 잘못일까, 고급언어를 사용할 줄 아는 능력의 차이인가….또 다시 먼 산을 바라보는 밍케터...)메-쓰) 여기저기서 쓸 수 있는 성격이라는 말입니다. 더 나으신 분이죠.상위 호환이 가능한 pm님) 선빈씨 시사잡지도 계속 주기적으로 사서 보시잖아요?메-쓰) 주기적이진 않지만 내킬 때…?상위 호환이 가능한 pm님) 주변에 보면 시사 잡지를 구독하는 이공계생을 본 적이 별로 없어요.시사에 관심이 많으신가요? 나아가서 정치 사회적인 부분까지 관심이 많으신가요?메-쓰) 음, 관심이 없진 않죠. ...왜 관심이 생겼을까요?시사주간지는 딱히 주기적으로 사진 않지만 내킬 때 사는데...지하철 편의점 지나다니다 보면 ‘이번 주는 저런 이슈가 있군.. 사볼까?’ 이럴 때 삽니다.뭔가 인생, 삶에 있어서 충실함이 떨어지거나 나태함이 차오를 때?열심히 사는 사람의 느낌을 낼 때 사는 것 같네요.밍케터) 그럼 이 인터뷰는 어떠세요? 핵심을 찔러서 평가해주세요.메-쓰) 분위기는 좋습니다. 굉장히 자유롭네요. 자유분방. 편한 분위기.제3장. 피카츄_개발팀의 핵심멤버밍케터) 선빈 씨 방에는 피카츄 친구들이 많잖아요? 혹시 피카츄가 최.애.캐(최강 애정 캐릭터)인가요?메-쓰)  캐릭터로 말할 것 같으면... 딱히 딱 떠오르는 것은 없습니다.밍케터) (당황… 최.애.캐인줄 알고 질문을 준비했는데…!)메-쓰) 피카츄는 포켓몬에서 상품 제작이 많이 되고 프로모션이 많잖아요?그래서 가보니까 귀여워서 사온거죠.밍케터)그럼 질문을 좀 바꿔서 드립니다.만약 살아있는 피카츄를 얻을 수 있다면 대표님과 피카츄 중 누구를 선택하시겠어요?세상에 단 하나 뿐이고, 100만 볼트도 쓸 줄 아는 피카츄라면?그런데 살아있는 피카츄를 얻으려면 대표님과 연을 끊어야 한다면?메-쓰) 이게 직장이 걸린 문제라서 다른 직장을 찾을 수 있는가가 문제인데요.밍케터) 그럼.. 대표님 자리에 앉아서 일하다가 퇴근할 때면 고개를 돌려 “피카!!!”하고 인사를 해주는 피카츄라면요?퇴근하는 직원들에게 인사하는 대표님_피카츄.jpg상위 호환이 가능한 pm님) 전 피카츄요.인터뷰 당일 대표님께 명품 벨트를 선물받은 문케터 ) 아 저도 피카츄로 하겠습니다. (대표님보다 피카츄)메-쓰) 음… 대표님이 양산에서 잘 살고 계시는 상태에서 연을 끊는 거라면…. 제4장. 마요 시리즈_에너지의 원천밍케터) 선빈 씨의 점심엔 항상 빠지지 않는 것이 있습니다. 한솥 마요네즈 시리즈.특별히 좋아하시는 이유가 있나요?메-쓰) 좋아해서가 아니라 싸서 먹고 있습니다.밍케터) (당황… 마요 시리즈를 좋아하는 줄 알고 질문을 준비했는데…이 인터뷰 호락호락하지 않다)그래도 가장 베스트 마요 하나만 말씀해주세요.인터뷰 당일 대표님께 명품 벨트를 선물받은 문케터 ) 그러지 마요.메-쓰) (무시) 제가 먹어본 것들이 치킨, 치킨샐러드, 닭가슴살 샐러드 등이 있는데 다 그놈이 그놈입니다.재료가 아니라 마요 맛이에요.싼 것을 먹어야 돈을 모을 수 있으니까 싼 걸 먹는 거죠.그래야 건물을 사서 임대를 줄 수 있고 ‘난 이렇게 돈이 많은 남자다’ 이러면서 사치도 부리는 거죠.빅치킨마요를 사 먹는다던가.밍케터) 그럼 우리 점심시간때 빅 치킨마요 사 먹는 분들은 선빈 씨 기준에서 사치 하는 사람이네요? ㅋㅋㅋㅋㅋ인터뷰 당일 대표님께 명품 벨트를 선물받은 문케터 ) 주연 씨? ㅋㅋㅋㅋㅋ상위 호환이 가능한 pm님) 선빈씨 “쟤는 건물 있나?”이러겠네요. ㅋㅋㅋㅋㅋ메-쓰) 최신게임도 풀 옵션으로 돌리고, 뭐 농담이고 돈 많으면 좋잖아요?   밍케터) 평소 과자 간식, 마요 시리즈, 콜라 등등 고칼로리를 즐겨 드시는데 날씬한 몸매 유지 비결이 무엇이죠?메-쓰) 마요가 고칼로리인가요? 먹을 때 칼로리 생각 안 하는데, 왜 살이 안 찔까...많이 안 먹어서 그런 것 아닐까요?저는 사실 먹을 만큼 먹는데 옆에서는 잘 안 먹는다 이런 평이 있긴 하더라고요.메-쓰) 그리고 저는 신체와 관련해서 그런 것을 해보고 싶긴 해요. 마사지? 교정? 교정이겠네요.요가라던가. 필라테스?이것들을 하면 나의 오랜 신체 불균형이 좀 개선되지 않을까 이런 생각이 있습니다.어멋_뒷사람을 못가렸네.jpg  제5장. 키. 손. 팔. 속눈썹… 메이비 전신_가장 자신 있는 부위밍케터) 마지막으로 가장 자신 있는 부위 알려주세요.메-쓰) 부위? 신체? 허….글쎄요.저는 막 몸이 이렇게 자신있는 스타일은 아닌데…그런데 살면서 들어본 신체에 대한 칭찬이 몇 가지가 있는데.밍케터) 몇 가지? (분명 몸이 자신 있는 스타일은 아니라 해놓고 천역덕스러우시군) 메-쓰) 네 몇가지는 누구나 있죠.우선, 키가 크다. 근데 이건 부위라고 하기엔 뭐하네요.왠지 전신을 이야기하는 것 같아서. "너는 손이 이쁘네. 손가락이 이쁘네. 손톱이 이쁘네."이런 이야기도 좀 들어 봤구요. 팔이… 이건 칭찬인지 모르겠지만, "여자들이 원하는 팔 형태네." 아니면 "속눈썹이 기네."  상위 호환이 가능한 pm님) 맞아요. 선빈씨 속눈썹 꼭 찍어야 해. 진짜 길어요. 장난 아니죠.메-쓰) 중학교 때 부터 여자애들이 “어우 속눈썹 굉장하다.” 이런 이야기를 몇 번 들었습니다. 메-쓰) 추가로 다리가 길다 정도?결론. 앵커리어 공식질문 1. 나에게 앵커리어란?뭐. 직장이죠.2. 자소설닷컴을 한 마디로 표현하자면?뭐. 우리 회사 서비스죠.#앵커리어 #팀원소개 #인터뷰 #팀원자랑 #기업문화 #조직문화
조회수 7287

Kafka 모니터링

Kafka 도입 이후에 점진적으로 모니터링을 개선해나간다. Kafka와 그 제반 환경에 대해 이해한만큼 모니터링을 구성하고 모니터링 시스템에서 피드백을 받아 다시 학습하고 그렇게 배운 것을 토대로 다시 모니터링을 구성한다. 그 과정을 따라 나가며 Kafka 를 어떻게 모니터링하면 좋을지 알아보자.프로세스 모니터링아무래도 가장 기초적이면서 중요한 지표는 Kafka 프로세스가 잘 살아 있는지 확인하는 것이다. 다섯 대로 구성한 클러스터라면 상시 Kafka 프로세스가 확인되어야 한다. 만약 Kubernetes의 StatefulSet으로 Kafka 클러스터를 구성한 경우라면 Kafka 프로세스 다섯과 프로세스 모두를 엮는 서비스, 그러니까 로드밸런서 하나를 포함해 총 여섯 개의 프로세스를 확인해야 한다. DataDog(통칭 멍멍이)을 이용해 모니터링하는 경우라면 다음과 같이 설정하면 된다.Monitoring Kafka ClusterKafka는 Zookeeper를 이용하므로 ZooKeeper 역시 동일하게 모니터링하면 된다.DataDog을 이용한 메트릭 모니터링`dd-agent는 Kafka 관련 메트릭을 Broker, Consumer, Producer 세 측면에서 수집한다.Monitoring Kafka with DatadogMonitoring Kafka performance metrics위의 두 문서가 Kafka 모니터링의 상세한 측면을 기술하는데 멍멍이를 이용하지 않더라도 꼭 한번 읽어볼만하다. 두 문서가 매우 훌륭하므로 이 글에서는 Kubernetes 환경에 초점을 맞춰 주목할 점만 살펴본다.Kubernetes 환경에서 멍멍이 에이전트는 보통 PetSet으로 구성한다. 말인즉 Kubernetes Worker 한 대마다 에이전트를 한 대씩 띄워서 Worker 안에서 작동하는 모든 도커 인스턴스의 메트릭을 수집한다. 일단 에이전트를 설정하고 나면 아래와 같이 Kafka 모니터링이 정상 작동하는지 확인하면 된다.kube exec -it dd-agent-17vjg -- /opt/datadog-agent/agent/agent.py info kafka ----- - instance #kafka-kafka-0.broker-9999 [OK] collected 46 metrics - instance #kafka-kafka-1.broker-9999 [OK] collected 46 metrics - instance #kafka-kafka-2.broker-9999 [OK] collected 46 metrics - Collected 138 metrics, 0 events & 0 service checks Emitters ======== - http_emitter [OK]Broker의 경우는 설정하기가 비교적 쉽다. Kubernetes에서 Kafka 같은 Stateful cluster는 StatefulSet으로 구성하게 되는데 이때 호스트 주소가 kafka-0, kafka-1 같이 예측 가능한 이름으로 정해지기 때문에 kafka.yaml을 미리 작성해두기 쉽다.instances: - host: kafka-0.broker port: 9999 # This is the JMX port on which Kafka exposes its metrics (usually 9999) - host: kafka-1.broker port: 9999Producer와 Consumer 모니터링은 이와는 다르다. 구현하기 나름이지만 Producer 또는 Consumer가 되는 응용프로그램은 Stateless cluster일 때가 많고 그런 경우에는 Kubernetes에서 Deployment로 클러스터를 구성한다. 이때는 StatefulSet인 경우와 달리 호스트 주소가 worker-903266370-q3rcx와 같이 예측하기 힘들게 나오므로 에이전트에 미리 설정을 넣을 수가 없다. 상당히 까다로운 문제이다.Consumer 모니터링Kafka의 설계는 매우 단순하면서도 강력해서 감탄하곤 한다. 하지만 복잡한 문제를 단순하게 풀어냈다고 해서 이를 둘러싼 환경을 제대로 모니터링하는 것도 쉽다는 뜻은 아니다. 특히 Consumer groups이 제대로 제 몫을 하고 있는지 파악하기는 더 어렵다. Consumer group마다 모니터링 체계를 갖추자니 번거롭다. 게다가 그런 번거로움을 극복하더라도 Kafka에 문제가 있는 경우를 탐지하기는 여전히 어렵다. 예를 들어 Consumer에게 가야 할 메시지 중 5%가 실제로는 전달되지 않는다 하면 이를 Consumer가 알기는 어려울 것이다. 이 외에도 Consumer 측 모니터링이 엄청나게 까다로운 문제임은 Burrow: Kafka Consumer Monitoring Reinvented에서 잘 밝혔다.Burrow: Kafka Consumer Monitoring Reinvented에 등장하는 Burrow는 Kafka를 세상에 내놓은 LinkedIn 엔지니어링 팀이 개발한 Kafka 컨슈머 모니터링 도구이다. 커뮤니티에서는 대체로 현존하는 가장 뛰어난 모니터링 도구라고 인정하는 분위기이다. 그러니 다른 도구도 많지만 우선 Burrow로 모니터링을 강화하기로 한다.Burrow로 Consumer 모니터링하기Burrow는 Dockerize가 잘 되어 있기 때문에 사용하기 어렵지 않다. LinkedIn이 공식 도커 이미지까지 제공했더라면 더 좋겠으나 GitHub에 Dockerfile과 docker-compose.yml을 올려놓아서 도커를 잘 아는 사람이라면 큰 어려움 없이 바로 설정하고 설치할 수 있다. 컨테이너 환경의 관례대로 주요 설정을 환경변수로 미리 빼놨으면 더 좋았겠지만 …알람 받기Burrow는 문제가 생겼을 때 알람을 발송하는 기능이 있다. 위키에는 이메일 알람과 HTTP 알람(Webhook)을 어떻게 설정하는지 설명한다. 그런데 Burrow 소스코드를 살펴보면 문서화되지 않은 알람 기능도 있으니… 바로! Slack 알람을 제공한다. 아직 공식 문서가 없고 소스코드도 godoc 관례에 맞춰 설명해놓은 부분이 전혀 없기 때문에 소스코드를 읽거나 GitHub 이슈에서 논의된 내용을 토대로 설정해야 한다.[slacknotifier] enable=true url=https://hooks.slack.com/services/xxxx/xxxxxxxxxx group=local,critical-consumer-group group=local,other-consumer-group threshold=0 channel="#general" username=burrower interval=5 timeout=5 keepalive=30멍멍이로 메트릭을 꾸준히 수집하고 이슈가 생겼을 때 알람을 받고자 한다면 packetloop/datadog-agent-burrow를 이용하면 된다.This plugin will push the offsets for all topics (except the offsets_topic) and consumers for every kafka cluster it finds into Datadog as a metric.멍멍이 에이전트에 필요한 파일과 설정을 넣고 나면 아래와 같이 메트릭이 수집된다.kafka.topic.offsets 와 kafka.consumer.offsets 이렇게 두 개의 메트릭만 수집하지만 각 메트릭을 cluster, topic, consumer 세 개의 토픽으로 세분화하기 때문에 실제로는 꽤 다양한 지표를 멍멍이에서 확인하고 이용할 수 있다.알`람 설정하기앞서 살펴봤지만 프로세스 모니터링 등은 어렵지 않다. 클러스터에서 한대라도 빠지면 바로 알람을 받는다. 끝!하지만 그 외의 지표는 알람의 기준을 설정하기가 힘들다. 예를 들어 Burrow의 kafka.topic.offsets 값이 600이면 정상인가? 그렇다면 700은? 또는 400은? 도무지 감을 잡을 수가 없다. 이럴 때는 멍멍이가 제공하는 Outlier detection기능으로 알람을 걸면 쉽다. 이 기능은 쉽게 말해 평소와 다른 행동을 감지했을 때 알람을 보낸다. 그러므로 정상의 범위를 확실하게 모를 때 아주 유용하다.설정 자체는 DBSCAN 또는 MAD라는 알고리즘이 등장하는 것만 빼곤 여타의 모니터링과 다르지 않기 때문에 매우 쉽다.참고 문헌How to Monitor KafkaCollecting Kafka performance metricsOriginally published at Andromeda Rabbit.#데일리 #데일리호텔 #개발 #개발자 #개발팀 #인사이트 #기술스택 #스택소개 #Kafka
조회수 1348

코인원 마이페이지가 더욱 더 새로워졌습니다 :) - 유저플로우셀 팀터뷰

웹서비스에서 나만의 서비스 이용내역과 개인정보를 확인할 수 있는 공간을 ‘마이페이지'라고 하죠. 유저들은 마이페이지를 통해 나의 상태를 체크하며 해당 서비스에 좀 더 애착을 갖기도 합니다. 이번에 코인원 마이페이지도 더욱 더 새로워지면서 애정이 가득해졌다는 유저들의 제보가 속속 들어오고 있어요!오늘은 코인원 마이페이지를 새롭게 탄생시킨 유저플로우셀 예은님, 정유님, 현진님, 종헌님과 함께 마이페이지의 모든 것을 파헤쳐보겠습니다.코인원 유저플로우셀은 트레이딩 영역을 제외한 전반적인 서비스 영역을 담당하고 있습니다. 각 서비스에 대한 유저 경로 동선을 만들고 서비스를 제공하며, 누구나 거래를 하고 싶은 코인원을 만들고 있답니다. :-)Q. 안녕하세요, 유저플로우셀 여러분. 자기소개와 함께 현재 업무를 소개해주세요!예은 : 유저플로우셀에서 서비스 기획자로 일하고 있는 지예은입니다. 저는 코인원 유저들이 겪는 문제상황과 UX트렌드 분석을 통해 기존의 서비스를 개선하고 고도화하고 있어요.정유 : 프로덕트 디자이너로 일하고 있는 이정유입니다. 유저플로우셀은 유저와 거래소를 이어주는 모든 페이지를 담당하고 있어요. 저는 기획자들과 함께 유저의 니즈를 페이지에 UI(User Interface)적으로 어떻게 반영할지 고민하고, 이를 디자인 시스템에 녹여 시각적 일관성을 전달합니다.  현진 : 프론트엔드 개발자로 불철주야 개발 중인 박현진입니다. 프론트엔드는 한마디로 코인원 프로덕트에서 실제로 유저들에게 보여지는 웹화면이에요. 저는 유저들에게 보이는 영역을 책임지며 프로그래밍하고 있습니다.종헌 : 웹 API를 담당하고 있는 백엔드 개발자 김종헌입니다. 프론트엔드가 유저에게 보이는 영역을 담당한다면, 저는 보이지 않는 곳인 백엔드에서 입출금 서비스, 거래기록, 개인정보 등 코인원의 다양한 서비스와 유저를 연결하고 있어요.Q. 이번에 마이페이지 개선이 대대적으로 진행되었습니다. 어떤 계기와 방향성으로 개선하게 되었나요?예은 :  마이페이지 개선은 유저의 고충을 파악하기 위한 코인원 고객센터 인터뷰에서 시작되었습니다. 거래소 이용에 필요한 인증, 계정 보안에 대한 관리가 익숙하지 않은 유저들의 ‘페인 포인트(Pain Point)’를 발견했거든요. 서비스 기획자, 디자이너, 개발자가 함께 모여 UI나 정보로 사용자에게 도움을 주고 CS비용을 최소화 할 수 있는 방법을 고민하기 시작했습니다. 우선, ‘마이페이지'는 코인원 서비스를 이용하는 유저 개개인을 챙겨주는 공간이라고 생각합니다. 이번 개선 과정에서 가장 중점을 둔 부분도 ‘고객을 챙겨주는 마이페이지' 경험을 전달하는 것이었어요. 이렇게 설정된 방향성에 따라 유저들의 상태별로 필요한 상황을 안내하도록 구성했습니다. 한마디로 ‘유저 맞춤형 마이페이지’로 진화한겁니다!▲ 더욱 더 새로워진 코인원 마이페이지정유 : 이전의 마이페이지는 엉켜있는 플로우로 인해, 유저가 어떤 상태인지, 어떤 인증과정을 거치고 있는지 인지하기가 힘들었습니다. 목적을 달성하기 위해 마이페이지에 접속했지만 목적 달성을 끝마칠 수 없었죠. 먼저 흩어져 있는 기능, 정보, 구조들을 그룹핑하며 플로우를 개선하는 작업을 시작했어요. 아이데이션 과정을 거치면서 마이페이지를 ‘내 서랍, 내 방' 등 나만의 정체성을 확인할 수 있는 키워드로 정의했습니다. 그리고 키워드를 확장시켜 ‘나의 데이터'를 한 눈에 관리할 수 있는 대시보드 형태의 디자인을 지향하게 되었어요. 결과적으로 현재 마이페이지에는 나의활동, 개인정보관리, 인증단계 총 3 개의 탭으로 위계를 설정했습니다. :D▲ 코인원 거래소 인증단계가 훨씬 간편해졌습니다!Q. 기술적으로는 어떤 변화가 있었을까요?현진 : 마이페이지를 포함해서 코인원 웹 프로덕트에 기술부채(Technical Debt)가 조금씩 쌓여 있었어요. 이 부분을 덜어내기 위해 마이페이지를 개선하면서 ‘기획/디자인/개발’ 삼박자로 변화를 주는 리빌딩(Re-building)을 진행했습니다. 덕분에 기술적으로 관리 포인트가 많이 줄었어요. 이제는 웹 유지/보수가 좀 더 용이하게 되었답니다.종헌 : 그 동안 코인원 웹은 하나의 비대한 서비스로 운영되었습니다. 하나의 서비스가 덩치가 점점 커지다 보니 개발자가 서비스 로직을 온전히 이해하기 어려웠어요. 웹을 유지하고 보수하거나, 새로운 기능을 추가하는 것도 쉽지 않았습니다. 그래서 하나의 비대한 서비스를 여러 개의 작은 서비스로 나누는 작업인 리빌딩을 진행했어요. 여러 작은 서비스로 분리하고 책임 영역을 나누면서 서비스 로직에 대해 제대로 이해하고 체계적으로 코드를 작성할 수 있게 되었습니다.    Q. 마이페이지 개선 전과 후, 달라진 점을 말씀해주세요.예은 : 코인원 마이페이지는 이전보다 유저들에게 더욱 친근하게 다가가고 있습니다. 마이페이지의 콘텐츠가 유저의 상태에 맞춰 변화하며, 유저마다 다음 인증 과정이나 활동 내역을 다르게 안내합니다. 유저가 기능을 먼저 찾지 않아도, 마이페이지가 길을 찾아주는 가이드의 형태를 띄고 있어요.또한 인증단계 별로 수수료나 회원등급이 달라지는데, 유저들이 하나하나 가이드를 보며 찾아볼 수는 없다고 생각해요. 한눈에 자신의 상태를 파악할 수 있도록 UI를 활용하는 것이 중요한 부분이죠. 마이페이지의 개선된 UI로 유저가 코인원의 서비스 정책을 한층 더 깊게 이해하는데 도움이 되었으면 해요.정유 : 유저가 코인원 프로덕트와의 관계성을 인지할 수 있는 디테일들이 추가되었습니다. 가장 대표적인 예시로는 ‘코인원과 함께한 지 000일째 입니다.’라는 문구가 있겠네요. 코인원 유저들에게 ‘챙겨준다'라는 느낌을 전달하기 위해 정말 많은 회의와 아이데이션을 거쳤습니다. 그 과정 중 나왔던 아이디어인데 이번에 반영하게 되었어요. ‘제품’보다는 ‘서비스'로서 느껴질 수 있도록, 대화하는 느낌을 잘 살려주는 포인트이기에 매우 뿌듯했죠.▲ 심...심쿵....!!!!!현진 : 개발자 입장에서 바라봤을 때, 페이지 애니메이션이 가장 좋았어요. 페이지 애니메이션은 웹페이지가 다른 웹페이지로 이동할 때 발생하는 애니메이션을 말합니다. ‘툭' 하고 넘어가는 것이 아니라 ‘sha~(?)’ 하게 넘어가는 느낌이라고 할까요. 페이지와 페이지 사이가 하나의 관계성을 가지고 넘어가게 됩니다. 유저들은 ‘암호화폐 거래소에서 마이페이지에 이런 디테일한 부분까지 신경쓰고 있구나’를 느낄 수 있을거에요. 또한 에러메시지, 경고메시지와 같은 피드백 인터랙션도 정교해졌어요. 사용자와 교감할 수 있는 쪽에 코인원만의 감성이 잘 버무려졌습니다.종헌 : 이전의 코인원 프로필 서비스는 사용빈도가 높지는 않았어요. 그라바타(Gravatar)라는 외부서비스를 사용했는데, 이것을 사용하지 않는 유저들에게 친숙하지 않았거든요. 이제는 코인원에서 프로필 이미지를 정해두고 원하는 이미지로 클릭해서 쉽게 변경할 수 있게 설정했어요. 참고로 프로필 이미지를 설정하는 것이 보안측면에서도 좋습니다. 예를 들어, 은행에서는 프로필 이미지를 설정하면 바로 내가 사용하는 계정인지 아닌지를 알 수 있어요. 코인원에서도 프로필 이미지를 설정하면 내가 가입한 계정인지 아닌지 식별이 가능합니다.▲ 프로필 사진 설정 기능도 많이 이용해주세요 :)Q. 마이페이지의 개선 작업 과정에서 많은 고민이 있으셨을 것 같아요. 가장 중점적으로 생각했던 부분이 있었나요?정유 : 가장 중점이 되었던 부분은 서비스를 이용하는 유저 개개인의 상태를 반영하는거였어요. 유저별로 동일한 정보가 아닌 맞춤형 정보를 제공하기 때문에 한 페이지 안에 들어가는 정보의 위계가 상태값에 따라 계속 변하는 것이 관건이었습니다. 예를 들어, 마이페이지에는 나의 정보를 업데이트하기 위한 많은 버튼들이 들어갑니다. 그럼 유저 케이스별로 중요한 정보를 바꿔보면서 어떤 버튼이 가장 위계가 높은지 고민하고 계산해요. 이러한 과정을 거치면서 유저의 상태값을 쉽게 알려주고 변경할 수 있는 디자인이 완성되었습니다. 예은 : 기존부터 유저 인터뷰를 진행하며 ①신규 유저 ②타사 이용 유저 ③거래소 이용에 문제를 겪고 있는 유저 ④코인원을 오래 이용해준 고마운 유저 케이스까지 다양한 상황에 놓여있는 유저들에게 만족스러운 UX 경험을 드리기 위해 고민해왔습니다. 특히 운영지원셀과 코인원 고객센터 CS로 인입되는 주요 인터뷰들을 중점적으로 수집하여 인증과정에 문제가 되는 것들을 모아서 개선회의를 해왔어요. 이외에 마케팅, 프로덕트쪽도 함께 서비스를 제공하는 공급자 입장에서의 니즈도 취합해 마이페이지에 반영할 수 있었습니다.▲ (절대 설정샷 아니에요) 훈훈하게 회의중인 유저플로우셀!Q. 혹시 개선된 마이페이지를 이용한 코인원 고객들의 후기도 있었나요?예은 : 개선된 마이페이지로 바뀐 지 얼마되지 않아, 유저의 피드백을 직접적으로 접하지는 못했어요. 대신 정량적인 부분에서 여러 수치들이 올라간 것을 확인할 수 있었습니다. 대략적으로 재방문자의 UV(Unique Visitor)수가 개선 전과 대비해서 약 70%정도 크게 증가했습니다. 이전에는 회원가입을 끝마치고 인증과정 중에 페이지를 이탈한 유저도 보였지만, 개선된 후에는 마이페이지 탭 이용빈도가 크게 올라갔습니다. 마이페이지가 좀 더 원활한 거래소 서비스 이용을 위한 가이드 역할을 해주길 기대하면서, 지속적으로 니즈를 관찰하고 개선해 나갈 예정입니다.Q. 마이페이지 이외에도 기억에 남는 유저플로우셀의 프로젝트가 있나요?예은 : 코인원의 수익현황을 한 눈에 볼 수 있는 자산탭이 기억에 남아요. 그 동안 코인원 유저들이 수익률을 확인할 수 있는 기능을 많이 요청했었는데, 팀원들이 함께 고민하여 새로 개편한 기능이라서 그 의미가 컸어요.정유 : 저는 실질적으로 프로젝트에 돌입하기 전에 진행했던 코인원 유저 인터뷰가 가장 기억에 남아요. 인터뷰 내용이 개선점으로 가득찰 줄 알았는데, 응원의 목소리를 전달해주셨거든요. 더 열심히 UI 디자인을 해야겠다는 의욕을 불타오르게 해주었어요!현진 : 코인원 웹프로덕트를 사용하시는 분들이 눈치 채셨는지 모르겠지만, 마이페이지 이전부터 진행해왔던 리빌딩 프로젝트(랜딩, 거래소, 프로차트, 코인원 톡 등)들이 기억에 남아요. 사실 마이페이지 이전 리빌딩 프로젝트들은 기술적으로만 접근하다보니 우여곡절이 많았어요. 그래도 마이페이지 리빌딩은 업무적으로도 많이 배우고, 기술 뿐만 아니라 전체적으로 변화한 것이 보여 저 또한 성장하는 시간이었습니다.종헌 : 이외에도 유저플로우셀은 UX개선을 여러 프로젝트와 함께 진행하고 있습니다. 정신없긴 하지만 개발요소도 새롭고 다이나믹한 것이 많아서 즐겁게 하고 있습니다!▲ 화기애애하게 UI 시안을 보고 있는(?) 유저플로우셀Q. 코인원에서 디자이너 그리고 개발자로 일하는 큰 장점은 무엇인가요?예은 : 코인원에선 셀마다 다른 직무의 인원들이 빠르게 소통하여 의사결정하는 목적조직 형태로 일합니다. 중간중간 기획리뷰, 디자인리뷰 과정을 거치면서 더 꼼꼼하게 일하고, 다른 직무에 계신 분들의 작업도 공유하고 있어요. 거래소에서 일어날 수 있는 다양한 문제 상황을 긴밀하게 대응하고 있죠.정유 : 현재 코인원은 ‘셀(Cell)’이라는 목적조직 형태입니다. PM, 개발자, 디자이너가 한 조직에 속하다보니 Output 나오는 속도가 매우 좋아졌습니다. 또한 여러 직군이 함께 팀웍을 맞추다보니 서비스를 다양한 각도에서 바라볼 수 있고, 이는 디자이너로서 서비스 이해도를 높이는데 굉장히 좋은 환경이라고 생각해요.  종헌 : 코인원은 개발자도 기획 단계부터 적극적으로 참여하여 프로젝트를 설계하고 있습니다. 이로 인해 개발을 하다 예기치 않은 변수가 생기는 일이 거의 발생하지 않아요. 또한 정기적으로 회고를 하며 프로세스의 문제점을 도출해내고, 개선을 위해 다양한 시도를 해볼 수 있다는 것도 장점입니다. 현진 : 현재 코인원 기술본부는 트렌디한 기술을 곳곳에 사용하고 있어요. 기술에 민감하게 반응하는 프론트엔드 개발자분이 코인원에 온다면 기술적으로 매우 만족하실거에요. Q. 앞으로 이루고 싶은 목표는 무엇인가요?예은 : 암호화폐 거래소는 UX를 기획하기에 매우 도전적인 분야입니다. 블록체인 기술이 곳곳에서 화제가 되고 있지만, 아직 업계의 워딩이나 사용에서의 유저 친화적 성숙도가 높지 않은것 같아요. 앞으로의 목표는 누구나 쉽게 거래할 수 있는 암호화폐 거래소를 만드는 것입니다. 점점 더 발전하는 코인원의 모습을 많이 기대해주세요!정유 : 코인원 UI에는 아직 블록체인 공급자적 시선이 많이 담겨있어요. 예를들어, 개발자가 아니면 이해하기 어려운 용어나 UI가 남아있는 부분이 있거든요. 이를 디자인적으로 해소하고 싶습니다. 유저가 갖고 있는 암호화폐 거래 장벽을 낮추고, 코인원의 가치가 잘 반영된 프로덕트를 만드는 것이 목표에요. 종헌 : 코인원에서는 트레이딩 이외에도 여러가지 서비스들을 유저에게 제공하는 다양한 시도를 하고 있어요. 저는 다양한 서비스들을 연결하면서 서비스의 안전장치를 견고하게 쌓아올리고 싶네요. 장애 발생에도 끄떡없는 안정적인 코인원을 유저에게 선보이고 싶습니다.현진 : 대한민국에서 적어도 사용성 1위 암호화폐 거래소를 만들거에요. 유저플로우셀에서 마이페이지 이후에도 많은 프로젝트를 준비하고 있거든요. 매주(?) UX가 점차적으로 개선되는 코인원 거래소의 모습을 확인할 수 있을 거에요. 마지막으로 꼭 하고싶은 말이 있는데, 코인원에 많은 개발자분들이 지원해주셨으면 좋겠어요. 아직 업계에 부정적인 인식이 강하지만, 블록체인이 발전하는 과정을 보며 점차 해소될거라고 믿어요. 기술적으로 발전할 가능성이 무궁무진한 곳이니 기술적인 욕심을 채우고 싶은 분들, 함께 성장하고 싶으신 분들 코인원으로 오세요!▲ 코인원 유저플로우셀 많이 기대해주세요!무엇보다도 긍정적인 에너지로 가득찼던 유저플로우셀의 인터뷰를 들어봤어요.코인원 마이페이지에 큰 변화를 가져온 활기찬 에너지, 다들 느끼셨나요?마이페이지 이후에도 다양한 프로젝트를 준비하고 있는 유저플로우셀. 곧 코인원 웹 거래소를 사용하면서 UX적으로 편리한 사용성을 경험할 수 있을겁니다.끝으로, 특별한 문화를 경험할 기회! 코인원 채용에 함께하는 것도 잊지 마세요 :-)

기업문화 엿볼 때, 더팀스

로그인

/