스토리 홈

인터뷰

피드

뉴스

조회수 2553

적절한 이벤트 데이터(Event Data) 추출하기

이번 칼럼에서는 프로세스 마이닝의 Input 요소인 이벤트 데이터에 대해 살펴보겠습니다. 이벤트 로그를 어떻게 얻고 프로세스 마이닝 분석이 가능하도록 어떻게 전처리를 할까요? 이벤트 로그는 SAP와 같은 ERP 시스템, 미들웨어, 금융 정보시스템, 사물인터넷 데이터 등 다양한 정보 소스에서 얻을 수 있습니다. 정보 소스는 어디에나 있으며 대부분 수많은 DB 시스템으로 구성되어 있기 때문에 문제는 어떤 데이터를 추출하고 어떻게 프로세스 마이닝에서 사용할 수 있는 이벤트 로그로 변환하느냐는 방법입니다. 아래 그림은 프로세스 마이닝에 필요한 데이터를 설명하는 개념 모델입니다. 각각의 케이스는 이벤트로 이루어져 있고, 이벤트는 여러 속성을 가질 수 있습니다. 원본 소스로부터 이와 같은 형태의 데이터를 추출하고 변환하는 방법이 필요합니다.[그림 1] 이벤트 로그 개념예를 들어 SAP에서 데이터를 추출하는 경우를 보겠습니다. SAP에는 수천 개의 테이블이 있고 여기에는 많은 이벤트 관련 정보가 있습니다. 정확한 데이터를 추출하려면 분석하고자 하는 프로세스가 무엇인지 정의하고 어디가 시작 위치인지 어디가 종료 위치인지 찾아야 합니다. 이러한 데이터 식별, 위치 지정이 제대로 되어야 적절한 이벤트 데이터 수집과 범위 선정이 가능합니다. 병원 데이터도 환자와 관련된 정보가 담긴 1,000개 이상의 테이블을 볼 수 있습니다. 병원 데이터를 분석하려면 마찬가지로 분석 프로세스를 정의하고 분석 범위와 이벤트 데이터 속성에 대해 정의해야 합니다. 이는 중요하지만 어려운 일입니다. 프로세스 마이닝을 위해 필요한 데이터는 여러 정보 시스템에 산재되어 있으며 수집할 수 있는 데이터의 종류와 양도 어마어마합니다.  근본적인 데이터 모델 구조를 이해하고 적합한 이벤트 데이터의 종류와 범위를 산정해야 하며 수집한 데이터를 하나의 테이블로 정리할 수 있어야 프로세스 마이닝을 위한 적절한 이벤트 로그 수집과 준비가 되는 것입니다.티켓 예약 데이터를 통해 데이터 추출과 이벤트 매핑을 살펴보겠습니다. 다음 그림에는 티켓, 예약, 공연, 지불, 고객과 같이 다양한 엔티티(Entity)가 있으며 이러한 엔티티는 관련된 이벤트 또는 액티비티를 가지고 있습니다.[그림 2] 티켓 예약 데이터베이스 구조데이터 분석을 위해 우리가 가장 먼저 결정해야 할 것은 프로세스 인스턴스, 즉 케이스가 무엇인가입니다. 우리가 티켓의 수명주기를 설명하는 모델을 알고 싶다면 티켓을 케이스로 설정하고 이에 해당하는 액티비티를 찾아야 합니다. 예약, 공연, 지불 등의 액티비티가 필요하며 여러 티켓이 동일한 예약 기록이나 지불 이벤트를 가지고 있을 수 있습니다. 따라서 여러 개의 다른 프로세스 인스턴스가 하나의 예약에 연결되어 있을 수 있습니다. 또한, 프로세스 모델이 예약에 대해 설명한다고 하면 다른 액티비티를 찾아야 합니다. 이러한 과정이 명확하거나 쉽지 않기 때문에 어려움이 있습니다. 하나의 예약에 5장의 티켓, 2번의 지불과 같이 여러 이벤트가 연결될 수 있습니다. 예약 취소와 같은 이벤트는 티켓, 공연, 예약 등 여러 엔티티에 영향을 미치게 됩니다. 따라서 엔티티 간의 단순 일대일 대응은 없으며 원하는 이벤트 로그를 얻기 위해서는 데이터 전처리가 필요합니다.케이스 선정과 매핑 문제 외에도 정확한 데이터 추출을 위해서는 고려해야 할 다양한 문제가 있습니다. 케이스나 이벤트가 기록되지 않는 데이터 누락이 발생할 수 있습니다. 실제 수행자가 아닌 다른 수행자가 기록되는 것과 같이 데이터가 정확하지 않을 수 있습니다. 원하는 데이터 레벨이 아닐 수도 있습니다. 예를 들어 개별 작업자에 대해 확인하고 싶은데 부서 레벨이 기록되어 있을 수 있습니다. 또 다른 문제는 관련성이 없는 데이터가 많아 분석 항목을 찾기 어려울 수 있습니다.지금까지 프로세스 마이닝의 이벤트 데이터 관련 문제를 검토하였습니다. 이러한 문제점을 염두에 두고 데이터를 추출해야 프로세스 마이닝 분석을 제대로 수행할 수 있습니다. 프로세스 마이닝 분석을 위한 로그 생성 가이드라인 (https://blog.naver.com/prodiscovery/221160671117) 칼럼을 참조하시면 데이터 추출 문제 해결에 대해 도움을 얻을 수 있습니다.#퍼즐데이터 #개발팀 #개발자 #개발후기 #인사이트
조회수 8266

Node.js로 Amazon DynamoDB 사용하기

DynamoDB 로컬 설정 (다운로드 버전)실제 DynamoDB 웹 서비스에 액세스하지 않고 로컬에서 애플리케이션 작성 및 테스트를 할 수 있음1. 다운로드 링크에서 DynamoDB 무료 다운로드2. 압축 해제 후 해당 디렉터리에서 아래의 명령어로 실행java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb* Ctrl+C로 중지할 수 있고 중지하기 전까지 수신 요청을 처리함* 기본적으로 8000번 포트를 사용Node.js 용 AWS SDK 설치1. 설치npm install aws-sdk2. 실행// app.jsvar AWS = require("aws-sdk");var s3 = new AWS.S3();// 버킷 이름은 모든 S3 사용자에게 고유한 것이어야 합니다.var myBucket = "dynamodb.sample.wonny";var myKey = "myBucketKey";s3.createBucket({ Bucket: myBucket }, function(err, data) {  if (err) {    console.log(err);  } else {    params = { Bucket: myBucket, Key: myKey, Body: "Hello!" };    s3.putObject(params, function(err, data) {      if (err) {        console.log(err);      } else {        console.log("Successfully uploaded data to myBucket/myKey");      }    });  }});node app.js테이블 생성// CreateTable.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var dynamodb = new AWS.DynamoDB();var params = {  TableName: "Movies",  KeySchema: [    { AttributeName: "year", KeyType: "HASH" }, // Partition key    { AttributeName: "title", KeyType: "RANGE" } // Sort key  ],  AttributeDefinitions: [    { AttributeName: "year", AttributeType: "N" },    { AttributeName: "title", AttributeType: "S" }  ],  // 다운로드 버전인 경우 아래 코드 무시  ProvisionedThroughput: {    ReadCapacityUnits: 10,    WriteCapacityUnits: 10  }};dynamodb.createTable(params, function(err, data) {  if (err) {    console.log(      "Unable to create table. Error JSON: ",      JSON.stringify(err, null, 2)    );  } else {    console.log(      "Created table. Table description JSON: ",      JSON.stringify(data, null, 2)    );  }});node CreateTable.js샘플 데이터 로드1. 이곳에서 샘플 데이터 파일 다운로드데이터 형태는 아래와 같음[    {        "year": 2013,        "title": "Rush",        "info": {            "directors": ["Ron Howard"],            "release_date": "2013-09-02T00:00:00Z",            "rating": 8.3,            "genres": [                "Action",                "Biography",                "Drama",                "Sport"            ],            "image_url": "http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg",            "plot": "A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda.",            "rank": 2,            "running_time_secs": 7380,            "actors": [                "Daniel Bruhl",                "Chris Hemsworth",                "Olivia Wilde"            ]        }    },    ...]- year 및 title을 Movies 테이블을 위한 기본 키 속성 값으로 사용- info의 나머지 값들은 info라는 단일 속성에 저장- JSON을 DynamoDB 속성에 저장2. 샘플 데이터 Movies 테이블에 로드// LoadData.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();console.log("Importing movies info DynamoDB. Please wait.");var allMovies = JSON.parse(fs.readFileSync("moviedata.json", "utf8"));allMovies.forEach(function(movie) {  var params = {    TableName: "Moves",    Item: {      year: movie.year,      title: movie.title,      info: movie.info    }  };  docClient.put(params, function(err, data) {    if (err) {      console.error(        "Unable to add movie",        movie.title,        ". Error JSON:",        JSON.stringify(err, null, 2)      );    } else {      console.log("PutItem succeeded:", movie.title);    }  });});node LoadData.js테이블에 항목 추가// PutItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Item: {    year: year,    title: title,    info: {      plot: "Nothing happens at all.",      rating: 0    }  }};console.log("Adding a new item...");docClient.put(params, function(err, data) {  if (err) {    console.error(      "Unable to add item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("Added item:", JSON.stringify(data, null, 2));  }});node PutItem.js- 기본 키가 필요하므로 기본 키 (year, title) 및 info 속성 추가항목 읽기// GetItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Key: {    year: year,    title: title  }};docClient.get(params, function(err, data) {  if (err) {    console.error(      "Unable to read item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("GetItem succeeded:", JSON.stringify(data, null, 2));  }});node GetItem.js항목 업데이트// UpdateItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Key: {    year: year,    title: title  },  UpdateExpression: "set info.rating = :r, info.plot=:p, info.actors=:a",  ExpressionAttributeValues: {    ":r": 5.5,    ":p": "Everything happens all at once.",    ":a": ["Larry", "Moe", "Curly"]  },  ReturnValues: "UPDATED_NEW"};console.log("Updating the item...");docClient.update(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));  }});node UpdateItem.js- 지정된 항목에 대해 수행하고자 하는 모든 업데이트를 설명하기 위해 UpdateExpression을 사용- ReturnValues 파라미터는 DynamoDB에게 업데이트된 속성("UPDATED_NEW")만 반환하도록 지시원자성 카운터 증가시키기update 메서드를 사용하여 다른 쓰기 요청을 방해하지 않으면서 기존 속성의 값을 증가시키거나 감소시킬 수 있음 (모든 쓰기 요청은 수신된 순서대로 적용)실행 시 rating 속성이 1씩 증가하는 프로그램// Increment.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";// Increment an atomic countervar params = {  TableName: table,  Key: {    year: year,    title: title  },  UpdateExpression: "set info.rating = info.rating + :val",  ExpressionAttributeValues: {    ":val": 1  },  ReturnValues: "UPDATED_NEW"};console.log("Updating the item...");docClient.update(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));  }});node Increment.js항목 업데이트(조건부)UpdateItem을 조건과 함께 사용하는 방법조건이 true로 평가되면 업데이트가 성공하지만 그렇지 않으면 수행되지 않음// ConditionalUpdateItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";// Increment an atomic countervar params = {  TableName: table,  Key: {    year: year,    title: title  },   UpdateExpression: "remove info.actors[0]",  ConditionExpression: "size(info.actors) > :num",  ExpressionAttributeValues: {    ":num": 3  },  ReturnValues: "UPDATED_NEW"};console.log("Attempting a conditional update...");docClient.update(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));  }});node ConditionalUpdateItem.js다음과 같이 작성하면 아래와 같은 에러 메시지가 표시 됨The conditional request failed"영화에는 3명의 배우가 있는데 배우가 3명보다 많은지를 확인하고 있어 에러가 발생다음과 같이 수정하면 정상적으로 항목이 업데이트 됨ConditionExpression: "size(info.actors) >= :num",항목 삭제// DeleteItem.jsvar AWS = require("aws-sdk");var fs = require("fs");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var table = "Movies";var year = 2017;var title = "The Big Wonny";var params = {  TableName: table,  Key: {    year: year,    title: title  },  ConditionExpression: "info.rating <= :val",  ExpressionAttributeValues: {    ":val": 5.0  }};console.log("Attempting a conditional delete...");docClient.delete(params, function(err, data) {  if (err) {    console.error(      "Unable to update item. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));  }});node DeleteItem.js다음과 같이 작성하면 아래와 같은 에러 메시지가 표시 됨The conditional request failed특정 영화에 대한 평점이 5보다 크기 때문에 에러가 발생다음과 같이 수정하면 정상적으로 항목이 삭제 됨var params = {  TableName: table,  Key: {    year: year,    title: title  }};데이터 쿼리- 파티션 키 값을 지정해야 하며, 정렬 키는 선택 사항- 1년 동안 개봉한 모든 영화를 찾으려면 year만 지정, title을 입력하면 2014년 개봉된 "A"로 시작하는 영화를 검색하는 것과 같이 정렬 키에 대한 어떤 조건을 바탕으로 일부 영화를 검색할 수도 있음한 해 동안 개봉한 모든 영화// QueryYear.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var params = {  TableName: "Movies",  KeyConditionExpression: "#yr = :yyyy",  ExpressionAttributeNames: {    "#yr": "year"  },  ExpressionAttributeValues: {    ":yyyy": 1985  }};docClient.query(params, function(err, data) {  if (err) {    console.error("Unable to query. Error JSON:", JSON.stringify(err, null, 2));  } else {    console.log("Query succeeded.");    data.Items.forEach(function(item) {      console.log(" -", item.year + ": " + item.title);    });  }});node QueryYear.jsExpressionAttributeNames는 이름을 교체함. 이를 사용하는 이유는 year가 DynamoDB에서 예약어이기 때문. KeyConditionExpression을 포함해 어떤 표현식에서도 사용할 수 없으므로 표현식 속성 이름인 #yr을 사용하여 이를 지칭ExpressionAttributeValues는 값을 교체함. 이를 사용하는 이유는 KeyConditionExpresssion을 포함해 어떤 표현식에서도 리터럴을 사용할 수 없기 때문. 표현식 속성 값인 :yyyy를 사용해 지칭* 위의 프로그램은 기본 키 속성으로 테이블을 쿼리하는 방법. DynamoDB에서 1개 이상의 보조 인덱스를 테이블에 생성하여 그 인덱스로 테이블을 쿼리하는 것과 동일한 방식으로 쿼리 작업 가능. 보조 인덱스는 키가 아닌 속성에 대한 쿼리를 허용하여 애플리케이션에 더 많은 유연성을 부여함한 해 동안 개봉한 모든ㄴ 영화 중에 특정 제목을 지닌 영화year 1992에 개봉한 영화 중에 title이 "A"부터 "L"까지의 알파벳으로 시작하는 영화를 모두 조회합니다.// QueryTitle.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();console.log(  "Querying for movies from 1992 - titles A-L, with genres and lead actor");var params = {  TableName: "Movies",  ProjectionExpression: "#yr, title, info.genres, info.actors[0]",  KeyConditionExpression: "#yr = :yyyy and title between :letter1 and :letter2",  ExpressionAttributeNames: {    "#yr": "year"  },  ExpressionAttributeValues: {    ":yyyy": 1992,    ":letter1": "A",    ":letter2": "L"  }};docClient.query(params, function(err, data) {  if (err) {    console.error("Unable to query. Error JSON:", JSON.stringify(err, null, 2));  } else {    console.log("Query succeeded.");    data.Items.forEach(function(item) {      console.log(        " -",        item.year + ": " + item.title + " ... " + item.info.genres + " ... ",        item.info.actors[0]      );    });  }});node QueryTtiel.js스캔테이블의 모든 항목을 읽고 테이블의 모든 데이터를 반환선택 사항인 filter_expression을 제공할 수 있으며 그 결과 기준이 일치하는 항목만 반환하지만 필터는 테이블 전체를 스캔한 후에만 적용됨// Scan.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var docClient = new AWS.DynamoDB.DocumentClient();var params = {  TableName: "Movies",  ProjectionExpression: "#yr, title, info.rating",  FilterExpression: "#yr between :start_yr and :end_yr",  ExpressionAttributeNames: {    "#yr": "year"  },  ExpressionAttributeValues: {    ":start_yr": 1950,    ":end_yr": 1959  }};console.log("Scanning Movies table.");docClient.scan(params, onScan);function onScan(err, data) {  if (err) {    console.error(      "Unable to scan the table. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    // print all the movies    console.log("Scan succeeded.");    data.Items.forEach(function(movie) {      console.log(        movie.year + ": ",        movie.title,        "- rating:",        movie.info.rating      );    });    // continue scanning if we have more movies, because    // scan can retrieve a maximum of 1MB of data    if (typeof data.LastEvaluatedKey != "undefined") {      console.log("Scanning for more...");      params.ExclusiveStartKey = data.LastEvaluatedKey;      docClient.scan(params, onScan);    }  }}node Scan.jsProjectionExpression은 스캔 결과에서 원하는 속성만 지정FilterExpression은 조건을 만족하는 항목만 반환하도록 조건을 지정. 다른 항목들은 모두 무시됨테이블 삭제// DeleteTable.jsvar AWS = require("aws-sdk");AWS.config.update({  region: "us-west-2",  endpoint: "http://localhost:8000"});var dynamodb = new AWS.DynamoDB();var params = {  TableName: "Movies"};dynamodb.deleteTable(params, function(err, data) {  if (err) {    console.error(      "Unable to delete table. Error JSON:",      JSON.stringify(err, null, 2)    );  } else {    console.log(      "Deleted table. Table description JSON:",      JSON.stringify(data, null, 2)    );  }});node DeleteTable.js#트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유 #데이터베이스 #DB #개발 #AWS #아마존 #NoSQL 
조회수 1271

MySQL에서 RDS(Aurora) 로 이관하기

안녕하세요. 스티비팀 서버 개발자 이학진 입니다. 저희는 최근 서비스에서 사용 중이던 MySQL DB를 RDS로 이관하는 작업을 진행하였습니다. 무엇 때문에 이관을 결정하게 되었는지와 어떻게 이관을 진행하였는지에 대해 글을 써보도록 하겠습니다.배경stibee.com은 작년 11월에 정식 오픈한 새내기 이메일 마케팅 서비스 입니다. 사실 오픈 초기부터 얼마전까지만 해도 AWS EC2의 m4.large 인스턴스 하나로 운영되던 서비스였습니다.(사실 웹+API 서버 1대, 메일발송서버 1대)그리고 이 싱글 인스턴스에 무려 6개의 서버, mysql 1개, kafka 1개, redis 1개가 돌고 있었습니다. 그럼에도 불구하고 cpu사용률은 20%를 넘지 않았습니다.하지만 최근 사용자도 점점 늘어났고, 네이버에서 메일 수신정책을 변경하면서 메일발송서버에 대한 요청이 급증했습니다.스티비에서 네이버로 대량메일을 발송했을 때 해당 메일의 본문 링크를 자동검사하는 것을 발견했는데요, 따라서 네이버로부터 비정상적으로 많은 요청이 들어오고 있었습니다. (어떤 기준으로 이런 검사를 하는 것인지 정확한 정책은 아직 모릅니다. 담당자분 이 글을 보신다면 연락주세요. 친하게 지냈으면 합니다#슬로워크 #스티비 #개발 #서버개발 #개발환경 #MySQL #인사이트
조회수 2254

JIRA하고 자빠졌네!?

Overview“JIRA하고, 자빠졌네!” 세종대왕은 확실히 개발자의 두뇌를 가지고 있었던 게 분명합니다. 먼 시대를 지나 오늘날 QA를 하는 저에게 응원을 해주시니 말입니다. 하지만 그는 틀렸습니다. 걱정과는 다르게 다행히 자빠지진 않았거든요. 지라(JIRA) 덕분입니다.갑자기 지라 이야기가 나와 당황하셨죠? 축하해주세요. 드디어 브랜디도 지라를 사용하게 되었답니다. (짝짝짝!) 지라 도입은 처음이라 세팅부터 쉽지 않았는데요. 이번 글은 눈물겨웠던 지라 세팅 과정과 브랜디의 이슈관리를 소개하겠습니다. 스크럼을 쓰면 좋은 점스크럼(Scrum)은 요구 사항 분석부터 하는 칸반(Kanban)보다 효율적입니다. 안드로이드와 iOS로도 나눠져 있고 업무를 짧게 반복하기 때문이죠. 스크럼에 적합한 워크플로우(Workflow)를 볼까요? 이것은 실제로 브랜디 R&D본부에서 사용하고 있기도 합니다. 스크럼에 적합한 워크플로우IN PROGRESS: 이슈나 개발 요건을 티켓으로 만들면 IN PROGRESS 상태가 됩니다. RESOLVED: 이슈나 개발 요건이 완료되면 RESOLVED 상태로 변경합니다.QA: QA가 필요한 개발 요건은 QA상태로 변경합니다.PASS: 이슈 또는 개발 요건이 수정되었거나 문제가 없다면 PASS 상태로 변경합니다.FAIL: 이슈 또는 개발 요건이 제대로 수정되지 않았거나 다른 이슈가 발생하면 FAIL 상태로 변경합니다.QA불필요: QA가 필요하지 않은 개발 요건은 QA불필요 상태로 변경합니다.DONE: 이슈를 해결했거나 개발을 완료하면 DONE 상태로 변경합니다CLOSE: 담당 팀장님이 이슈 확인 후 CLOSE 처리합니다. 예를 들어보겠습니다. 킥오프 서비스 회의를 하고, SB를 제작, 리뷰합니다. 이후에 디자인팀과 개발팀 일정을 공유하고 스크럼 마스터는 스프린트 주기를 책정하죠. 스프린트가 시작되면 개발자는 스토리 티켓을 작성하는데요. 개발이 끝나면 QA가 필요한 티켓은 테스트를 진행하고, QA가 종료되면 스프린트도 종료됩니다.Epic 티켓위의 이미지는 Epic 티켓입니다. Android, iOS, 이슈 등 모든 티켓은 Epic 안에서 관리합니다. 한 곳에서 한꺼번에 관리하기 때문에 히스토리 관리가 편하고, 진행 상황도 확인할 수 있습니다.티켓 생성개발팀의 티켓 생성입니다. 개발자는 SB를 보고 개발 티켓을 작성합니다. 개발 티켓 작성 후에 개발이 진행되며 QA 판단 여부를 체크해 QA 상태로 변경합니다. 변경된 티켓에 관한 QA가 진행되며 문제가 없으면 해당 티켓은 종료됩니다.이슈 생성다음은 이슈 생성입니다. 파악한 SB는 디자인 시안과 비교하며 개발이 된 Android, iOS 테스트 파일을 QA합니다. QA를 진행할 때 발생한 이슈는 지라 티켓으로 등록하여 이슈를 관리합니다. 모든 이슈 티켓 종료되면 해당 차수의 QA는 끝나고 마침내 상용에 배포합니다. 배포가 완료되면 필수 및 크리티컬 리그레이션 테스트가 진행됩니다. Conclusion실수는 항상 모든 것이 끝난 이후에 보이기 마련입니다. 수십 번 QA를 해도 보이지 않던 문제들이 상용에 올라간 이후부터 보이기 시작하죠. 스크럼은 이런 실수들을 가장 최소화할 수 있는 툴이 아닐까 생각합니다. 물론 아무리 좋은 툴을 써도 팀원들과 함께 뭉치는 것보다 중요한 것은 없겠죠. 다음 글은 자동화를 주제로 찾아뵙겠습니다. JIRA하고 자빠지지 않는 개발자가 됩시다!글김치영 대리 | R&D PM팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발자 #개발팀 #인사이트 #경험공유 #JIRA
조회수 3160

eventlet을 활용한 비동기 I/O 프로그래밍

안녕하세요. 스포카 크리에이터팀 문성원입니다. 현대적인 프로그래밍 환경에서 네트워크는 더는 특정 직군의 개발자만 접하는 분야가 아닙니다. 그런 만큼 대량의 요청을 네트워크를 통해 송수신하는 프로그램이 생각보다 성능이 나오지 않는 경우를 경험하신 분들도 많으실 겁니다. 물론 스포카 개발팀도 예외는 아니었습니다. 그래서 오늘은 저희의 이러한 경험과 그 해결책-eventlet을 통한 비동기 I/O(Asynchronous I/O)-에 대해 소개합니다.Why우선 스포카 개발팀에서 겪었던 문제부터 시작하죠. 얼마 전 페이스북(facebook)의 FQL(Facebook Query Language)를 통해 정보를 수집해서 이를 활용하는 기능을 작성해야 했습니다. 기존의 함수들은 필요할 때마다 FQL을 요청하는 방식이었고 당연히 이건 너무 느렸죠. 그래서 생각한 것이 “하루의 일정 시간마다 대량의 FQL 요청을 보내서 필요한 정보를 미리 갱신시켜놓자.”였습니다. 여기까진 좋았죠. 이때 제가 작성한 코드의 얼개를 살펴보면 대강 이렇습니다.# 페이스북 계정들을 가져와서 반복하면서for account in FacebookAccount.query:    account.update() #FQL을 보내자.view rawgistfile1.py hosted with ❤ by GitHub그런데 문제가 있었습니다. 기존의 FQL을 보내는 FacebookAccount.update()는 FQL요청이 완료될때까지 멈추고 기다립니다. 대부분의 FQL요청이 2, 3초 정도 걸린다고 했을 때 이러한 지연은 매우 치명적입니다. 대안이 필요했고 자연스레 떠오른 것이 서두에 소개한 비동기 I/O(Asynchronous I/O)였습니다.Asynchronous과거 일부 고급 서버 개발자만 알고 있는(혹은 알아야 하는) 기술로 치부되던 ‘비동기(Asynchronous)’란 개념은 2000년대 들어 등장한 Ajax(Asynchronous JavaScript and XML)의 성공 이후 많은 개발자에게 강한 인상을 줬습니다. 사용자는 HTTP 요청이 끝날 때까지 멈추어 있는 하얀 화면으로부터 해방되었고, 다양하고 많은 요청과 응답들이 자연스럽게 서버로 흘러들어 가서 나왔습니다. 개발자들의 이러한 경험과 통찰은 이후 node.js와 같은 플랫폼의 등장에도 많은 영향을 끼쳤습니다.다시 문제로 돌아가죠. 그렇다면 이러한 비동기에 관한 개념은 위의 상황을 어떻게 해결할 수 있을까요? 문제의 원인부터 다시 살펴봅시다. 2, 3초 정도씩 걸리는 FQL 요청이 문제일까요? 물론 요청이 매우 빨리 처리된다면 별도의 처리 없이도 저 코드는 문제없이 동작합니다. 하지만 현실적으로 이런 I/O의 속도를 빠르게 하는데에는 물리적으로 한계가 있습니다. 오히려 여기에서 주목해야 할 점은 ‘2, 3초’ 보다 ‘기다린다’라는 점입니다. FacebookAccount.update() 같은 경우, I/O가 처리되는 동안 CPU는 하던 일을 멈추고 문자 그대로 기다리게 됩니다. 만약 CPU가 멈추지 않고 다른 요청을 보낸다면 어떨까요? 이렇게 말이죠.비동기만으로는 부족하다?이러한 아이디어는 그동안 많은 개발자가 대량의 I/O를 다루는 올바른 방식으로 여겨왔습니다. 하지만 보통 이러한 비동기 I/O를 통한 구현은 동기식 I/O와는 좀 다른 형태를 띠게 됩니다. 이렇게 말이죠.# http://docs.python.org/library/asyncore.html#asyncore-example-basic-http-clientimport asyncore, socketclass HTTPClient(asyncore.dispatcher):    def __init__(self, host, path):        asyncore.dispatcher.__init__(self)        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)        self.connect( (host, 80) )        self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path    def handle_connect(self):        pass    def handle_close(self):        self.close()    def handle_read(self):        print self.recv(8192)    def writable(self):        return (len(self.buffer) > 0)    def handle_write(self):        sent = self.send(self.buffer)        self.buffer = self.buffer[sent:]client = HTTPClient('www.python.org', '/')asyncore.loop()view rawgistfile1.py hosted with ❤ by GitHub불행하게도, 이 경우 기존에 사용하던 urllib2대신 HTTP 요청을 처리하는 핸들러를 이처럼 재작성 해야합니다. 거기에 FacebookAccount.update()의 호출 방식마저 바뀔 수 있죠. 더군다나 콜백(Callback) 투성이의 코드는 유지보수가 쉬어 보이지도 않습니다. 여러모로 손이 많이 가는 상황이죠.결국, 기존 코드를 최대한 수정하지 않으면서도, 어느 정도 성능은 보장되는 그런 해결책이 필요했습니다. 그런 해결책이 있을까요? 다행히도 그렇습니다.What저희가 해결책으로 택한 eventlet은 Python(정확히는 CPython)에서 코루틴(Coroutine)을 지원하기 위해 만들어진 greenlet을 이용해 작성된 네트워크 관련 라이브러리입니다. 생소한 용어가 갑자기 튀어나와서 놀라셨을지도 모르니 우선 eventlet에 대해 설명하기 전에 앞에 나온 용어들을 찬찬히 한번 살펴보죠.코루틴과 greenlet먼저 코루틴(Coroutine)부터 살펴보죠. 전산학도라면 누구나 그 이름을 한번은 들어봤을 도널드 카누쓰(Donald Knuth)는 자신의 저서 The Art of Computer Programming에서 코루틴을 다음과 같이 설명합니다.Subroutines are special cases of more general program components, called “coroutines.” In contrast to the unsymmetric relationship between a main routine and a subroutine, there is complete symmetry between coroutines, which call on each other.코루틴은 우리가 잘 알고 있는 서브루틴(Subroutine)과 달리 진입점(Entry Point)이 여러 개일 수 있습니다. 쉽게 이야기하면 실행을 멈췄다가(Suspend) 재개(Resume)할 수 있다는 점인데요. 이 특성을 살리면 우리가 익히 아는 스레드(Thread)처럼 쓸 수 있게 됩니다. 다만 스레드와 달리 코루틴은 비선점적(Non-Preemptive)이기때문에 코드의 흐름을 전적으로 사용자가 제어할 수 있습니다.하지만 불행히도 모든 언어에서 이런 코루틴이 지원되진 않습니다. greenlet은 이런 코루틴을 CPython에서 지원하기 위해 작성된 라이브러리입니다.eventlet코루틴을 통해 스레드를 대체할 수 있다는 점에 주목한 사람들은 greenlet을 통해 유용한 네트워크 라이브러리를 만들어냈습니다. eventlet도 그 중 하나죠. 잠시 eventlet의 소갯글을 봅시다.Eventlet is a concurrent networking library for Python that allows you to change how you run your code, not how you write it.위에서 볼 수 있듯이 eventlet은 사용성에 중점을 두었습니다. 기존의 블로킹 I/O 스타일의 프로그래밍에 익숙한 개발자들도 쉽게 비동기 I/O의 장점을 얻을 수 있게끔 하는 게 목적이죠.특히 저희가 주목한 점은 eventlet의 멍키패치 기능입니다. 멍키패치는 본래 동적 언어에서 런타임에 코드를 고쳐서 별도의 파일 변경 없이 본래 소스의 기능을 변경하는 것을 말합니다. eventlet은 eventlet.monkey_patch 메서드를 통해 표준 라이브러리의 I/O 라이브러리를 논블러킹으로 동작하게끔 변경해서 코루틴에 적합하게 만듭니다.How앞서 소개한 eventlet.monkey_patch를 이용하면 실제로 고칠 부분은 정말로 적어집니다. 다음 코드가 eventlet을 이용해 변경한 전부입니다.import eventleteventlet.monkey_patch() #표준 라이브러리를 변환# 여러가지 import를 하고...pool = eventlet.GreenPool()# 페이스북 계정들을 가져와서 반복하면서for account in FacebookAccount.query:    # 코루틴들에게 떠넘기자.    pool.spawn_n(FacebookAccount.update, account)        pool.waitall()view rawgistfile1.py hosted with ❤ by GitHub정말 적죠? 조금만 구체적으로 살펴보죠. 우선 eventlet.monkey_patch는 socket이나 select등의 Python 표준 라이브러리를 eventlet.green 패키지안에 정의된 코루틴 친화적인 모듈들로 바꿔치기 합니다.# from eventlet/pathcer.pydef monkey_patch(**on):    """Globally patches certain system modules to be greenthread-friendly.    The keyword arguments afford some control over which modules are patched.    If no keyword arguments are supplied, all possible modules are patched.    If keywords are set to True, only the specified modules are patched.  E.g.,    ``monkey_patch(socket=True, select=True)`` patches only the select and     socket modules.  Most arguments patch the single module of the same name     (os, time, select).  The exceptions are socket, which also patches the ssl     module if present; and thread, which patches thread, threading, and Queue.    It's safe to call monkey_patch multiple times.    """        accepted_args = set(('os', 'select', 'socket',                          'thread', 'time', 'psycopg', 'MySQLdb'))    default_on = on.pop("all",None)    for k in on.iterkeys():        if k not in accepted_args:            raise TypeError("monkey_patch() got an unexpected "\                                "keyword argument %r" % k)    if default_on is None:        default_on = not (True in on.values())    for modname in accepted_args:        if modname == 'MySQLdb':            # MySQLdb is only on when explicitly patched for the moment            on.setdefault(modname, False)        on.setdefault(modname, default_on)            modules_to_patch = []    patched_thread = False    if on['os'] and not already_patched.get('os'):        modules_to_patch += _green_os_modules()        already_patched['os'] = True    if on['select'] and not already_patched.get('select'):        modules_to_patch += _green_select_modules()        already_patched['select'] = True    if on['socket'] and not already_patched.get('socket'):        modules_to_patch += _green_socket_modules()        already_patched['socket'] = True    if on['thread'] and not already_patched.get('thread'):        patched_thread = True        modules_to_patch += _green_thread_modules()        already_patched['thread'] = True    if on['time'] and not already_patched.get('time'):        modules_to_patch += _green_time_modules()        already_patched['time'] = True    if on.get('MySQLdb') and not already_patched.get('MySQLdb'):        modules_to_patch += _green_MySQLdb()        already_patched['MySQLdb'] = True    if on['psycopg'] and not already_patched.get('psycopg'):        try:            from eventlet.support import psycopg2_patcher            psycopg2_patcher.make_psycopg_green()            already_patched['psycopg'] = True        except ImportError:            # note that if we get an importerror from trying to            # monkeypatch psycopg, we will continually retry it            # whenever monkey_patch is called; this should not be a            # performance problem but it allows is_monkey_patched to            # tell us whether or not we succeeded            pass    imp.acquire_lock()    try:        for name, mod in modules_to_patch:            orig_mod = sys.modules.get(name)            if orig_mod is None:                orig_mod = __import__(name)            for attr_name in mod.__patched__:                patched_attr = getattr(mod, attr_name, None)                if patched_attr is not None:                    setattr(orig_mod, attr_name, patched_attr)        # hacks ahead; this is necessary to prevent a KeyError on program exit        if patched_thread:            _patch_main_thread(sys.modules['threading'])    finally:        imp.release_lock()view rawgistfile1.py hosted with ❤ by GitHub이렇게 바꿔치기된 eventlet.green안의 모듈들은 I/O에 의해 블럭되는 경우 다른 코루틴에 제어권을 넘기는 식으로 지연을 방지합니다.다른 대안들사실 이러한 목적으로 사용되는 라이브러리는 eventlet만 있는 것은 아닙니다. gevent는 eventlet에서 영향을 받았지만, libevent를 기반으로 하여 더욱 나은 성능과 성숙한 인터페이스를 갖추고 있습니다. 저희처럼 libevent의 설치에 제한이 있는 환경이 아니라면 이쪽을 살펴보셔도 좋습니다.만약 이벤트 주도적 프로그래밍(Event-Driven Programming)에 흥미가 있으신 분은 Twisted역시 좋은 대안이 될 수 있습니다.#스포카 #개발 #개발자 #인사이트 #꿀팁
조회수 1050

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

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

워크인사이트 프론트엔드 개발환경

조이코퍼레이션은 오프라인 고객 분석 서비스인 워크인사이트를 만들고 있습니다. 워크인사이트는 스마트폰 신호를 통해 매장 방문객의 출입 및 체류 패턴을 측정하고 분석합니다. 분석된 데이터는 웹 대시보드를 통해 한 눈에 파악하기 쉬운 형태로 매장에 제공됩니다. 매장들은 이 대시보드를 보고 중요한 판단과 의사 결정을 내리기 때문에 대시보드는 보기 쉬워야 하고 쓰기 편해야 하며 무엇보다 아름다워야 합니다. 조이의 빅데이터 기술을 통해 분석된 데이터를 매장에 효과적으로 전달하기 위해 프론트엔드 기술에 많은 노력을 기울이고 있습니다. 이 글에서는 조이의 대시보드를 만들기 위해 사용하고 있는 기술과 개발 환경 그리고 기술적인 관점에서 고민하고 있는 부분들을 간략하게 공유하고자 합니다.그림1. 대시보드 화면사용하는 기술AngularJS: AngularJS를 기본 프레임워크로 사용하고 있습니다. AngularJS는 SPA (Single Page Application) 형태의 웹 애플리케이션을 빠르게 개발할 수 있도록 도와주는 MVC 프레임워크입니다. 조이에서는 현재 프로덕션 버전인 1.3.x를 사용하고 있습니다. 대시보드는 사용자의 이벤트에 따라 동적으로 데이터를 변경해야하는 애플리케이션적 요소가 많기 때문에 AngularJS의 양방향 데이터 바인딩의 유용함을 느끼고 있습니다.D3.js: 다양한 그래프를 아름답게 보여주기 위해서 D3.js를 사용합니다. D3.js는 데이터 시각화를 위한 자바스크립트 라이브러리로, HTML/CSS/SVG 등의 웹 기술을 이용해 그래프를 그릴 수 있습니다. 자유도가 매우 높아서 생각할 수 있는 많은 형태의 그래프를 그릴 수 있으며 부드러운 전환이나 애니메이션도 추가할 수 있습니다. 다만 초기 학습 비용이 높고 신경쓰지 않으면 너저분한 코드가 양산될 수 있다는 단점도 있습니다.CoffeeScript: 자바스크립트를 더 깔끔하고 효율적으로 사용할 수 있도록 Compile to JS 언어를 사용하는데, 여러 선택 사항 중 CoffeeScript를 사용하고 있습니다. CoffeeScript는 문법적 간결함 덕분에 타이핑을 줄이고 빠르게 코드를 작성할 수 있습니다. 특히 클래스와 클래스 상속 등을 문법적으로 지원하기 때문에 OOP적인 설계를 할 때 도움을 받았습니다. 하지만 자바스크립트와는 다른 새로운 문법을 익혀야하고 그마저도 일관성이 떨어지는 문제가 있습니다. 또 특별한 신경을 쓰지 않으면 가독성이 안 좋은 코드를 작성하기 쉽습니다. 조이에서는 Lint 툴과 코드 리뷰를 통해 코딩스타일을 엄격히 제한하고 있습니다.개발 환경빌드 및 배포: Bower와 npm(Node Package Manager)을 이용해서 패키지를 관리합니다. 빌드 시에는 JS Minify & Uglify, HTML/CSS 최적화, CoffeeScript Lint를 통한 코드 품질 검증, Karma를 이용한 테스트 수행 등의 과정을 거치며 이 모든 빌드 과정은 Grunt를 사용하여 자동화하고 있습니다. 빌드가 끝난 파일들은 AWS (Amazon Web Service)의 S3 저장소로 배포하고 있습니다. 배포 과정 역시 Grunt의 task로 자동화되어 있습니다.코드 관리: 모든 코드는 Jenkins로 통합되어 자동화된 테스트를 통과해야 합니다. 모든 커밋은Gerrit을 통해 다른 엔지니어의 리뷰를 거쳐야만 머지를 할 수 있습니다. 따라서 모든 코드는 적어도 둘 이상의 엔지니어가 이해하고 있습니다. 같은 코드에 대해 더 좋은 설계가 있는지 논의하면서 함께 코드를 발전시켜 나갑니다. 더 좋은 설계가 발견될 때마다 수시로 리팩토링을 진행합니다.프로젝트 관리: 디자이너와 엔지니어 그리고 기획에 참여하는 데이터 분석가 등은 Trello를 이용해 태스크와 이슈를 관리합니다. 일주일을 한 번의 스프린트로 보고 매주 월요일에 일을 분배하고 금요일에 회고를 합니다. 이 과정에서 엔지니어도 기획에 능동적으로 참여할 수 있으며, 어떤 데이터를 어떤 형태의 그래프로 보여주어야 효과적인지를 함께 고민할 수도 있습니다.그림2. 트렐로를 이용한 프로젝트 관리지속적으로 고민하는 부분성능 이슈: 대시보드에는 많은 수치 데이터를 다룹니다. API 서버로부터 하나의 큰 JSON 데이터를 받아서 시간별/일별/요일별/날씨별/최고기온별/평일휴일별 방문객 정보, 방문전환율/체류전환율/구매전환율 등의 지표를 그리기 위한 데이터로 가공합니다. 여기에는 계산량이 적지 않기 때문에 성능에 대한 고민을 많이 하게 됩니다. 연산 로직을 더 간단히 하거나 더 적게 Draw/Redraw 하는 방법을 고민하고 응답성을 향상시키기 위해 Async하게 연산하는 등의 고민을 합니다. 설계: 대시보드는 빠르게 업그레이드됩니다. 기능이 추가되고 변경됨에 따라 그에 맞는 좋은 설계도 계속해서 변합니다. 수시로 진행하는 리팩토링이 좋은 설계를 만든다고 생각합니다. 따라서 리팩토링에 쓰는 시간을 아까워하지 않습니다.테스트: 테스트는 매우 중요합니다. 특히 버그로 인해 잘못된 데이터가 보여지는 것은 용납될 수 없습니다. 그래서 데이터를 가공하는 로직에 대한 테스트는 엄격하게 수행됩니다. 설계 단에서도 테스트하기 쉬운 코드를 작성하려고 노력합니다.최신 기술: 조이의 프론트엔드 팀은 최신 기술에 민감합니다. Gulp, Angular 2.0, EcmaScript6, TypeScript, React와 같은 자바스크립트 최신 기술들에 관심을 갖고 그들의 기본 철학이나 장단점들을 파악하려고 노력합니다. 때때로 우리에게 더 잘 맞는 기술이 등장하면 과감하게 적용하기도 합니다.맺음말조이는 임베디드 기술과 빅데이터 기술을 보유한 기술 회사입니다. 그러나 프론트엔드 기술 역시 그 못지 않게 중요하게 생각하고 있습니다. 말뿐이 아니라 며칠 전에는 OKKY 자바스크립트 컨퍼런스에 후원을 하고 좋은 자바스크립트 개발자들을 만나기 위해서 부스를 차리기도 했습니다. 앞으로도 프론트엔드 기술 관련 컨퍼런스에 후원도 하고 기여도 계속 할 계획입니다.저도 조이에서 엔지니어로 일하면서 훌륭한 동료 엔지니어들과 함께 많은 성장을 했습니다. 무엇보다 기술적인 욕심과 의욕이 넘치는 분위기 속에서 일하는 것 자체가 즐겁고요. 혹시 이 글을 읽고 위와 같은 고민을 공유하고 폭풍 성장을 함께 할 멋진 자바스크립트 개발자가 있다면 이 글을 읽어보시길 바래요 :)그림3. OKKY 자바스크립트 컨퍼런스 부스#조이코퍼레이션 #개발팀 #개발자 #개발환경 #업무환경 #기업문화 #조직문화
조회수 1419

반복적인 모니터링 프로세스 구축

IT 서비스에 장애가 발생 할 경우 모니터링 프로세스는 장애를 찾는 것으로 끝나지 않습니다. 장애를 발견하는 것은 모니터링 프로세스의 시작 점이며 최종적으로 모니터링을 통해 장애의 근본 원인을 찾아낼 수 있어야 합니다. 그리고 찾아낸 원인들은 예측과 추론에서 확인까지 이르는 하나의 프로세스로 정착되어 다시금 모니터링 과정에 포함되어져야 합니다. 이렇게 서비스를 운영하는 과정에서 근본적인 장애를 찾기 위해 모니터링을 어떻게 이해해야 하는지 알아보겠습니다. 우리가 모니터링 해야 하는 지표어플리케이션 지표(WORK METRICS)- 처리량 지표(THROUGHPUT)- 성공 지표(SUCCESS)- 에러 지표(ERROR)- 성능 지표(PERFORMANCE)시스템 지표(RESOURCE METRICS)- 가동률(UTILIZATION)- 포화상태(SATURATION)- 에러 지표(ERROR)- 이용률(AVAILABILITY)이벤트(EVENTS)- 코드 변경(CODE CHANGES)- 경고 알림(ALERTS)- 규모 변경(SCALING EVENT)- 기타(ETC)IT 서비스를 운영하는 과정에서 발생하는 문제의 근본원인을 추적하기 위한 모니터링 데이터는 크게 3가지로 나눌 수 있습니다. 어플리케이션 지표(Work metrics)서비스의 흐름(트렌젝션)을 측정하여 시스템의 최상위 레벨의 이슈를 보여줍니다. 시스템 지표(Resource metrics)이용률, 상태, 에러 또는 시스템 의존적인 리소스의 이용률을 수량화합니다.이벤트(Events)코드변경, 내부 경고, 확장 이벤트와 같이 드물게 발생하는 불연속적이 이슈를 보여줍니다.일반적으로 IT 모니터링의 핵심 이슈는 어플리케이션 지표를 통해 확인할 수 있습니다. 하지만 다른 지표들 또한 어플리케이션의 지표에서 나타난 문제의 원인을 찾기 위한 중요한 요소이기 때문에 같이 모니터링 해야 합니다. 시스템 지표를 통한 모니터링인프라스트럭쳐는 대부분 시스템의 자원으로 구성됩니다. 최상위 수준에서 유용한 작업을 하는 각각의 시스템들은 다른 시스템들과 연동하기도 하는데요. 예를 들어, 여러분의 아파치 서버가 MySQL 데이터베이스를 자원으로 사용하여 요청을 처리하는 작업을 지원할 수 있습니다. 연관된 작업을 따라 들어가보면 MySQL은 제한된 커넥션 풀을 관리하기 위한 리소스를 가지고 있고 MySQL이 실행되는 서버의 물리적인 리소스 레벨에서는 CPU, Memory, Disk 같은 지표를 보게 됩니다.어플리케이션이 서비스를 제공하는 데 있어서 각각의 리소스가 그 작업을 지원한다면 우리는 장애가 발생한 경우에, 필요한 원인을 얻는 좋은 방법을 시스템을 통해서도 찾아볼수 있습니다. 이런 프로세스를 만들어 간다면 시스템에서 발생한 경고를 통해 장애의 원인을 체계적인 조사하는데 도움이 될 것입니다. 1. 최상위 어플리케이션 지표에서 시작하기첫번째 해야 하는 질문은 "발생한 장애를 설명할 수 있는가?" 이다. 처음부터 문제를 명확하게 정의하지 못하면 이슈를 분석하기 위해 파고들어가야 하는 시스템 패스를 잃어버릴 확률이 높다.다음으로 문제가 있을 것으로 보여지는 최상위 시스템의 작업 지표를 검사해라. 이 지표들은 종종 문제의 원인을 알아내거나 또는 적어도 추적해야 하는 방향을 알려 줄 것이다. 예를 들어 성공적으로 진행된 작업의 성공율이 한계치 이하로 떨어졌다면 에러 지표를 찾아보고 반환된 에러의 형러의 타입을 살펴봄으로써 문제의 방향을 찾아나갈 것이다. 반면에, 대기시간이 길고 외부 시스템에 의해서 요청된 작업처리량이 매우 높다면 시스템 과부하로 인한 문제일 확률이 높다. 다만 와탭의 어플리케이션 분석 서비스를 사용한다면 약간 방법을 달리해도 된다. 와탭의 성능 분포도(어플리케이션 히트맵)와탭의 어플리케이션 성능 분포도를 통해 문제가 발생한 트랜잭션을 드래그하여 선택하게 되면 실제 어플리케이션에서 발생하는 스탭들을 추적하여 문제 해결에 바로 도달할 수도 있다. 하지만 더 복잡한 형태의 장애라면 시스템의 리소스 정보를 찾아봐야 합니다.  2. 리소스 찾아보기최상위 work metrics를 조사하여 문제의 원인을 알수 없다면, 다음으로 시스템이 사용하는 리소스(물리적인 요소 뿐만 아니라 시스템의 리소스 역할을 하는 소프트웨어 또는 외부 서비스)들을 조사합니다. 해당 리소스가 높다면 리소스를 사용하는 하위 Application 지표를 찾아보는 방식으로 찾아나갑니다. 와탭의 데시보드(CPU, MEMORY)3. 변경 내용 찾아보기다음으로 지표에 연관된 경고와 다른 이벤트들을 살펴봅니다. 문제가 발생하기 직전 코드가 릴리즈 되었거나, 내부 경고가 발생하고나 다른 이벤트가 등록되었다면 문제와 연관된 부분을 찾아봐야 합니다. 4. 수정하기 (잊지 말기)문제의 원인을 찾았다면 문제의 원인이 되는 상태를 수정해보고 증상이 사라지는 것을 확인합니다. 증상이 더이상 나오지 않는다면 향후 유사한 문제를 피하기 위해 시스템을 어떻게 변경할지 고민해야 합니다.  서비스가 중단된 상황이 오면 1분이 중요합니다. 문제를 찾는 속도를 높이기 위해 눈앞에서 벌어진 상황에 대한 높은 집중력을 유지하면서 대쉬보드를 상황에 맞춰 재 조정합니다. 최상위 어플리케이션 데쉬보드와 각각의 서브시스템들을 위한 대시보드를 하나씩 설정합니다. 시스템 대시보드는 시스템 지표의 하위 시스템의 키 메트릭스와 함께 어플리케이션 메트릭을 확인 할 수 있어야 합니다. 이벤트 데이터도 이용가능한 상황이라면 연관 분석 차트에서 관련된 이벤트가 올라가 있어야 합니다. 와탭의 알림 서비스정리하기   서비스에 장애는 무조건 발생하지만 우리는 모니터링을 통해 빠르게 해결 할 수 있습니다. 이를 위해 표준화된 모니터링 프로세스를 만들고 대시보드로 연관관계를 만들어 놓는다면 문제를 빠르게 추적 조사할 수 있습니다. 가능하면 모든 지표는 어플리케이션 지표에서 부터 찾을 수 있도록 대시보드를 구성합니다.인프라스트럭처를 통해서도 문제를 분석할 수 있습니다. 시스템에 대해 대시보드를 설정하고 주요 지표들을 올려놓아야 합니다. 문제의 원인을 조사하는 것은 증세가 나타나는 최상위 시스템에서 부터 시작합니다. 문제가 되는 리소스가 발견되면 문제를 발견하고 수정할 때가지 리소스에서 발견되는 패턴을 조사하고 적용시키는작업을 반복해야 합니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 1066

Team Profile: Meet Jungkap

As a yet minuscule startup, each member holds a significant power over the overall atmosphere of the team. And in our ultimate quest to make big waves in the data world, we need to make sure that the people at the helm are at least kind of cool. We think we’ve done a pretty good job so far in assembling a society of unique but equally driven members.So we bring you this seven-part series, one of each devoted to interviewing each of our members in detail, to give you an in-depth glimpse into the people responsible for bringing you the future of machine learning with Daria. Plus, we peppered the interviews with questions from Dr. Aron’s “The 36 Questions that Lead to Love”*, cherry picked to make work appropriate and concise, but interesting.(*actually falling in love with our members highly discouraged)Jungkap, the most recent addition to our team, made the move from sunny Santa Clara to Seoul, a city that is slowly freezing over as you read this. But he is used to the cold, Jungkap assures us, having spent his doctorate years in the apocalyptic winters of Michigan. When he’s not busy helping build Daria’s machine learning engine, Jungkap devotes his time to re-exploring Korea and taking care of his cats Jolie and Brad (named so before the tragic dissolve of Brangelina). Learn more about him here!Tell us about your role at XBrain.JP: I joined the team as a machine learning engineer, and my main task is to develop our machine learning engine. I have the task of researching and finding solutions to obstacles that hinder people from using automated machine learning technology with ease.What does a typical work day look like for you, morning to evening?JP: I get to work at about 9:15 AM (*the earliest, we note, out of any of the members), and check the Slack messages and emails I got overnight. Since I concentrate the best in the morning, I take a look at relevant articles and dissertations then. Since I didn’t major in machine learning at school, there’s a lot I still have quite a bit to learn, learning’s still a big part of my work process. After I’ve warmed up a bit, I study the code that’s already been written, and develop the parts that need to be developed. Then I have lunch with the team, which is a part of our culture that I really enjoy — a set meal time and a chance to have a conversation with other members. Today I did investigation into an issue we had with the machine learning engine, and worked on how to solve that problem based on my discoveries. I think I’ll be working on constructing that idea into actuality, with a lot of validation, tests, trial and error.What are the parts of your job that you enjoy the most?JP:I enjoy enhancing and optimizing processes, and actually seeing improvement after I’ve worked on something. I’m working on improving the system that we have right now, but a long-term project we have in mind is developing technology of XBrain’s own, and figuring out the needs of our customers. In order to do that, I’m spending a lot of time looking into any issues that we have with our current technology, hoping to get insight from the process.What are the least enjoyable/most challenging parts of your job?JP:The most challenging, rather than the least enjoyable, is issue definition. There are four types of situations to what can happen: either I find a problem that’s already been found, or something that’s so insignificant that no one cares, something that’s unsolvable, and, finally, an issue that’s both important and solvable. The fourth is what we’re going after, and the process is long and arduous, but I do enjoy it to a certain extent.Pick one item on your desk that tells us something about you.JP:I don’t have much stuff on my desk, which is something I also noticed about some of the Silicon Valley companies I visited while I was working in the States, like Twitter or LinkedIn. Most engineers’ desks just had computers on them, and I appreciate that sort of simplicity.Jungkap keeps things on his desk simpleWhat made you go into machine learning?JP:I was on the user end of machine learning technology in grad school and at work thereafter, and felt that the process of utilizing and understanding tools was too complex and difficult. I thought that I might find it fulfilling to optimize this process myself by becoming a machine learning engineer myself.Why XBrain?JP:First off, I really liked how the team was set up, people-wise. I was also struck by the competency of the members and the company culture, which suited me well. The values that XBrain pursues, and the ideas that it had about the future of machine learning technology was in line with my own. Not to see it simply as a source of profit, but as something that could potentially bring a lot of people a great deal of help.As our most recent member, what’s a vision you have for our team?JP:It’s not so much a vision as a direction we should be heading in — despite how machine learning is such a huge buzzword now, I think it’s still in the process of research and development. A lot of work needs to be done before it can start having a real impact in the world. What our role is, then, is to look far ahead and start with the basics.Recommend a movie for our next Cinema Society, please.JP:Downsizing, which hasn’t come out in Korean theaters yet, but I think it presents a lot of points for discussion.If you could sum up XBrain in three words or less?Serious, but quirky.If you could have dinner with any XBrain member, who would it be and why?JP: JY — we haven’t really gotten a chance to share a meal, and I feel like he’d have some interesting storiesWhat can you tell us about the JP 10 years from now?JP:He will probably be a more seasoned machine learning engineer, from his 10 years of research and studying. I’m a novice engineer now, but I’d like to be in a more senior position then, mentoring younger engineers.Given the choice of anyone in the world, whom would you want as a dinner guest?JP:Carl Sagan, who first got me interested in science and technology. In my head, he’s this benevolent father figure who would offer to mentor me.Would you like to be famous? In what way?JP:No…What would constitute a “perfect” day for you?JP:I think a “perfect” day is a day that’s yet to come. Is that too weird to publish?If you were able to live to the age of 90 and retain either the mind or body of a 30-year-old for the last 60 years of your life, which would you want?JP:The body, definitely. Minds can mature — bodies not so much.For what in your life do you feel most grateful?JP:Probably soundness of mind and body.If you could wake up tomorrow having gained any one quality or ability, what would it be?JP:Speedier comprehension upon reading something?What is the greatest accomplishment of your life?JP: Forging strong relationships with good people.What, if anything, is too serious to be joked about?JP:It depends on the audience, I think. Anything that they might consider offensive, or a weak spot, is off limits.Your house, containing everything you own, catches fire. After saving your loved ones and pets, you have time to safely make a final dash to save any one item. What would it be? Why?JP: My hard drive — it has everything on it.#엑스브레인 #팀원소개 #팀원인터뷰 #기업문화 #조직문화 #팀원자랑 #머신러닝 #머신러닝엔지니어
조회수 3334

빅데이터 '분석가' '전문가'가 부족한 이유...

업계에서는 대기업이나 공공기관 등의 데이터 분석 수요가 커지면서 빅데이터를 다루거나 데이터 분석가들을 찾는 기업이 늘어난다고 하는 기사나 이야기들이 떠돌아다닌다.한국정보화진흥원에서 발간한 '2015년 빅데이터 시장 현황조사'보고서에 의하면 빅데이터 공급기업과 수요기업 모두 빅데이터 분석가가 필요하다고 내다보고, 많은 데이터 분석가가 필요하다고 이야기했다.분야도 금융을 비롯하여 통신, 커머스 등을 아우르고, IT 관련부서뿐만 아니라, 현업이라고 불리는 마케팅이나 영업도 포함된 관계에서의 데이터 활용을 위해서 빅데이터 '분석가'가 필요하다고 이야기를 한다.죄송하지만.. 한국형 환경에서는 '빅데이터 분석가'나 '전문가'는 그다지 필요 없을 것 같다.1. 변화하지 않는 기업어차피 정해져 있는 프로세서, 내부 R&R과 내부 혁신을 하기 위한 인사이트를 찾고, 데이터 변수를 찾는다고 하더라도 굳이 기업 내부의 변화를 일으키지 않을 것이기 때문에 '진정한 데이터 분석가'는 해당 기업에 무의미할 것이다.정말, 전문가라면 '내부 혁신'에 대한 키워드들을 뽑아줄 텐데... 이런 이야기는 '컨설팅'업체에서도 하지 않고, 내부에서도 '금기'시 해야 할 단어들이 대부분이다.만일, 대기업인 중요 키워드가 '오너'의 키가 문제라고 지적한다면... 아마도, 해당 부서나 관련자들은 움직이지도 못할 것이다.죄송하지만, '내부 혁신'이 불가능하고, '오너'중심의 대기업은 데이터 분석가가 필요하지 않다. 다만, '오너'의 생각을 읽고서 적당하게 마사지된 '데이터'를 보여줄 '외부 데이터 분석'서비스 업체만 필요할 뿐이다.그래서, 국내에서는 데이터 분석 서비스 업체 정도가 적당하다.2. 기업과 조직에 데이터가 없다.프로세스 하단에서 동작하는 수많은 로그들을 추적 감시, 감사하는 시스템이 가동되고 있어야 하며, 고객 서비스를 하는 서비스 집단에서도 하단에서 아이디어가 상단으로 올라가는 환경들이 이미 가동되고 있어야 한다. 데이터의 대부분은 그런 인사이트를 증명하는 근거가 되기 때문이다.이미, 중요한 움직임을 보이고 있을 때에만 '의미 있는 정보'를 추출할 데이터들이 축적되는데... 사실상, 의미 없이 마사지된 '보고서'들만 존재한다.원천적으로 의미 있는 데이터를 추출할 데이터가 있어야 하는데.. 대부분이 왜곡된 정보들이거나, 특정 힘에 의해서 데이터들이 왜곡돼 있다면, 해당 기업과 조직은 데이터가 없다고 봐야 한다.3. 오랜 경험을 축적한 실전 전문가들이 일찍 퇴직한다.빅데이터를 통해서 단지 현황만을 보여주는 것이 아니라, 기업의 미래나 새로운 먹거리를 유도할 수 있는 인사이트를 추출하기 위해서는 해당 도메인이나 해당 마켓에 익숙하고 경험이 풍부한 전문가들이 같이 있어야 한다. 실제, 데이터가 의미하는 방향성이나 수치, 지수가 어떤 것을 의미하는지 읽어 줄 수 있는 것은 데이터 전문가들이 하는 일이 아니다.해당 업무와 해당 도메인의 전문가가 그 '수치'를 읽어 줄 수 있는 것이다.대부분의 기업에서 '실전'이거나 '실제 업무'에 익숙한 전문가나 경험이 축적된 사람들은 하청업체이거나 이미 퇴직한 경험이 풍부한 사람들이다.해당 기업에서는 아무리 데이터가 분석되어도 어떤 의미인지 판독해줄 사람이 없다.4. IT기술 전문가가 필요한 것이 아니다.빅데이터나 머신러닝과 같은 지식화 인사이트는 절대 IT기술이나 주변의 소프트웨어 설루션으로 만들어지는 것이 아니다. 기업 내부에 축적된 '지식'을 기반으로 '사람'을 기준으로 데이터가 만들어진다. 데이터 분석 전문가는 단지, 그것의 가치를 '판정'해줄 수 있는 기준을 마련해줄 뿐이다.대부분의 '한국형'조직들은 데이터 거버넌스 조직도 없으며, 제대로 된 인사시스템이 가동되지 않고 있다. 슬프지만, 빅데이터 전문가들은 내부에서 영입하는 것이 아니라, 내부에서 자생적으로 생성되는 것이다.자생적으로 빅데이터 전문가가 생성되지 않는 조직은 이미, 지식화가 불가능한 형태이기 때문에, 너무 무리하지 말고, 현재 환경에서 연착륙하는 것을 고려하는 것이 최선일 것이다.역시, '한국형'에서는 굳이 '빅데이터 분석가'가 필요한 것이 아니라, '빅데이터 분석가 코스프레'를 하는 사람이 필요한 것 아닌가?오너가 이야기하는 'A'를 'A'처럼 써줄 수 있는 코스프레가 가능한 사람이면 충분한 것 아닌가 한다.
조회수 1045

[사람이 서비스다] #4 JD, 안드로이드앱 개발 담당

셀잇은 기존 중고거래 시장에서 이용자들이 겪는 불편과 불안감을 해소하기 위해 등장한 서비스라는 자부심을 가지고 구매자와 판매자를 잇는 접점이 되고자 합니다. 이를 위해 서비스를 기획하고 실행하는 저희 구성원들에 대한 이야기를 간간히 들려드리고자 합니다. 좋은 서비스든 아이디어든 결국 사람이 하는 일이니까요-저희가 어떤 생각을 품고 어떤 마음가짐으로 살아가는지에 대해 진솔하게 풀어보고자 합니다. 이 청년들의 이야기, 한 번 들어보실래요? Interviewee: JD (제이디, 개발팀 / 안드로이드앱 개발 담당)Interviewer: Austin (오스틴, 마케터)  우선 자기소개부터 간단히 해주시죠. 흔해 빠진 소개일랑 집어치우고! 최대한 자신을 우리에게 알려봐요! 정~ 뭐라고 쓸지 모르겠으면 자기 이름으로 삼행시라도 해보세요. 우선 저에게 이런 귀찮은 일을 안겨준 브라이언에게 감사의 인사를 전하는 바입니다. 덕분에 독무대에 이어 다시 한번 불면증에 시달리게 되었어요. 그건 브라이언에게 개인적으로 앙갚음(?)을 해주시고, 본인 소개부터 해주세요. 저도 바쁘답니다. 안녕하십니까? 저는 전남 해남의 작은 시골 마을에서 2남 중 장남으로 태어나 안드로이드 개발을 하고 있는 JD라고 합니다. 원래는 게임 개발이 하고 싶어서 프로그래밍 공부를 시작하였지만 어쩌다 보니 앱을 개발하고 있네요. (뭐, 뭐지? 이 ‘신입사원의_패기.wav’ 같은 느낌은?) 그럼 현재 셀잇에서 개발자로 일하시겠군요. 그럼 본인이 하는 일 중에서 이건 나만의 스페셜티다! 하는 부분은 무엇인가요? 당연히 안드로이드 개발입니다. 우리 회사에서 저 밖에 못하는 거죠~(찡긋) (찡...찡긋?) 하하하;; 네네 그렇군요. (셀잇이 잘 되는 이유가 이거였군. 정상적인 놈이 없는...) 그게 다인가요? 개발하시다가 잘 안풀리거나 열 받을 때는 어떻게 하나요? 자세한 건 ‘영업비밀’이니까- 전 안풀리면... 음- (한참을 생각한다)잠을 잡니다. (역시 오늘도 산으로 가는건가…) 아…(포기한 듯) 얼마나 자나요? 한 20분 정도 짧게 자요. 사실 잔다기보다는 자는 척을 하면서 생각을 하는거죠. 읭? 굳이 자는 척을 해야 될 필요가 있나요? 그냥 대놓고 생각하면 안되는건가요? 안됩니다! 온전한 집중을 위해서 자는 척을 해야 해요. (정적) 인터뷰 하는 중에 월드시리즈까지 끝나버렸네요... 올해 모든 야구가 끝나버렸어요 ㅠ (후우... 내가 이걸 왜 시작했을까...) 그럼 일 얘긴 그만하고(더 할 수도 없겠어;;) 업무 외의 시간에는 주로 뭘 하시나요? 듣자하니 야구를 좋아하는 것 같은데- 야구를 봅니다. 한국 야구는 기아를 응원하고, 메이저리그는 한국 선수들이 진출한 팀들을 응원하고 있어요. 주말에는요? 주말이면 아침에 일어나서 메이저리그 두 경기 정도 보고 오후에는 한국 야구를 보면서 하루를 보냅니다. 이제 야구 시즌도 다 끝나서 다가오는 겨울이 두렵습니다ㅠ 차라리 야구선수로 전향하시는게- 만약 실력이 문제라면 사회인 야구팀이라도 해보시는건요? 그건 돈도 많이 들고, 일단 귀찮고-부상 위험도 크고, 일단 귀찮고-그냥 친구랑 캐치볼 하는 것으로 만족합니다. 그리고 일단 귀찮고- 커피나 한 잔 하실래요? 커피나 마시면서 다른 얘기로 넘어가죠~ 괜찮습니다. 저는 카페인 마시면 안되서- 아, 그럼 그냥 계속 하죠. (여자랑은 술 마시고 나랑은 커피도 안 마시냐?-_- 쳇, 근데 이해되네...) 중고에 대해서 어떻게 생각하세요? 중고를 바라보는 가치관 같은게 있으시면 말씀해 주세요. 제가 환경 문제에 관심이 많습니다. 중고거래가 보편적으로 활성화 된다면 상대적으로 공산품의 생산량이 줄어들게 되고, 이는 지구의 자연 환경에 도움이 되지 않을까 합니다. (응? 뭔가 익숙한데?) 제가 예전에 쓴 글을 보신건가요?… 네~ 꼭 중고 거래가 활성화되서 지구 환경을 지켜주세요… 그럼 마지막으로 셀잇에서 이루고 싶은 것은 무엇인가요? 주로 컴퓨터 부품들을 중고 거래를 이용해 구매했던 적이 있는데요. 항상 직거래를 했지만 정상 작동하는지 불안했던 기억이 있습니다. 집에 와서 컴퓨터에 장착해 보고서야 안심을 하곤 했었는데, 셀잇을 이용하면 최소한 이런 걱정 없이 믿고 안심하며 거래할 수 있는 서비스로 자리 잡을 수 있었으면 합니다. 아니 이건 셀잇이 나아갈 방향이고~ 저는 제이디 본인 개인의 목표에 대해서 물은거예요. 셀잇이 곧 저입니다. 화, 화이팅...! (후우...) 이런 자리가 부끄럽죠? 가슴 속에 뜨거운 뭔가가 있는게 보이지만 굳이 밝히지 않으시겠다면 앞으로 안드로이드 앱을 통해 그 뭔가를 제가 찾아보겠습니다. (빨리 끝내려 애쓴다;;) 인터뷰는 이 정도로 마치는 것으로 하고~ 셀잇에서 칭찬하고 싶은 사람 한 명만 꼽아주세요. 이유도 함께 말해주세요. 전 네이쓴을 칭찬하고 싶습니다. 특유의 친화력과 유머러스함으로 주변 사람들을 기분 좋게 만들어주는 아주 훌륭한 팀원이기 때문입니다. 로봇입니까? 네? 아닙니다. 그럼 오늘 수고하셨습니다. 아! 최근에 셀잇 앱 2.0이 배포됐는데 감회가 남다를 것 같 같은~ 어떠세요? 딱히 이렇다할 소감은 없습니다만 이용자분들이 이전보다 더 편하게 서비스를 이용할 수 있었으면 하는 바람입니다. (로봇 맞네...) 넵- 수고하셨습니다. (하아... 네이쓴이라... 다음엔 우주로 가겠구만...)#셀잇 #번개장터 #인터뷰 #팀소개 #팀인터뷰 #팀원소개 #기업문화 #조직문화 #회사문화 #사내문화
조회수 295

프로그래밍 수업의 모든 것.

안녕하세요 엘리스입니다. :)엘리스의 프로그래밍 수업은 누구에 의해서, 어떻게, 어떤 생각을 바탕으로 만들어질까요?미래를 이끌어나갈 컴퓨터 사이언스 기술과 그 근간이 되는 교육 사이에서 좋은 프로그래밍 수업을 만들기 위해 치열하게 고민하는 엘리스의 코스 매니저가 직접 이야기합니다! 마침 엘리스는 코스 매니저 채용 중에 있으니 관심이 있다면 눈여겨 봐주세요!코스 매니저가 관여한 프로덕트로 인하여 사용자가 성장을 하고 있다면 그것은 충분히 의미 있는 일.안녕하세요 저는,트라우마를 극복한 프로그래밍 수업 크리에이터.Q. 자기소개 부탁드려요.A. 엘리스의 프로그래밍 과목을 만드는 코스 매니저 이용희입니다.Q. 엘리스에서 일하게 된 이유는 무엇인가요?A. 원래는 프로그래밍에 대한 트라우마가 있었어요. 하지만 기술 창업에 대한 꿈이 있었기 때문에 프로그래밍은 극복해야 할 산이었죠. 엘리스는 가장 뛰어난 기술자들이 모여 창업한 스타트업이에요. 당연히 기술 창업을 가장 가까이에서 경험할 수 있는 매력적인 곳으로 느껴졌죠. 그리고 프로그래밍 교육을 제공한다는 것 역시 기회로 느껴졌어요. 저와 같이 프로그래밍을 미워하고 두려워하는 사람들에게 보다 쉽게 배울 수 있는 환경을 마련해주고 싶다는 기대로 일을 시작하게 되었습니다.Q. 두려운 대상을 향해 몸을 던지셨군요! 그런데 코스 매니저가 프로그래밍을 몰라도 되나요?A. 많이 알면 알수록 당연히 좋아요. 많이 알고 있을수록 시도할 수 있는 것도 많고 학생에게 전달해줄 수 있는 것은 더욱더 많기 때문에요. 하지만 최소한으로는 Class가 뭔지 알고 있으면 OK. 예를 들어서 코드를 보고 이 코드가 어떤 목적을 갖는지 알 수 있으면 직접 코딩을 하지는 못한다고 해도 괜찮아요.Q. 코스 매니징 외에도 라이브 수업 참여, 조교, 챌린지 사회자 등 많은 역할을 하셨는데 이유가 있나요?A. 좋은 수업을 만들기 위한 첫 번째 방법은 코스를 만드는 모든 과정에 참여하는 사람들의 역할을 직접 체험해 보는 것이라고 생각했어요. 학생으로서, 조교로서, 사회자나 라이브 어시스턴트로서. 이렇게 하니까 학생으로서 수업을 접할 때의 감상은 무엇인지, 조교로서 가르쳤을 때는 어떤 어려움이 있는지를 알 수 있었어요. 라이브 수업 어시스턴트로 참여했을 때는 방송하시는 선생님들의 애로사항을 알 수 있겠더라고요.코스 매니징의 정수.프로그래밍적 성장을 도움으로써 가치를 만들어 냅니다.Q. 코스 매니징의 A to Z는? 구체적인 업무 프로세스가 궁금해요.A. 크게 기획 - 모집 - 제작 - 분석의 네 단계로 이루어져 있어요. 1. 수업 기획 -  어떤 과목을 만들 것인가? 주차별로 무엇을 다룰 것인가? 흥미로운 콘텐츠는? 2. 선생님, 조교 모집 - 엘리스가 구상한 수업을 가장 잘 전달할 수 있는 선생님과 조교를 모집. 3. 수업 제작 및 운영 - 실습 문제, 강의 자료 등을 엘리스의 색깔로 제작하여 수업을 운영. 4. 데이터 분석 - 학생들의 피드백과 데이터를 다음 수업의 발전 및 교육자와의 관계 개선에 반영.Q. 업무 방식은? 어떤 메리트가 있나요?A. 처음부터 끝까지 모든 과정을 주도해나가는 방식이에요. 어떤 회사를 가도 프로덕트의 end to end 프로세스를 전부 경험하기는 어려운데 엘리스에서는 그 전 과정을 경험할 수 있어요. 저는 이러한 경험이 교육 업계나 특정 프로덕트에만 적용할 수 있는게 아니라 다른 업계에 간다고 하더라도 충분히 전환될 수 있는 좋은 경험이라고 생각해요.Q. 미래 산업의 근간이 될 교육을 직접 만든다는 중책을 맡고 계신다고 생각하는데요, 좋은 프로그래밍 수업을 만들기 위해 어떤 노력들을 하시나요?A. 그런 영향을 미칠 수 있다는 게 무서운 일인 것도 같아요. 어떤 사람들은 엘리스를 통해서 프로그래밍을 처음 접하는 것일 수도 있는데 그 경험이 불쾌했다면 앞으로 프로그래밍을 배울 생각이 전혀 들지 않을 수도 있는 거잖아요. 그래서 최대한 다양한 피드백을 받아서 수렴하려고 해요. 외적으로는 대학강의, 수많은 수업들을 참고해요. 여러 강의를 보다보면 좋은 예도 많지만 모든 수업이 재미있지는 않아요. 중간에 듣다 마는 경우도 있고요. 그럴 때마다 내가 왜 중단했고 어떤 요소를 바꾸면 엘리스에서는 학생들이 끝까지 들을 수 있을까 고민해서 반영하려고 하죠.Q. 언제 보람을 느끼나요?A. 내가 관여한 프로덕트가 누군가에게 임팩트를 만들어내고 나뿐만 아니라 프로덕트를 사용하는 사람들이 성장을 하고 있다면 그것은 충분히 가치 있는 일인 것 같아요. 저희 플랫폼에서는 대시보드를 통해서, 그리고 학생이 코드를 어떻게 짜고 있는지 보면서 그 결과를 가시적으로 확인할 수 있어요. 누군가 제가 만든 코스를 수강함으로써 실질적으로 성장하는 게 눈에 보일 때 가장 큰 보람을 느끼는 것 같아요.한 번은 한 선생님께서 학생으로부터 ‘선생님 덕분에 취업할 수 있었어요’라는 메시지를 받은 것을 엘리스와 공유해주셨는데 그때 정말 행복하더라고요. 이게 엘리스가 추구하는 거다,라는 생각을 했어요. 엘리스도 하나의 커뮤니티이고 싶거든요. 이 경우에는 학생-선생님-엘리스가 서로의 영향으로 좋은 결과를 만들어 낸 거죠. 이런 접점을 앞으로 더 많이 만들려고 생각하고 있어요.대시보드에 나타나는 학생들의 학습 현황 및 성취도.엘리스는 이런 팀.가치, 성장, 사람. 포기할 수 없는 세 가지가 있는 곳.Q. 함께 일하는 동료들은 어떤 사람들인가요? 총평을 하자면?A. 항상 내가 최고의 사람들과 함께하고 있다라는 확신이 있어요. 각자 자기 분야에서 최고의 실력을 가진 사람들과 함께 일한다는 것만으로도 큰 자극이 되죠. 프로그래밍이든 스타트업 생존 노하우든 항상 뭔가를 새롭게 배우고 성장하게끔 동기부여를 해주는 사람들이에요. 저는 트라우마가 있었을 정도로 프로그래밍을 두려워했지만 이들과 함께 일하며 작은 피드백을 하나 듣는 것만으로도 제 실력이 빠르게 성장한다는 것을 몸소 느낄 수 있었어요. Q. 엘리스의 분위기, 팀 문화는 어떤가요?A. 새로운 것에 도전하는 것을 환영하는 수평적이고 자유로운 팀. 인턴도 아이디어를 제시할 수 있어요. 이 다음이 더 중요한데, 아이디어에서 그치는 게 아니라 활발한 피드백이 오가요. 아이디어를 실행하기 어렵다고 판단하더라도 왜 그렇고 어떻게 발전시킬 수 있는지 이야기하죠. 실행하게 되었을 때는 아이디어를 제시한 사람에게 일에 대한 권한이 전적으로 주어지고요. 저도 처음엔 파트타임 인턴이었지만, 이런 팀문화 덕분에 계속해서 업무 범위를 확장하고 제 역량을 키울 수 있었어요.코스 매니저 채용.Generalist & Infinite LearnerQ. 현재 코스 매니저를 구인하고 있는데요. 코스 매니저에 적합한 성향이 있나요?A. 두 단어가 떠오르네요. Generalist, 그리고 Infinite Learner. 깊게 한 분야를 아는 사람보다는 얕고 넓게 아는 사람이 더 적합하다고 생각해요. 다르게 말하면 새로운 것을 시도하는 것을 좋아하고 새로운 것을 접할 때 포용력이 높은 사람이요. 두 번째로는 배움에 재미를 느끼는 사람. 엘리스는 교육 스타트업이고 코스 매니저는 직접 교육의 경험을 만드는 사람이니 스스로가 배움에서 행복을 느끼는 사람이라면 훨씬 더 재미있게 일할 수 있겠죠. 한 가지 덧붙이면, 데이터 분석을 배우고 싶은 분께 엘리스는 최고의 장소입니다.Q. 코스 매니저로서 갖추고 있으면 좋은 역량이나 자질이 있다면?A. 소통 능력과 균형 감각. 코스 매니저는 수업을 만드는 모든 단계에서 다양한 이해당사자들과 일하게 돼요. 이들과 원활하게 소통하고 의견을 공유하는 게 중요하죠. 그리고 다양한 사람들 사이에서 최고의 균형을 찾아내는 것도 중요해요. 예를 들어서 선생님의 경우 개발만 해왔고 교육이라는 것을 접해본 적이 없는 분들이 대부분이고, 학생은 프로그래밍을 처음 접하면 그 수업이 좋은 건지 아닌지 평가하기 어려워요. 때문에 코스 매니저가 이 둘 사이에 다리를 놓는 중재자의 역할을 하기 위해서는 다양한 시각에서 볼 수 있는 균형 감각이 필요하다고 생각해요.

기업문화 엿볼 때, 더팀스

로그인

/