스토리 홈

인터뷰

피드

뉴스

조회수 3568

코인원 개발자는 어떻게 일할까?

코인원의 파이콘 한국 2018 참여기 그 두번째 이야기!코인원의 핵심, 코인원의 자랑 ‘코인원 개발자’들에 대한 이야기를 나눠보려 합니다.지금의 코인원이 있을 수 있는 이유는 바로 더 좋은 프로덕트를 만들어내기 위해 치열하게 고민하는 개발 크루들의 노력이 있었기 때문입니다.코인원은 지난 파이콘 한국 2018 ‘열린공간(Open Speak Talking)’ 세션에 참여했습니다. 이 시간을 통해 그동안 코인원 개발 크루들이 축적한 지식과 경험 그리고 노하우를 공유했어요. 파이콘이 개발자들을 위한 행사인만큼, 코인원의 개발문화와 환경, 채용 원칙에 대한 심도깊은 이야기가 오고갔답니다.그래서 예고했던대로 코인원 개발자들의 ‘Mini Interview’를 통해 개발 크루들은 기술본부를 어떻게 만들어나가고 있는지 자세히 알려드릴게요 :-)'파이콘 한국 2018' 후기 1탄 현장스케치▼코인원X'파이콘 한국 2018' 현장스케치!파이썬 개발자들의 즐거운 축제, ‘파이콘 한국 2018’이 지난 19일 성황리에 막을 내렸습니다. 이번 행사...blog.naver.com지난 8월 19일 진행된 파이콘 열린공간 현장, Open Speak Talking!진우님(CTO)을 집중 심문하고 있는 피플팀 수장 대경님코인원 기술본부는 어떻게 구성되어 있나요?진우님(CTO) : 안녕하세요, 코인원 CTO(Chief Technical Officer, 최고기술책임자) 이진우 입니다. 현재 코인원 기술본부는 총 8개의 팀으로 이루어져 있는데요. Core팀, Web팀, API팀, APP팀, QA팀, SRE팀, 데이터팀, 기술연구팀으로 나뉘어진답니다. 코인원 기술본부는 사용자에게 편리한 서비스를 제공하기 위해 프로덕트를 만들어나가고 있어요. 먼저, 사용자에게 직접적으로 보여지는 화면인 프론트엔드를 책임지는 ‘Web팀과 APP팀’ 그리고 사용자에게 보이지 않지만 시스템의 안쪽에서 수행되는 백엔드를 책임지며 프론트엔드에서 필요로하는 결과를 제공하는 ‘Core팀, API팀, QA팀, SRE팀, 데이터팀, 기술연구팀’이 인터렉티브하게 일하고 있습니다.저희는 암호화폐 거래소 코인원을 더욱 더 잘 구축하고, 개선하고, 안전하게 운영하는 것을 목표로 삼고 있어요. 또한 개발 뿐만 아니라, 지속적인 블록체인 연구를 통해 거래소에 최신 기술 트렌드를 반영할 수 있도록 항상 고민하죠.파이콘의 질문왕, 웹팀의 경화님 :)저 멍때리는거 아니에요, OST에 집중하고 있는거에요. (feat. 새로찬가로찬님)눈감은거아니에요, 뜬거에요. (feat. 킹갓제너럴대현님)코인원 개발자들은 어떻게 일하나요?경화님 (Web developer) : 코인원 개발자들은 서로 어떤 업무를 하고 있는지 눈으로 트래킹 할 수 있고, 투명하게 업무를 공유할 수 있는 개발환경을 만들고 있어요. 저희는 협업툴로 Jira와 Confluence를 사용하고 있습니다. 협업툴로 업무의 효율성을 높이면서 새로운 개발업무에 집중하는데 많은 도움이 되고 있어요. 또한 협업툴 이외에도 새로운 기술 도입에 긍정적이라 자기주도하에 여러가지 기술을 실무에 적용해볼 수 있다는 점이 매력적이죠.새로찬님 (Engineer) : 주어진 요구사항에 맞게 그대로 개발하기 보다는 요구사항의 필요성에 대해 공감하고 더 좋은 방향으로 만들 수 있도록 기획, 디자인, 개발 모든 단계에서 능동적으로 의견을 제시합니다. 기술본부에서는 빠르게 변화하는 시장상황에 맞추기 위해 매일 아침마다 PM, 개발자, 디자이너가 모여 *데일리스크럼을 진행하는데요, 서로의 Task나 Project 진행상황을 공유하고 최고의 프로덕트를 사용자에게 전달하기 위해 노력하고 있어요. *데일리스크럼이란? 정해진 시간에 개발크루들이 모여서 어제 했던 일과 오늘의 할 일 등을 공유하고, 다른 개발 크루들이 이야기하는 업무현황을 들으면서 내가 기여할 수 있는 부분과 이슈를 빠르게 파악할 수 있는 자리에요! 대현님 (Engineer) : 서비스를 만드는데 있어 주도적으로 그리고 적극적으로 개발업무를 수행합니다. 예를 들어, 코인원 Live Service에서 Manual하게 처리하고 있었던 이슈들을 자동화하는 프로젝트가 있었는데요. 수동으로 해결할 수 있는 부분이지만, 이를 서비스 기획자분들과 연계해 Task로 만들어 해결방안을 얻을 수 있었습니다. 또한 여기서 그치는게 아니라, 계속해서 코인원 사용자들을 위해 필요한 기능들을 추가하는 프로젝트에 참여하면서 많은 재미를 느꼈죠.왼쪽부터 파이콘의 나이스걸 (윤정님), 개발자 (희수님), ddddeveloper (선우님)개발본부의 공식포즈, 쁘쁘브브브이 종헌님 ㅇ_ㅇV코인원 개발자가 되면 어떤 점들이 좋나요?희수님 (Engineer) : 이전에 경험하지 못했던 거래소 프로덕트를 만들어볼 수 있다는 점이 정말 좋습니다. 저는 원래 암호화폐와 핀테크에 관심이 많았는데요. 코인원에서 거래소 프로덕트를 직접 만들면서 관심 분야에 대해서도 더 많이 알게 되고, 또 이런 서비스를 개발할 때 중요한 것이 무엇인지 고민해보면서 배워가는 것이 정말 많아요. 제가 코인원 합류 이전에 개발한 프로덕트들과는 성향이 많이 달라서 더 재미를 느끼고 있어요!종헌님 (Web developer) : 함께 일하는 모든 개발자 분들이 더 효율적인 개발 프로세스를 만들기 위해 치열하게 고민하는 것을 보면서 매일 놀라고 있어요. 서비스를 개발하며 느끼는 문제점을 도출하면서 치열하게 토론하고, 그 문제들을 해결하기 위한 아이디어들을 직접 시도해 볼 수 있어서 좋아요. 일을 더 효율적으로, 즐겁게 할 수 있는 환경을 만드는 과정이 코인원만의 애자일을 만들어나가는 것 같아 더 열정적으로 일할 수 있는 것 같습니다.선우님 (Web developer) : 많은 사용자들이 직접 이용하는 서비스를 만드는 개발자로서 다양한 문제 상황을 직면하고 해결해 나가며 보고 배우는게 많습니다. 또 코인원 기술본부는 여느 코인원 조직처럼 언제든 자신의 의견을 자유롭게 이야기하는 분위기가 형성돼있어요. 특히 하나의 프로젝트가 끝나면 회고(Retro)를 진행하는데, 프로젝트의 진행과정, 이슈, 결과물을 공유하면서 저 자신을 성장시켜나갈 수 있는 요소들이 많습니다.안녕하세요, 코인원 신입개발자 (a.k.a CTO) 입니다. (인사성 밝음밝음)“코인원에서는 어떤개발자를 원하나요?진우님 (CTO) : 블록체인을 좋아하고, 개발을 좋아하시는 분이라면 언제든지 환영입니다. 코인원 개발 크루들을 생각했을 때 ‘몰입’이라는 단어가 가장 먼저 떠오르는데요. 모두가 마니아적이고 덕후기질이 있어, 자기주도적으로 업무를 하고 적극적으로 개발할 것들을 찾고 실행합니다.  앞으로 코인원 개발 크루로 합류하실 분들 또한, 적극적으로 아이디어를 내고, 그 아이디어를 실현할 수 있도록 프로젝트를 리딩할 수 있는 분이었으면 해요. 물론 개발하는데 있어서는 누구보다 신중한 자세가 필요하겠죠? 누구보다도 내가 짠 개발코드를 꼼꼼하게 검토하고, 표용력이 있어 좋은 아이디어에는 귀 기울일 줄 아는 분들이셨으면 좋겠습니다. 참고로 Node.js, Python, Spring, C#, AngularJS를 업무에 많이 활용하고 있답니다.개발을 사랑하시는 분! 블록체인을 함께 탐험할 준비가 되신 분! 코인원 개발자 채용에 많은 관심 부탁 드립니다.코인원 개발자 채용에 많은 관심 부탁드립니다 :-)지금까지 미니 인터뷰를 통해 코인원 개발 크루들의 이야기를 들어봤습니다:) 블록체인이라는 새로운 기술 영역에서 매일매일 즐겁게 도전하고 있는 코인원 개발팀에 합류하고 싶은 분들은 현재 코인원 개발자 채용이 진행되고 있으니 지금 바로 확인해주세요!#코인원 #블록체인 #기술기업 #암호화폐 #스타트업인사이트 #기업문화 #조직문화 #팀원소개 #인터뷰
조회수 7430

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

레진 기술 블로그 - IntersectionObserver를 이용한 이미지 동적 로딩 기능 개선

구글 크롬 51 버전부터 DOM 엘리먼트의 노출 여부를 비동기로 처리하는 IntersectionObserver API를 사용할 수 있게 되었습니다. 이 기능을 이용하면 이미지의 동적 로딩이나 광고 배너의 노출 측정 등을 효율적으로 사용할 수 있다고 구글 개발자 블로그에서 소개하고 있습니다. 이 글에서는 기존의 이미지 동적 로딩에 대한 문제점을 짚어보고 여러 예제를 통해 IntersectionObserver의 사용 방법을 익혀 기존 기능을 개선할 수 있도록 합니다. 사용한 예제들은 브라우저의 호환성을 고려하지 않고 구글 크롬을 기준으로 작성하였습니다.기존의 이미지 동적 로딩 구현이미지의 개수가 많거나 용량이 큰 페이지를 불러올 경우 쓸데없는 네트워크 비용이 증가하고 이미지를 불러오는 과정에서 서비스 속도에 문제가 발생할 소지가 있어서 이미지가 사용자에게 보일 때만 불러오는 동적 로딩 기능이 필요합니다. IntersectionObserver를 소개하기 전에 먼저 기존 라이브러리들이 이미지 동적 로딩을 어떤 방법으로 구현하고 있는지 간단하게 알아보도록 합니다.엘리먼트 가시성 판단이미지 동적 로딩에서 가장 중요한 코드는 해당 엘리먼트가 현재 화면 내에 보이는지 알아내는 것입니다. 이를 위해 엘리먼트의 크기와 위치 값을 돌려주는 Element.getBoundingClientRect 함수를 사용하는 경우가 많습니다. 아래는 이 함수를 사용한 간단한 구현 예제입니다.function isInViewport(element) { const viewportHeight = document.documentElement.clientHeight; const viewportWidth = document.documentElement.clientWidth; const rect = element.getBoundingClientRect(); if (!rect.width || !rect.height) { return false; } var top = rect.top >= 0 && rect.top < viewportHeight; var bottom = rect.bottom >= 0 && rect.bottom < viewportHeight; var left = rect.left >= 0 && rect.left < viewportWidth; var right = rect.right >= 0 && rect.right < viewportWidth; return (top || bottom) && (left || right); } 이벤트 처리위에서 구현한 isInViewport 함수는 언제 호출해야 할까요? 먼저 문서를 처음 불러왔을 때 호출해야 합니다. 그 후에는 동적 로딩이라는 단어에 맞게 사용자의 동작에 따라 보이지 않던 엘리먼트가 보이게 되는 이벤트를 감지해야 합니다. 마우스나 터치로 스크롤을 통해 문서의 위치가 바뀌거나 브라우저의 크기가 바뀔 수도 있고 모바일 기기의 화면을 돌려서 볼 수도 있습니다. 데스크톱의 경우 scroll, resize 이벤트를, 모바일의 경우 orientationchange 이벤트의 처리를 생각해야 합니다.const images = Array.from(document.querySelectorAll('img')); document.addEventListener('scroll', () => { images.forEach(image => { if (isInViewport(image)) { image.onload = () => images.splice(images.indexOf(image), 1); image.src = 'original_image_path'; } }); }); 간단하게 위 코드와 같이 구현할 수 있습니다. 물론 실제 서비스를 위해서는 수정할 점이 몇 가지 있습니다. 동적 로딩의 대상이 되는 이미지를 구분하기 위해 해당 엘리먼트에만 특정 클래스를 부여하거나 HTML5에서 지원하는 data 속성을 이용하기도 합니다. 스크롤이나 리사이즈 이벤트가 과도하게 발생하는 경우가 많으므로 throttle 또는 debounce 등을 사용해 실행 빈도를 조절할 수도 있습니다. 일부 라이브러리에서는 requestAnimationFrame을 이용해 이벤트 핸들러를 처리하기도 합니다.TADA레진코믹스에서는 이미지 동적 로딩을 위해 서비스 초기에 Unveil 라이브러리를 사용했었습니다. 그러나 적용 후 몇 가지 아쉬움이 있어 따로 TADA 라이브러리를 제작했습니다. 먼저 마크업 구조상 이미지를 태그가 아닌 다른 태그에 배경 이미지로 사용하는 경우를 지원해야 했습니다. 그리고 모바일 웹에 주로 많이 적용하는 수평 스크롤 구역에 대한 처리도 필요했습니다. 문서의 스크롤 이벤트로는 처리가 되지 않기 때문에 특정 엘리먼트를 받아 그 엘리먼트의 스크롤 이벤트 핸들러를 등록할 수 있도록 해야 했습니다. 이를 해결하기 위해 만든 라이브러리를 현재 서비스에 적용 중이지만 이 라이브러리 역시 아래와 같은 문제점들을 가지고 있습니다.</> < id>기존 이미지 동적 로딩의 문제점getBoundingClientRect 함수의 문제점위 코드에서 특정 엘리먼트가 현재 화면 내에 보이는지 검사할 때 사용하던 Element.getBoundingClientRect 함수에는 치명적인 단점이 있습니다. 이 함수를 호출할 때마다 브라우저는 엘리먼트의 크기와 위치값을 최신 정보로 알아오기 위해 문서의 일부 혹은 전체를 다시 그리게 되는 리플로우(reflow) 현상이 발생한다는 점입니다. 호출 횟수가 적을 경우에는 부담이 되지 않지만, 이 함수는 위에서 구현한 것처럼 스크롤이나 리사이즈 이벤트가 발생할 때마다 등록한 모든 엘리먼트를 순환하면서 호출하게 됩니다. 이 코드들이 하나의 메인 스레드에서 실행되기 때문에 스크롤을 할 때마다 실행 속도가 눈에 띄게 느려질 수도 있습니다.외부 도메인 문서를 사용하는 iframe최신 브라우저들은 동일 도메인 정책에 따라 iframe 내의 외부 도메인 문서에서 현재 문서에 접근하지 못 하게 막고 있습니다. 이 제한은 서비스 개발에서 겪게 되는 문제는 아니고 외부 광고 플랫폼 개발자의 입장에서 발생하는 문제입니다. 광고 이미지의 표시나 클릭 이벤트의 처리 등은 iframe 내에서 처리할 수 있지만 광고 이미지를 지연 로딩한다거나 이 광고가 사용자에게 노출이 되었는지 기록하는 등의 기능은 iframe 내에서 불가능합니다. 그래서 광고를 적용하는 서비스 개발자에게 스크립트를 제공하고 서비스 문서 내에 삽입하는 방식으로 처리하고 있습니다. 이러한 외부 광고가 여러 개라면 삽입해야 하는 코드가 늘어날 뿐만 아니라 코드 내에서 스크롤이나 리사이즈 이벤트 등을 각각 사용하기 때문에 위에서 언급한 문제점들이 배가될 수 있습니다.기타 이벤트에 대한 처리대부분의 동적 로딩 라이브러리들은 적용할 엘리먼트에 특정 클래스 또는 data 속성을 부여하면 코드를 추가 작성하지 않더라도 쉽게 사용할 수 있습니다. 하지만 화면에서 보이지 않던 엘리먼트가 갑자기 나타나는 현상은 스크롤이나 리사이즈 이벤트에서만 발생하는 것이 아닙니다. 더보기 버튼을 눌렀을 때 숨겨져 있던 엘리먼트를 노출할 수도 있고 AJAX 호출 후 엘리먼트를 생성한 후 보여줄 수도 있습니다. 이런 경우 일괄적으로 처리하기가 어렵우므로 해당 이벤트가 발생할 때 수동으로 처리하는 수밖에 없습니다.IntersectionObserver위에 나열한 문제들을 효과적으로 처리하기 위해 크롬 51/엣지 15/파이어폭스 55 버전부터 IntersectionObserver를 지원하기 시작합니다. 우리말로 번역하면 교차 감시자 정도가 될 이 기능은 등록한 엘리먼트가 보이는 영역에 나타나거나 사라질 때(용어에 충실하자면 대상 엘리먼트의 영역이 루트 엘리먼트 영역과 교차하기 시작하거나 끝났을 때) 비동기로 이벤트를 발생시켜 줍니다. 기본적인 사용법은 아래와 같습니다.const intersectionObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { // do something observer.unobserve(entry.target); } }); }); intersectionObserver.observe(element); 먼저 IntersectionObserver 생성자에 콜백 함수를 인자로 넘겨주고 생성한 인스턴스의 observe 메소드를 통해 동적으로 처리할 엘리먼트를 등록합니다. 콜백 함수는 IntersectionObserverEntry 객체 목록을 전달받으므로 순환문을 통해 각 엘리먼트에 대한 처리를 완료하고 필요한 경우 unobserve 메소드를 이용해 감시 대상에서 해제할 수 있습니다.IntersectionObserver 예제아래 예제들은 기본적으로 아래 코드를 바탕으로 작성되었습니다. 각 예제들의 최상단에 엘리먼트에 짝을 이룬 표시등을 두었고 콜백 함수가 호출될 때마다 파동 효과를 주었으며 이 때 isIntersecting 속성을 기준으로 표시등을 켜지거나 꺼지게 되어 있습니다.const io = new IntersectionObserver(entries => { entries.forEach(entry => { // isIntersecting 속성으로 해당 엘리먼트가 보이는 지 표시 }); }); Array.from(document.querySelectorAll('.box')).forEach(box => { io.observe(box); }); 스크롤 이벤트를 다루지 않더라도 엘리먼트의 가시성 여부를 isIntersecting 속성을 통해 알 수 있습니다.<iframe class="demo" data-fr-src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/basic-verticalscroll.html" width="600" height="400" frameborder="0">수평 스크롤의 경우에도 overflow 속성이 적용된 컨테이너 엘리먼트의 스크롤 이벤트를 처리하지 않더라도 상관없습니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/basic-horizontalscroll.html" width="600" height="200" frameborder="0">더보기 버튼에 대한 클릭 이벤트를 따로 등록하지 않아도 동작합니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/basic-unfold.html" width="600" height="240" frameborder="0">리사이즈 이벤트 역시 따로 처리할 필요가 없습니다. 새 창을 띄운 후 창 크기를 조절하면서 확인할 수 있습니다.위의 모든 예제들은 <iframe> 태그 안에서 실행되고 있습니다. 이처럼 작성한 코드가 외부에서 실행이 되더라도 IntersectionObserver API는 문제없이 동작합니다.IntersectionObserver 생성자 옵션rootroot 옵션에는 가시성의 판단 기준이 될 HTML 엘리먼트를 지정합니다. observe 메소드로 등록하는 엘리먼트들은 반드시 이 루트 엘리먼트의 자식이어야 합니다. 이 옵션을 지정하지 않을 경우 브라우저 화면에서 현재 보이는 영역인 뷰포트가 기본이 됩니다. 아래 예제에서 알 수 있듯이 루트 엘리먼트를 지정하면 현재 화면과는 상관없이 루트 엘리먼트와 등록한 엘리먼트들의 영역이 교차하는 지 판단하게 됩니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/option-root.html" width="600" height="400" frameborder="0">rootMargin루트 엘리먼트의 마진값을 지정할 수 있습니다. CSS에서 사용하는 형식과 같기 때문에 “10px”, “10px 20px”, “10px 20px 30px 40px” 형태가 모두 가능하며 음수값으로 지정할 수도 있습니다. 기본값은 0이고 루트 엘리먼트를 지정한 상태라면 퍼센트값을 사용할 수도 있습니다. 이 옵션을 이용하면 이미지 동적 로딩에서 해당 엘리먼트가 화면에 나타나기 전에 이미지를 불러오기 시작해 이미지 공백을 줄이는데 유용하게 이용할 수 있겠습니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/option-rootmargin.html" width="600" height="400" frameborder="0">위 예제에서는 root 옵션은 지정하지 않고 rootMargin 옵션을 각각 0, 100px, -100px, 50%로 지정했습니다. 하지만 iframe 내에서 실행을 하게 되면 이 옵션이 정상적으로 동작하지 않습니다. 프로젝트 페이지의 이슈 댓글에는 동일 도메인의 프레임 안에서는 동작하는 것으로 논의되고 있지만 뷰포트에는 해당하지 않거나 버그 또는 아직 크롬에 반영이 되지 않아 보입니다.이 예제는 새 창을 띄워 프레임을 벗어나면 정상적으로 동작합니다. 스크롤을 내리다보면 엘리먼트마다 IntersectionObserver 콜백 함수가 다른 위치에서 호출됨을 알 수 있습니다.thresholdthreshold 옵션은 엘리먼트가 콜백 함수의 호출 시점을 정하는 옵션입니다. 0과 1을 포함한 그 사이의 숫자 또는 숫자 배열을 지정할 수 있는데 이 숫자는 엘리먼트의 전체 영역 중에 현재 보이는 영역의 비율입니다. 이 비율의 경계를 넘나들 때마다 콜백 함수가 호출됩니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/option-threshold-value.html" width="600" height="400" frameborder="0">위 예제에서는 threshold 옵션을 각각 0, 0.5, 1, [0, 1]로 지정했습니다. rootMargin 예제처럼 엘리먼트마다 콜백 함수가 다른 위치에서 호출됩니다. 두 번째와 세 번째 상자는 콜백 호출 시점에 isIntersecting 값이 항상 참이기 때문에 표시등이 정상적으로 표시되지 않습니다. isIntersecting 속성을 기준으로 처리해야 할 작업이 있다면 반드시 threshold 속성에 0을 포함시켜야 정상적으로 동작합니다. threshold 속성은 아래 예제처럼 콜백 함수의 인자로 받는 IntersectionObserverEntry 객체의 intersectionRatio 속성과 같이 사용하기에 유용합니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/option-threshold-ratio.html" width="600" height="400" frameborder="0">위 예제에서는 threshold 옵션을 각각 [0, 1], [0, 0.5, 1], [0, 0.25, 0.5, 0.75, 1]로 지정하고 콜백이 호출되면 intersectionRatio 값을 기준으로 표시등의 배경 투명도를 바꾸도록 했습니다.IntersectionObserver의 활용이미지 동적 로딩지금껏 알아본 IntersectionObserver를 이용해 이미지 동적 로딩을 간단하게 구현해봅니다. 이미지 엘리먼트를 구성할 데이터가 배열로 존재한다고 가정하고 ES6에서 지원하는 템플릿 문자열을 사용해 배열을 순환하면서 이미지 목록을 생성합니다. 그 후 IntersectionObserver를 초기화하고 만들어진 엘리먼트들을 등록합니다. 콜백 함수에서는 엘리먼트가 보이는 상태일 때 이미지를 로딩하고 해당 엘리먼트를 감시 해제합니다. id="comics"> const comics = [ { alias: 'eunsoo', id: 6080299074584576, title: '은수' }, ... ]; const template = comics => ` ${comics.map(comic => ` ${comic.id}"> ${comic.alias}" class="info">${comic.title} `).join('')} `; document.getElementById('comics').innerHTML = template(comics); const io = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (!entry.isIntersecting) { return; } const target = entry.target; const id = target.dataset.id; target.querySelector('.info').style.backgroundImage = `url(https://cdn.lezhin.com/v2/comics/${id}/images/wide?width=600)`; observer.unobserve(target); }); }); Array.from(document.querySelectorAll('.comic')).forEach(el => { io.observe(el); }); 아래 예제를 실행하면서 브라우저의 개발자 도구를 열고 네트워크 탭을 살펴보면 이미지 엘리먼트가 보이기 시작할 때 불러오기 시작하는 것을 확인할 수 있습니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/demo-lazyload.html" width="600" height="400" frameborder="0">무한 스크롤IntersectionObserver 기능을 이용하면 무한 스크롤 역시 쉽게 구현할 수 있습니다. 아래 예제에서는 스크롤의 끝부분에 감시를 할 엘리먼트를 두고 그 엘리먼트가 노출이 될 때마다 콘텐츠를 추가로 불러오도록 작성하였습니다. id="items"> id="sentinel"> const count = 20; let index = 0; function loadItems() { const fragment = document.createDocumentFragment(); for (let i = index + 1; i <= index + count; ++i) { const item = document.createElement('p'); item.classList.add('item'); item.textContent = `#${i}`; fragment.appendChild(item); } document.getElementById('items').appendChild(fragment); index += count; } const io = new IntersectionObserver(entries => { entries.forEach(entry => { if (!entry.isIntersecting) { return; } loadItems(); }); }); io.observe(document.getElementById('sentinel')); loadItems(); 실행 결과를 아래 예제에서 확인할 수 있습니다.<iframe class="demo" src="https://cdn.rawgit.com/fallroot/intersectionobserver-examples/master/demo-infinitescroll.html" width="600" height="400" frameborder="0">마무리IntersectionObserver API는 아직 몇몇 브라우저의 최신 버전에서만 사용할 수 있지만 지원하지 않는 브라우저를 위해 Polyfill을 제공하고 있습니다.IntersectionObserver API는 웹 광고 플랫폼 제공자와 사용자 모두에게도 좋은 소식이라 봅니다. 이 API가 정착된다면 광고 노출 여부를 측정하기가 쉬워지고 서비스에 더해졌던 리소스 낭비를 줄일 수 있을 것이라 생각합니다.레진코믹스는 크롬 브라우저 사용자의 비중이 높은 편이기 때문에 빠른 시일 안에 적용해 볼 예정입니다. 이미지 동적 로딩 기능의 개선과 정주행 기능과 같이 현재 스크롤 이벤트를 과하게 사용하고 있는 코드에 대한 부담을 덜 수 있기를 기대하고 있습니다.참고자료Intersection Observer API SpecificationIntersectionObserver’s Coming into ViewIntersection Observer API
조회수 1137

레진 기술 블로그 - 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처럼 골치아픈 동작 사례도 있는만큼 선택하지 않았습니다.물론 이 방법이 최선은 절대 아니며(심지어 배포할때마다 돈이 들어갑니다!), 현재는 자원의 활용 등 다른 측면에서의 고민 때문에 새로운 구성을 고민하고 있습니다. 이건 언젠가 나중에 다시 공유하겠습니다. :)
조회수 1092

EOS Proxy Voting이란?

우선 EOS BP 투료를 한 번쯤 해보신 분들은 매번 새롭게 등장하는 BP 후보들은 넘쳐나고 그들의 이름과 공약을 확인하는 것이 귀찮다고 느끼셨을 수 있습니다.또한 어렵게 공약을 확인하고 정말 이 팀이 EOS를 위해 무엇을 할 수 있는지 다른 팀들과 어떤점이 다른지 꼼꼼하게 비교하여 선거한 여러분의 소중한 투표권 파워는 시간이 지날수록 가치가 줄어들게 됩니다.그렇다면 나 대신에 꾸준히 선거를 대신해줄 사람이 있다면 얼마나 좋을까요?사실 이런 문제에 대해 EOS도 알고 있었으며, 어떤 해결 방법이 있을지 생각해왔습니다.그래서 바로 만들어진 것이 EOS Proxy Voting입니다.Proxy란 ‘대리인’이란 의미를 갖고 있습니다.따라서 EOS Proxy Voting은 EOS BP 대리 투표 시스템을 뜻합니다.이 대리인 투표권을 신청하게 되면 여러분은 더 이상 투표에 대해 고민하실 필요가 없게 되는 거예요!이제 이 Proxy 시스템을 어떻게 이용하는지 방법을 소개하고자 합니다.1. 어떻게 Cleos를 통해 다른 사람에게 나의 투표 권한을 넘길 수 있나요?나의 투표 권한을 Cleos를 통해 다른 사람에게 넘기기 위해선 다음과 같은 명령어를 입력해야합니다.간단하지요? 이 명령어는 eosaccount12가 자신의 투표 권한을 proxyvoter34에게 넘기겠다는 의미를 갖고 있습니다.2. 어떻게 툴킷을 통해 다른 사람에게 나의 권한을 넘길 수 있는 건가요?대표적으로 https://eostoolkit.io/vote/setproxy에서 Proxy를 설정하는 방법을 안내해드릴게요! (참고로 https://www.myeoskit.com/#/tools/proxy/https://eosvoter.eosphere.io 에서도 가능합니다. )나의 proxy를 툴킷을 통해 다른 사람에게 넘기기 위해선 먼저 Scatter 구글 확장 프로그램을 설치해야 합니다.Scatter 설치 후 EOS 계정 및 접속 정보를 Scatter에 등록하셔야 합니다. (Scatter에 정보를 등록하는 방법은 곧 업데이트 하도록 하겠습니다.)그렇다면 등록을 다 하셨을 테니 다음으로 넘어가겠습니다.우선 EOStoolkit에 접속하셔서 스캐터 계정으로 로그인하셔야 합니다.로그인 하셨다면 이제 왼쪽 카테고리에서 [Manage Voting] 항목을 보실 수 있을거에요![Manage Voting]를 클릭하시면 Voting에 관한 여러 항목이 촤르르 나오게 되는데 그 중에 [Set Proxy]를 눌러주세요!자 그럼 아래 화면에 나온 대로 그대로 따라하신 후 저장만 해주시면 됩니다.드디어 투표 권한을 지정 Proxy에게 넘기게 되었습니다.3. 어떻게 내가 설정한 Proxy를 해제할 수 있나요?Proxy 지정을 하고 며칠동안 투표에 신경을 쓰지 않았다가 오랜만에 들어간 투표 사이트에서 내가 지정한 대리인이 행사하는 나의 투표권이 마음에 들지 않을 땐 어떻게 해야할까요?해제를 해야겠지요!그렇다면 지금 내가 지정한 Proxy가 마음에 안들어서 해제하고 싶을 때는 어떻게 할지도 알아보겠습니다.Proxy 설정을 했다면, 저 네모박스에 체크되어 있을겁니다. 그 체크를 해지 하면 간단하게 내가 설정한 Proxy를 해제하게 되는 것입니다.아주 간단하네요.그럼 이제 다음은 내가 직접 Proxy가 되기 위해선 어떻게 할 수 있을지 알아보겠습니다.그 방법도 마찬가지로 Cleos 또는 Toolkit 과 Scatter를 통해 할 수 있습니다.4. Cleos를 통해서 내가 직접 Proxy가 될 수 있는 방법은 어떤게 있나요?내가 직접 Cleos를 통해 Proxy가 되기 위해선 다음과 같은 명령어를 입력해야합니다.이 명령어는 proxyvoter34는 Proxy로 지정되었는 의미를 갖고 있습니다.5. 어떻게 툴킷을 통해 내가 직접 Proxy가 될 수 있는 건가요?우선 툴킷을 통해 Proxy로 등록하기 위해선 가장 먼저https://eostoolkit.io/vote/setproxy 에 나의 Scatter 계정으로 로그인해야 합니다.(참고로 https://www.myeoskit.com/#/tools/proxy/https://eosvoter.eosphere.io 에서도 가능합니다. )로그인 하셨다면 왼쪽 카테고리에서 [Manage Voting]을 찾아주세요!찾으셨다면 해당 항목의 아래 항목에서 [Create Proxy] 를 클릭해주세요. 그럼다음과 같은 화면이 나오게 됩니다.아래 나와있는 설명 그대로 적어주시고 저장해주시면 됩니다. 다 완료하셨으면 드디어 Proxy가 되셨어요!6. 더이상 Proxy로 활동하고 싶지 않으면 어떻게 해야 하나요?더 이상 Proxy로서 활동을 하고 싶지 않다면 마찬가지로 [Manage Voting]를 통해 Proxy 철회를 할 수 있습니다.[Manage Voting]를 클릭 후 아래 항목에서 [Resign Proxy]을 누르시면 됩니다. 첫 번째 Resign 버튼은 Proxy 등록을 해지하는 것이고 두 번째 Unregister 버튼은 등록한 정보를 삭제하는 버튼입니다.각각의 버튼을 눌러 그대로 진행하시면 Proxy 철회가 완료될 거예요!자 여기까지 이제 EOS Proxy Voting을 하기 위해Proxy 설정하는 방법을 알아보았습니다. 어렵게 보이지만 Scatter 연동만 하면 Proxy를 설정하거나 내가 직접 Proxy가 되는 것은 어렵지 않습니다!아 참고로, 현재 등록된 모든 Proxy 리스트를 Aloha EOS Proxy Research Portal에서 확인할 수 있습니다.또한 해당 사이트에서 Proxy들이 자신들이 Proxy로 활동하면서 어떻게 투표를 행사할 것인지에 대한 공약도 자세히 나와있으니 한 번쯤 들어가서 보시면 Proxy를지정하는 데에 있어서도, 내가 직접 Proxy가 됨에 있어서도 도움이 될 거예요!#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심
조회수 1322

단일 TABLE을 SELECT하자!

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

전산팀의 홍일점, 김민서 개발자

안녕하세요 써티입니다!벌써 4월 중순, 벚꽃 흩날리는 봄이에요.비욘드펀드는 오늘도 상품 두개나 오픈했어요!오늘의 인터뷰 주인공은전산팀의 홍일점! 김민서 개발자입니다.입사 4개월차 신입이지만맡은 몫을 완벽히 해내고 계신 민서님:)사내인터뷰를 거부하며 3주간 저를 피해다니셨지만............ 언주역 태양빌딩에서 나의 인터뷰를 피할 수 있는 자 아무도 없으리.재밌는 이야기 들어볼까요?1. 안녕하세요 민서님. 전산팀의 유일한 여자 개발자이시네요. 현재 맡고 있는 일을 간단히 설명해주세요.일단 전산팀은 부장님, 과장님, 대리님, 저까지 총 4명인데요. 저는 비욘드펀드 홈페이지 프론트엔드를 맡고 있습니다.2. 프론트엔드가 뭔가요? (역시 개발자 인터뷰가 젤 어렵;;)음….홈페이지 구성할 때 프론트엔드와 백엔드가 있는데요. 프론트엔드는 브라우저로 보이는 기능들을 만드는거고 백엔드는 프론트엔드가 기능을 제대로 할 수 있도록 해주는 거거든요. 지금 백엔드는 과장님이 하고 계시고요. 제가 하는 일은 사용자들이 비욘드펀드 홈페이지에 들어갔을 때 보이는 모든 것들이라고 생각하시면 됩니다.3. 여기가 첫 직장이시라고 들었어요. 어떻게 오게 되셨어요?비욘드플랫폼에 합류하기 전에 한국정보기술연구원(Kitri) 산하 학원에서 웹/어플리케이션 과정을 공부하고 있었는데요. 추천 채용이 들어와서 면접을 보게 됐어요.4. 그러면 전공도 공대쪽이겠네요? 혹시…. 공대 아름이?+_+여대였어요……………………(절망) (역시 여대나온 써티도 함께 웁니다)서울 모 여대에서 컴퓨터학과를 졸업했습니다^^5. 면접 보고 어떠셨어요? P2P금융이라는 산업에 대해서는 알고 계셨었나요?잘 몰랐어요. 금융회사의 개발자가 되라라고는 상상도 못했죠. 사실 스타트업에서 일한다는 생각 자체를 해본 적이 없어요. 아는 분이 스타트업에 다니셔서 제안을 받아본 적은 있지만 진지하게 고려해보지 않았었거든요. 항상 일이 많은 전산팀...... ㅠㅠ 태양빌딩 3층에서는 커피를 양손에 들고 전산실로 걸어가는 그녀의 모습을 종종 발견할 수 있다.6. 오, 그런데 비욘드플랫폼에는 합류를 하신거네요?처음에는 회사소개에 ‘카드론’, ‘대부업’ 같은 단어가 나오니까 걱정이 좀 됐었어요. 사실 아직도 P2P금융이 일반인들에게는 많이 알려져 있지 않잖아요. 더구나 저처럼 금융에 대해서 잘 모르는 사람들은 더더욱 들어본 적이 없고요. 친구들에게 ‘여기 어떤 것 같아?’라고 물어봐도 다들 가지말라고 하더라고요ㅎㅎ그런데 홈페이지 들어가보니까 깔끔한 분위기가 맘에 들었어요. 트렌디한 회사 같다는 느낌? 대표님도 삼일회계법인 임원 출신의 대단한 분이라서 믿고 입사를 하게 됐어요.7. 그래서 P2P금융에는 관심을 좀 갖게 되셨어요?아니요. 돈이 없어요ㅋㅋㅋㅋㅋㅋㅋ 농담이고요. 비욘드펀드 상품이 좋은건 알겠는데 개발자다 보니 솔직히 완벽히 상품을 이해하진 못했어요. 지금은 사회초년생이라 투자할만한 돈은 없지만 목돈이 생기면 P2P로 재테크해볼 생각입니다.8. 비욘드펀드가 이제 좀 커나가고 있는데, 어떤 회사가 됐음 좋겠어요?비욘드펀드라고 말했을때 ‘거기 믿을만하다!’라는 평을 들을 수 있는 그런 회사가 됐으면 좋겠습니다. (사내 복지 쪽으로는 아침을 주면 좋겠…)9. 일적으로 목표가 있다면?솔직히 아직 잘 모르겠어요. 그게 문제라고 생각하기도 하면서도… 이제 4개월차 개발자니까 한창 고민할 때라고 생각해요. 예전에는 모호하게 알던 것들이 이제 조금 구체적으로 다가와요. 점점 더 디테일하게 알아가면서 깊이 공부하고 싶은 부분들이 생기는 것 같아요. 일단은 비욘드플랫폼에서 주어진 일을 열심히 해나가는 것이 목표입니다.10. 마지막으로 민서님이 제일 좋아하는건?누워있는거요. 주말에 약속 잡는 친구들이 제일 싫어요. 완전 집순이거든요. 그래서 우리 회사 휴게실에 있는 영롱한 오렌지색의 이케아 빈백이 너무 탐나요. 나중에 사려고요.민서님이 좋아하는 휴게실 빈백(옆)에서 진행된 즐거운 인터뷰!요즘 비욘드펀드가 상품출시를 활발히 하다보니 민서님이 많이 바쁘신 것 같은데, 화이팅입니다:)#비욘드플랫폼서비스 #비욘드펀드 #개발자 #인터뷰 #팀원 #팀원소개 #팀원인터뷰 #사내문화 #조직문화 #기업문화
조회수 1130

해커 준비: 좋은 코드 만들기

출처 : 구글 이미지 검색Just Hacks지난 몇 주간 저는 I/O의 devops문화 기반을 다지는 작업을 해왔습니다. 여전히 부족한 점이 많지만 그동안 일어난 변화를 지켜보면 첫 걸음은 비교적 잘 뗀듯 합니다. 지금부터는 이 devops문화가 제대로 자리잡는 일이 중요한 단계입니다. 다시말해, devops문화가 튼튼하게 뿌리내릴 수 있게 Hacking하는 것이 저의 당분간의 과제입니다.최근 devops를 연구하고 도입하는데 적잖은 시간과 노력을 쏟았기 때문에 실패할 경우 매몰비용이 만만치 않습니다. 꼭 성공시켜야하는만큼 실증적으로 엔진을 검증하기로했습니다. 그래서 지난 주부터는 저도 devops문화에 소속된 벡엔드 엔지니어로서의 일을 시작했습니다. 당분간 직접 코드를 만들어내야겠지요.설계에 그치지 않고 스프린트를 직접 참여해야만 현재 devops문화가 지닌 문제점이 무엇인지 제대로 볼수 있고 훌륭한 기술조직으로 거듭날 수 있다고 저는 믿습니다. 다시 개발자의 자세로 돌아가기 위해 가장먼저 좋은 코드를 작성하는 공부를 시작하였습니다.좋은 코드 만들기컴퓨터가 인식 가능한 코드는 바보라도 작성할 수 있지만, 인간이 이해할 수 있는 코드는 실력 있는 프로그래머만 작성할 수 있다. -마틴 파울러-SW엔지니어가 되기로한 이상, 제겐 감동까지는 아니지만 코드리뷰를 하는 짝꿍이 쉽게 이해할 수 있는 좋은 코드를 짜야할 의무는 있습니다. 그래서 지금까지 감명 깊게 읽은 고전 책들을 복습하기 시작했습니다. 그 첫 번째 책이 켄트백의 구현패턴입니다. 이 책은 설계나 디자인 패턴과 같은 추상적인 내용보다 키보드로 코드를 짜내는 순간에 고민해야하는 부분에서 교훈을 줍니다. 저는 이 책을 통해 코드를 바라보는 제 관점이 다음과 같이 바뀐듯 합니다.필드(현업)에서 생산된 코드는 코드를 작성하는데 드는 시간보다 읽는 시간이 압도적으로 많기 때문에 이를 감안해 봤을 때 읽기 “좋은 코드”를 짜는 노력이 가장 중요하다.돌이켜보면 학생 시절에는 왜 좋은 코드를 짜야하는지 당연히 모를 수 밖에 없었던 것 같습니다. 프로젝트성격의 코드만 짰기 때문에 종강하고나면 제가짠 코드를 다시는 들여다 볼일이 거의 없었거든요. 만약 대학교가 학생들의 취업경쟁력을 높이기 위해 CS 지식 뿐만아니라 Hacker 소양도 가르치고 싶다면 1학년부터 졸업할 때까지 서서히 발전되는 프로그램 하나를 만드는 4년짜리 과제를 두면 효과적일 것 같습니다.말씀드린 것처럼 필드에서 생성된 코드는 작성 시간보다 유지보수를 위해 읽혀지는 시간이 더 많은 편입니다. 특히 린스타트업을 충실하게 따르는 스타트업이라면 런칭기간이 극단적으로 짧기 때문에 제품(SW) 의 생애주기 중 99%의 시간이 유지보수 단계에 있을 것입니다. 이런 관점에 비춰보면 독자를 고려한 좋은 코드를 짜야한다는 사실은 더욱 중요해집니다.새로운 원칙지금까지 제가 견지하고 있는 좋은 코드를 만드는 원칙은 단순화와 중복제거였습니다. 이번 기회에 이 책을 다시 읽고 제 프로그래밍관에 새로운 원칙을 한 가지 더 추가하였습니다. 일관된 추상화인데요.좋은 코드는 일관된 추상화를 보여줍니다. 아래 예시 코드로 바로 확인하실 수 있습니다.void compute() { input(); flag |= 0x0080; // 나쁜 추상화 output(); }이 간단한 compute라는 함수는 제목처럼 입력(input)을 처리하고 이를 16진수 연산을 거친뒤에 출력(output)과정을 거치면서 마무리 됩니다. 그런데, 함 수 2번째 줄에 드러난 flag변수의 16진수 연산은 조금 쌩뚱 맞습니다. 암호처럼 느껴지네요. comput의 절차를 보여주는 input, output 사이에서 세부 구현사항을 친설하게 알려주려는 작성자의 배려는 되려 독자에게 혼란을 주기만 합니다. 이 혼란스러운 코드를 캡슐화를 통해서 일관된 추상화 수준으로 아래 코드처럼 리팩토링 할 수 있습니다.void compute() { input(); updateFlag(color.Brown); // 좋은 추상화 output(); }16진수 연산대신 의도가 드러나는 함수명과 인자전달을 통해 우리는 input을 처리하고 ouput을 갈색 텍스트로 출력시킨다는 사실을 자연스럽게 받아들일 수 있게 됩니다. 보시는 예제처럼 일관된 추상화는 문제해결 능력, 알고리즘 실력보다 코드를 작성하는 센스에 가깝습니다. 항상 독자를 배려하는 마음을 갖고 상대방에 입장에서 서서 코드를 작성하는 습관을 가져야 겠습니다. 이제 코드를 짜고 리뷰도 받으면서 구린내나는 코드를 신나게 리팩토링 할 일만 남았네요 :-)#스위쳐 #Switcher #DevOPS #데브옵스 #개발 #개발자 #DevOPS도입 #인사이트 #성장
조회수 1602

RxJava2 함수 파헤치기!

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

MOIN 안드로이드 개발자를 소개합니다

영화 같은 일들이 매일같이 벌어지는 요즘 모두들 안녕하신가요?해외송금 스타트업 모인에게 최근 새로운 변화가 생겼습니다.안드로이드 개발자가 합류했습니다.어떤 분인지 지금부터 소개 해드리겠습니다 ^^안드로이드 개발을 해주실 효찬님!- Professional Experience -2015.01~2016.10 kt R&D 연구개발센터 전임연구원2013.07~2015.12 kt R&D 연구개발센터 연구원2013.03~2013.06 kt R&D 연구개발센터 인턴- Education -2006.03~2013.08 고려대학교 컴퓨터통신공학부 학사2001.09~2005.05 Jakarta International School▶ 모인에서 어떤 일을 담당하고 계신가요?총체적인 안드로이드 개발과 웹 서버를 보조하는 일을 맡고 있습니다.▶ 개발자가 되겠다고 한 계기가 궁금합니다. 개발자로서 이력에 대해 간략히 설명해주시겠어요? 특정한 계기가 있어서 개발자가 되겠다고 한 건 아니었어요. 고등학교 시절, 컴퓨터 게임 하면서 막연히 나도 게임을 만들어 보고 싶다고 생각했고 그래서 관심은 가지고 있었죠. 친구들 이름 넣어서 RPG를 만들어보기도 했는데, 생각해보면 스토리 만들기가 재밌었던 거 같아요. 그러면서 자연스럽게 어떻게 하면 컴퓨터 개발할 수 있나 생각도 해보게 됐고, 책도 뒤적여보게 됐습니다. 이런 생활이 고2때까지 이어졌어요. 그런데 마땅히 공부할 수 있는 방법이 없어서 대학가서 해야겠다 생각했습니다. 대학 와서 본격적으로 컴퓨터공학을 공부하면서 재미를 느끼게 됐어요.▶ 그 중에서도 안드로이드 개발을 선택하게 된 이유는 뭐였을까요?예전에 KT에 있을 때, 안드로이드 개발 프로젝트를 맡으면서부터입니다. 원래 이 분야에 대해 전혀 몰랐는데 회사에서 3일간 안드로이드 개발 교육을 받고 해보라는 지시를 받게 된거죠. 막상 해보니 재밌었어요. 특히 이 시기가 2014년 초였는데, 당시에는 안드로이드가 워낙 인기 있는 분야여서 더욱 할만하겠다는 생각을 하게 됐습니다. 효찬님이 선보인 안드로이드 앱 (왼부터)가계부투게더, 메모캐스트, 돈테크 ▶ 본격적으로 모인에 들어오게 된 이야기를 들어보고 싶습니다. 어떻게 모인을 알게 되셨나요?이전 회사에서 늘 입버릇처럼 ‘스타트업을 하고 싶다’는 말을 하고 다녔습니다. 생각해보면 제가 굉장히 밉상이었을텐데 주변 회사분들이 응원을 많이 해주셨어요. 정말 좋으신 분들입니다. (하하) 지인 추천으로 원티드를 알게 됐어요. 저는 초기 단계에 있는 스타트업에 가고 싶었는데 쉽게 찾기는 힘들더라고요. 이후 설립한지 1년도 안 된 ‘모인’을 찾게 됐습니다. 회사에 대해 이것저것 찾아보고 한 번 만나서 이야기해보면 좋겠다는 생각이 들어 대표님을 만났어요. 대표님과 만나 이야기를 나누어 보니 같이 일하고 싶었습니다.  ▶ ‘스타트업을 하고 싶다’는 말을 입버릇처럼 하셨다고 했는데, 특별한 계기가 있었나요?대학 때, 그래픽 프로그래밍 관련 Term Project를 수행했던 적이 있었어요. 이 때 친구들과 밤을 새면서도 웃고떠들며 프로젝트를 해낸 게 제겐 정말 좋은 경험이었습니다. 친한 친구들과 같이 일을 하면 힘든 업무도 웃으면서 즐겁게 할 수 있다는 생각이 들었어요. 앞으로도 좋은 사람들과 같이 즐겁게 일할 수 있으면 좋겠다는 생각을 가지게 되었죠. 스타트업에서 근무하면 일과 동시에 좋은 조직문화를 만들어나갈 수 있을 거라고 생각했어요.  ▶ 개발자로서 자신 있는 영역이 무엇인가요?두루 다룰 줄 안다는 게 제 장점일 수 있겠네요. 그래서 스스로 찾아가면서 어떤 서비스든 개발할 수 있다는 자신이 있습니다. 하지만 역시 한 분야에 대한 전문성은 좀 부족하지 않나 생각해요. 안드로이드에 더더욱 집중해보려고 노력한 이유이기도 합니다. 앞으로도 저만의 차별점을 발굴하는데 계속 노력을 기울일 생각입니다. 효찬님이 가장 애착간다는 원피스 '상디'▶ 개발 외 관심 있는 영역이 무엇인가요?개발 외적으로는 조직 문화에 관심이 많습니다. 제가 개인적으로 일본 만화 ‘원피스’를 좋아해요. 루피 해적단을 보면 개개인이 발전하면서 동시에 팀이 강해지는 모습을 볼 수 있거든요. 어떠한 모험도 할 수 있을 정도로 강해지죠. 루피 해적단 같은 조직을 꿈꿉니다. 어떻게 보면 제가 꿈꾸는 조직 문화가 담겨있다고도 할 수 있죠.특히, 배트맨을 보면 악당이 배트맨 지인을 인질로 잡으면 배트맨은 지인을 구하러 가죠. 하지만 원피스에서는 악당이 루피 친구들을 인질로 잡으면 루피는 친구를 구하러 가지 않아요. 다만, 친구가 함정에서 알아서 잘 나올거라 믿습니다. 그리고 악당을 쓰려트려야 하는 자신의 역할을 수행하는 데 충실해요. 동료를 믿기 때문에 가능한 자세라고 생각해요. 제 나름대로 ‘믿음의 리더십’이라고 혼자 정의해봤어요. (웃음) 대신 내 능력은 스스로가 키워나가야 하죠. 이렇게 각자 자신의 일에 최선을 다하는 사람들과 함께 큰 꿈을 이루는 게 지금 제게 마지막으로 남아 있는 순수한 이상입니다.▶ 더 키워나가고 싶은 역량이 있나요?역량이라기 보다는 제가 만든 작품을 Developing 해나가고 싶어요. 발전가능성이 없는 서비스는 더 이상하고 싶지 않습니다. 내가 만든 앱을 상용화 시키고, 고객들이 반응하는 걸 직접 보고 싶어요. 더불어 서비스 개선에 필요한 역량은 지속적으로 키워나가려고 합니다.장효찬 개발자에게 '함께 일하고 싶은 사람'이란?#온정 #진솔 #파이팅 ▶ 출근한지 일주일도 안됐지만 (웃음) 모인에 대한 첫인상은 어땠어요?정말 아직 1주일도 안됐는데…. (웃음) 대기업에 있다 와서 그런지 소위 ‘젊음의 열정’이라고 하죠? 다들 파이팅 넘치는 모습이 좋았습니다. 그러면서 동시에 나 잘났다고 으스대지도 않고, 특히 대표님 같은 경우는 능력도 있으신데, 겸손하기까지 해서 반했어요. 무언가를 물어보면 설명도 친절하면서 꼼꼼하게 해주시구요. 팀워크가 좋을 거 같다는 긍정적인 예감이 들었어요. 사람 뽑는 데 신중하시다는 대표님을 믿으며, 앞으로도 잘 부탁드립니다.  ▶ 앞으로 어떤 개발자가 되고 싶으신가요? 모인에게도 한마디 해주세요.개발 PM(Project Manager)에 관심이 많습니다. 사실 앱 구현보다 뼈대를 구축하는 일이 더 중요하다고 생각해요. 저는 이 부분이 개발에서 30% 혹은 그 이상을 오롯이 혼자 차지하고 있다고 생각해요. 그러기 위해서는 리소스나 구현한 코드를 어떻게 관리할까, 어떤 부분을 어떻게 더 추가를 해서 연동시킬 수 있을까 등을 서비스 앱 전체를 보고 관리할 줄 알아야 하죠. 이 역할이 국내에서는 중요하게 다뤄지는 거 같지 않아 안타깝습니다. 대부분 보이는 것에만 관심을 가지고 제품이 어떤 제질로 만들어졌는지는 큰 관심을 가지지 않는 추세거든요. 저는 이러한 부분을 중요시 여기는 개발자가 되고 싶습니다. “저를 버리시면(?) 아니됩니다 (웃음)”- 장효찬이 꼽은 인생 명언 -“Do what you love. Everything else is secondary”by. Steve Jobs#모인 #MOIN #개발자 #개발팀 #안드로이드개발자 #안드로이드 #팀원 #팀원소개 #팀원인터뷰 #인터뷰 #기업문화 #사내문화 #조직문화
조회수 2965

Python 기반의 웹서비스 개발 환경 구축 방법

안녕하세요. 스포카 크리에이터팀의 프로그래밍 인턴을 맡고 있는 __박종규, 정성재, 고아라__입니다. 저희 세 명은 각각 다른 OS 환경에서 웹서비스를 개발하였는데 이번 포스팅에서는 OS별로 개발 환경을 구축하는 과정을 설명하겠습니다.PythonPython(파이썬)은 비영리의 Python 소프트웨어 재단이 관리하는 개방형, 공동체 기반 개발 모델을 가지고 있습니다. Python은 C파이썬 구현이 사실상의 표준이며 동적 타이핑 범용 프로그래밍 언어로, 펄 및 루비와 자주 비교됩니다. Python은 순수한 프로그래밍 언어의 기능 외에도 다른 언어로 쓰인 모듈들을 연결하는 Glue language로써 자주 이용됩니다. 실제 Python은 많은 상용 응용 프로그램에서 스크립트 언어로 채용되고 있습니다. 또한, 유니코드 문자열을 지원하여 다양한 언어의 문자 처리에도 능합니다. 현대의 Python은 여전히 인터프리터 언어처럼 동작하나 사용자가 모르는 사이에 스스로 Python 소스 코드를 컴파일하여 바이트 코드를 만들어 냄으로써 다음에 수행할 때에는 빠른 속도를 보여줍니다.Windows에서 Python 2.x 설치 방법Linux와 Mac OS에서 Python은 기본적으로 설치되어 있기 때문에 터미널 창에서 Python 명령만으로 쉽게 실행할 수 있지만 Windows에서는 Python을 따로 설치해주어야 합니다.Python 공식 사이트해당 사이트에 들어가서 Quick Links (2.7.3) – Windows Installer 선택하여 python-2.7.3.msi 다운로드 후 실행하여 설치합니다. 특별히 원하는 버전이 있을 때에는 DOWNLOAD – Releases에서 버전별로 설치파일을 다운로드 할 수 있습니다. Python의 기본 설치 경로는 C:\Python27 이며 설치 완료 후, 환경변수를 등록해야 합니다. 환경변수를 등록하는 방법은 다음과 같습니다.내컴퓨터(마우스 오른쪽 버튼 클릭) – 속성(시스템 등록 정보) – 고급 – 환경 변수 클릭시스템 변수 – Path 선택 후 편집 클릭변수 값에 맨 뒤에 C:\Python27;C:\Python27\Scripts; 입력 후, 확인 버튼을 눌러 시스템에 적용변수 값은 각각 ; (세미콜론) 으로 구분Python의 패키지 설치 방법시작 메뉴 – 실행 – CMD 로 커맨드 창을 실행 시킨 후에 Python이 설치된 디렉터리로 이동합니다.cd C:\Python27\Scripts 그 다음 easy_install pip 명령으로 pip를 설치해줍니다. pip는 PyPI(Python Package Index)에 등록된 패키지들을 설치하고 관리할 수 있는 패키지 관리 시스템입니다.$ easy_install pip 다음은 pip 명령의 사용법입니다.pip install packageName 명령 : 최신버전으로 설치pip install packageName==0.x.x 명령 : 0.x.x버전으로 설치패키지 설치 경로 : C:\Python27\Lib\site-packagespip uninstall packageName 명령 : package 제거pip freeze 명령 : 현재 환경에 설치된 package 이름과 버전 목록 PostgreSQLPostgreSQL는 California 대학 Berkeley computer science 학부에서 개발한 POSTGRES, Version 4.2 를 기반으로 한 오브젝트 RDB 관리 시스템(ORDBMS)입니다. 또한, PostgreSQL은 BSD 허가권으로 배포되며 오픈소스 개발자 및 관련 회사들이 개발에 참여하고 있습니다. 따라서 누구라도 사용, 수정, 배포할 수 있으며 목적과 관계없이 무료로 이용할 수 있습니다.각 운영체제 별 PostgreSQL 설치 방법WindowsWindows에서 PostgreSQL을 설치하기 위해 해당 사이트로 접속합니다.PostgreSQL 공식 사이트해당 사이트의 Download탭을 선택하여 Downloads 페이지로 이동합니다. 그 다음 Binary packages 에서 Windows를 선택하여 Windows installers 페이지로 이동합니다. One click installer 에서 Download 선택 후 이 페이지 로 이동하여 해당하는 OS 버전으로 선택하여 다운로드 후 설치합니다.Linux (ubuntu 12.04 LTS)Linux(리눅스)에서 PostgreSQL은 여러 가지 방법으로 설치 할 수 있습니다. 패키지로 설치하는 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ sudo apt-get install postgresql-X.X(버전명) 다른방법으로 설치하는 방법은 아래 주소로 들어가시면 자세히 나와 있습니다.PostgreSQL 공식 사이트추가로 PostgreSQL을 편하게 이용하고 싶은 사용자는 pgadmin3이라는 PostgreSQL의 GUI 프로그램을 설치하시면 됩니다. 설치 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ sudo apt-get install pgadmin3 Max OS ( MAC OS X Mountain Lion 10.8.2)Mac OS에서 PostgreSQL은 Homebrew를 이용하여 설치하도록 하겠습니다. Homebrew는 Mac OS의 패키지 관리자 프로그램입니다. PostgreSQL은 Homebrew 이외에도 PostgreSQL 다운로드 사이트, Homebrew와 비슷한 기능을 하는 MacPorts 등을 이용해서도 다운받을 수 있습니다. 하지만 PostgreSQL 이외의 다른 프로그램을 설치하기에도 패키지 관리자가 유용하기 때문에 저는 Homebrew를 이용하도록 하겠습니다. 일단, Homebrew 패키지를 컴파일 하기 위해서 xCode가 설치되어 있어야 합니다. 앱스토어에서 xCode를 검색하여 받도록 합니다.xCode 설치를 완료했으면 메뉴에서 __xCode탭 - Preferences - Downloads - Command Line Tools__를 다운받습니다. 그럼 이제 Homebrew 패키지를 컴파일할 수 있게 되었습니다.이제 터미널을 실행시키고 다음 명령을 입력하면 Homebrew가 설치됩니다.$ ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)" Homebrew가 이미 설치되어있다면 업데이트할 사항이 없는지 확인해보고 업데이트를 시켜주세요.$ brew update이제 Homebrew 설치를 완료했으니 간단하게 PostgreSQL을 설치할 수 있습니다.$ brew install postgresql 설치된 버전을 $ psql –ver 명령으로 확인해 보니 PostgreSQL 9.1.4 버전이 설치되어 있었습니다.psycopg2psycopg2는 Python library의 한 종류로써 하는 역할은 Python에서 PostgreSQL를 활용하게 해주는 library입니다. 설치 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ pip install psycopg2 * 참고 ubuntu/debian 사용하시는 분 중에서 설치가 안 되시는 분들은 psycopg2를 설치하시기전에 python-dev라는 패키지 파일을 설치하시면 psycopg2를 설치 하실 수 있습니다. 설치 방법은 터미널 창에서 아래와 같은 명령어를 입력하시면 됩니다.$ sudo apt-get install python-dev FlaskFlask는 Python용 Micro Framework이며, Jinja2 template engine과 Werkzerg WSGI toolkit에 의존합니다. Flask에는 기본적으로 많은 환경설정 값들이 존재합니다. 규칙에 따라 템플릿 파일과 CSS, JavaScript, Images 등의 파일들은 templates과 static 이라는 하위 디렉터리에 각각 보관해야 합니다. 기본적으로 Flask는 다른 라이브러리에는 존재하는 Database abstraction layer, Form validation 등의 기능을 포함하지 않지만, 기능을 추가할 수 있는 확장성을 제공합니다. 이미 수많은 라이브러리들이 Database integration, Form validation, Upload handling, Various open authentication technologies등을 제공합니다. SQLAlchemy나 다른 DB Tool을 이용하여 고급 패턴을 구현할 수 있으며, Flask를 이용해보면 다양한 기능을 확장할 수 있다는 것을 알 수 있을 것입니다.Flask 설치 방법Flask를 설치하려면 터미널 창에서 다음 명령어를 입력하면 됩니다. (windows의 경우 cmd창) $ sudo pip install flask SQLAlchemySQLAlchemy는 Oracle, DB2, MySQL, PostgreSQL, SQLite와 같은 관계형 데이터베이스에 강력하고 유연한 인터페이스를 제공하는 Python SQL Toolkit이자 Object Relation Mapper(ORM)입니다. 여기서 ORM은 객체를 관계형 DB 테이블에 매핑해주는 역할을 하는데 SQLAlchemy는 객체를 매핑하기 위해 특정 클래스를 상속받지 않아도 되기 때문에 높은 수준의 라이브러리라고 할 수 있습니다.앞선 단계까지의 설치가 완료되었다면 SQLAlchemy의 설치법 역시 pip 명령을 쓰면 되므로 간단합니다. 윈도우는 커맨드창을 실행시키고, 리눅스와 MAC OS의 경우 터미널을 실행시켜 다음 명령을 입력합니다. 저는 SQLAlchemy 0.7.2 버전을 설치했습니다.$ pip install SQLAlcheymy==0.7.2Permission denied라는 문구가 뜨면 권한이 없는 것이므로 관리자 계정으로 설치를 해주어야 합니다. 관리자 계정은 $ sudo su로 로그인하거나 명령 앞에 sudo를 붙이면 됩니다.$ sudo pip install SQLAlchemy==0.7.2 여기에서 ==0.7.2는 설치할 SQLAlchemy의 버전을 뜻하며 버전을 명시하지 않으면 최신 버전으로 깔립니다.Flask-SQLAlchemyFlask-SQLAlchemy는 SQLAlchemy를 더욱 뒷받침할 수 있는 Flask의 확장으로 SQLAlchemy 0.6 이상의 버전을 필요로합니다. Flask-SQLAlchemy 역시 pip 명령으로 설치할 수 있으며 저는 Flask-SQLAlchemy 0.15 버전을 설치했습니다.$ sudo pip install Flask-SQLAlchemy==0.15 마치며지금까지 Windows, Linux, Mac OS에서 Python, PostgreSQL을 이용한 웹서비스 개발 환경 구축 방법에 대해 알아보았습니다. 이 글을 통해서 처음 접한 사람들도 특정 OS에 구애받지 않고 쉽게 웹서비스 개발 환경을 구축하는 데에 도움이 되었으면 좋겠습니다.출처Python 위키백과pip 위키백과 Flask 홈페이지PostgreSQL plusPostgreSQL 위키백과 Essential SQLAlchemy, O REILLY, 2008Flask-SQLAlchemy 홈페이지#스포카 #개발 #개발팀 #개발자 #파이썬 #Python #개발환경 #업무환경 #꿀팁 #조언 #인사이트
조회수 7343

HTTP 404 Status Code 에 대한 고찰

뭐가 문제였나필자는 현재 HMR(가정간편식) 커머스를 다루는 모 스타트업에서 백엔드 개발자로 재직 중이다. 말이 백엔드지 최근 변화되고 있는 트렌드에 맞춰 열심히 API 작성 셔틀을 하고 있다.API 개발에 주로 사용하는 HTTP 상태 코드는 주로 200 (정상), 400 (잘못된 요청), 401 (보안 토큰 에러), 403 (권한 없음), 404 (찾을 수 없음) 정도가 있었다.문제는 여기에서 발생했는데, API를 계속 개발해 나가다 보니 API 요청 시 데이터가 없을 때 200 상태 코드에 빈 배열을 돌려주어야 하는지, 404 상태 코드를 돌려주어야 되는지 상황에 따라 다를 수 있겠다는 생각이 들었다.만약 '데이터가 없을 수도 있는 상황'과 '데이터가 없으면 안 되는 상황'에서 404 Not Found 에러 코드로 같게 응답할 경우 다음과 같은 애매한 상황이 펼쳐질 수 있다.API를 사용하는 클라이언트가 404 에러에 대한 대응을 에러로 표시할지 데이터 없음으로 표시할지 상황에 따라 다르게 정의해줘야 한다. 결과적으로 클라이언트에서 API 요청에 대한 처리가 복잡해진다.// front-endimport fetch from 'node-fetch'; function fetchUserList() {  // 유저 목록을 가져오는 API를 사용한다고 가정  return fetch('https://api.exmaple.com/users')    .then((response) => {      if (response.statusCode === 404) {        // 이 404 Http 상태 코드를 에러로 처리할 것인가? 데이터 없음으로 처리할 것인가?        // 에러일 경우 : throw new Error('Not Found');        // 데이터 없음일 경우 : return [];      } else if (response.statusCode === 200) {        return response.json();      } else {        throw new Error('Unexpected Http Status Code');      }    })    .then(result => render(successPage, result))    .catch(error => render(failurePage, error));}결국, 어떤 식으로 표시해야 명확하게 표현할 수 있을까 하여 페이스북 존잘 개발자님들에게 의견을 물었다. # 굉장히 많은 분이 의견을 주셨고 나름대로 생각을 정리할 수 있었다.결론적으로는 '데이터 없음'과 '404 Not Found'를 같은 용도로 사용하면 안 된다.그렇다면 뭘 어째야 하나위에서 나온 결론을 조금 더 자세히 풀어보면 다음 내용이다.상황에 따라 데이터가 없는 것이 정상인 상황이 있고, 데이터가 없는 것이 에러인 상황이 있다. 이를 구분 해야 한다.데이터가 없는 것이 정상일 수 있는 상황// server-sideAPI.get('/orders/date/:date', async (request, response) => {  // 특정 날짜의 주문을 검색. 특정 날짜에 주문이 없을 수도 있다.  const { date } = request.params;  const orders = await Repository.Order.findByDate(date);  // 200: OK  // 204: No Contents  response.statusCode(orders.length > 0 ? 200 : 204).json(orders);});데이터가 없는 것이 에러인 상황API.get('/orders/:orderId', async (request, response) => {  // 특정 ID의 주문을 검색. 데이터가 없으면 에러다.  const { orderId } = request.params;  const order = await Repository.Order.find(orderId);  if (order.length > 0) {    response.statusCode(200).json(order);  } else {    // 404: Not Found    response.statusCode(404).json({      message: `${orderId} is Not Found`    });  };});그렇다면 요청한 API 리소스가 없는 경우에는 어떤 에러를 보여줘야 하는가? 일반적으로는 404 Not Found 가 통상적으로 사용되지만 우리는 이미 404를 다른 용도로 사용하고 있다. 다행히도 HTTP 상태 코드에는 501 Not Implemented 이라는 좋은 친구가 있다. 이 친구를 사용할 수 있다.import { Users, Orders } from './Routes'; app.route('/users', Users);app.route('/orders' Orders);app.all('*', (request, response) => {  // 501: Not Implemented (구현되지 않음)  response.statusCode(501).json({    message: 'This Method is Not Implemented',  });})대충 이 정도면 클라이언트는 Http 상태 코드를 보고 다음 로직을 처리할 수 있을 것이다.물론 일반적으로 사용되는 상태 코드들이지만 실제 개발 진행 시에는 클라이언트를 개발하는 개발자와 미리 어떤 상황에서 어떤 상태 코드를 보낼 것인지 정해야 할 것이다.마무리API 개발 시 사용할 법 직한 응답 코드를 정리해보았다.200: OK (정상, 데이터 있음)204: No Contents (정상, 데이터 없음)301: Moved Permanently (리다이렉션)400: Bad Request (실패, 클라이언트에서 넘어온 파라미터가 이상함)401: Unauthorized (실패, 클라이언트에서 넘어온 보안 토큰이 이상함)403: Forbidden (실패, 사용자의 권한으로 리소스를 사용할 수 없음)404: Not Found (실패, 데이터가 있어야 하나 없음)410: Gone (실패, 데이터가 있었으나 삭제됨. 이건 굳이...?)500: Internal Server Error (실패, 서버 로직 문제)501: Not Implemented (실패, 없는 리소스 요청)기타 304나 502, 503 등의 상태 코드의 경우 API Application을 작성하는 개발자의 역할보다는 Server 쪽의 역할에 가깝다고 생각하여 작성하지 않음.뭔가 어렵다고 느껴진다면 다음 짤을 참고해서 쉽게 이해할 수 있다. #플레이팅 #개발 #개발자 #인사이트 #경험공유 #조언 #꿀팁 #HTTP #버그 #버그수정 #문제해결

기업문화 엿볼 때, 더팀스

로그인

/