마이크로소프트 Azure Web App과 Function App을 이용한 실시간 & 배치 예측 분석 솔루션 구축
넥슨은 대한민국의 대표 게임 기업이자 글로벌 마켓 리더로 전세계의 게임 플레이어들에게 최고의 게임을 제공하고 있습니다. 넥슨은 마이크로소프트와 함께 게임 서비스에 중요한 고객 이탈 예측 및 게임 AI(인공지능)를 향상시켜 더 재미있는 게임을 플레이어에게 제공하기 위한 Hackfest를 진행했습니다.
Hackfest 멤버:
- 노기태 - 넥슨코리아, 부장
- 김재석 - 넥슨코리아, 전문연구원
- 조정훈 - 넥슨코리아, 리드프로그래머
- 김주복 - 넥슨코리아, 테크니컬 디렉터
- 심예람 - 넥슨코리아, 프로그래머
- 황천섭 - 넥슨코리아, 프로그래머
- 이성희 - 디에스이트레이드, 상무
- 김환덕 - 디에스이트레이드, 이사
- 명대성 - 디에스이트레이드, 차장
- 박고은 - 디에스이트레이드, 대리
- 구예진 - 마이크로소프트, Audience Evangelism Manager
- 김대우 - 마이크로소프트, Sr. Technical Evangelist
- 류혜원 - 마이크로소프트, Audience Evangelism Manager
- 김은지 - 마이크로소프트, Technical Evangelist
고객사
넥슨은 대한민국의 대표 게임 기업으로, 1994년 설립 이후로 MMORPG(대규모 다중사용자 온라인 롤 플레잉 게임)의 선두 주자 및 글로벌 리더로 손꼽히고 있습니다. 넥슨은 50개 이상의 세계적인 PC 온라인 게임과 30개 이상의 모바일 게임 시리즈를 제작 및 퍼블리시(http://www.nexon.com/Home/Game.aspx) 하였고, 현재 대규모 투자를 통해 20개 이상의 모바일 게임 프로젝트를 개발 중에 있습니다.
온라인 게임의 대중화를 이끌어 온 넥슨은 2002년 넥슨 일본 법인, 2005년 넥슨 아메리카, 2007년 넥슨 유럽을 세우는 한편 2011년 12월 도쿄증권거래소 1부에 상장하며 본격적인 글로벌 기업으로 성장했습니다.전 세계 110여 개국에 진출, 14억 명의 이용자를 보유하고 있으며, 경쟁력 높은 콘텐츠와 서비스를 통해 세계 시장을 꾸준히 공략해 나가고 있습니다. 넥슨은 다양한 장르의 게임을 기반으로 전 세계 많은 이들에게 최고의 경험과 참신한 재미, 그리고 즐거움을 제공하고 있습니다.
고객의 난제
넥슨은 MMORPG인 ‘마비노기’의 큰 성공에 힘입어 카드 게임 TCG-Trading Card Game인 ‘마비노기 듀얼’을 Google Play와 App Store에 출시 하였습니다.
TCG의 경우, 특히 PvE(플레이어 Vs 환경) 일 때, 인공지능인 AI의 역할은 매우 크다고 할 수 있습니다. 많은 게임 플레이어들이 AI와의 게임 중에 전형적인 게임 패턴이 지루하고 재미없게 느껴져 게임을 중단하는 일이 빈번히 발생하기 때문입니다. 넥슨은 이런 게임 AI를 향상시켜 플레이어의 이탈율을 낮추고, 수익을 극대화하기 위해 스케일러블 실시간 웹 서비스, 배치 분석 등 최적의 솔루션에 대한 필요성을 느끼고 있었습니다.
솔루션
넥슨은 사용자 이탈율을 예측하기 위해 게임 AI 및 카드덱 추천, 배치 작업 등의 스케일러블 실시간 웹 서비스를 향상 시켜야 할 필요성이 있었습니다. 마이크로소프트는 이를 위해 스케일러블 서비스를 위한 인증 및 대규모 Azure Machine Learning API Call을 실현하기 위해 복잡한 게임 플레이의 프론트엔드 Restful API로 Azure Machine Learning 분석과 Azure Web App를 제안했습니다. 또한 베치 분석 작업 스케쥴러 프로세스를 위해 Azure Function App도 제안했습니다.
이 난제를 해결하기 위해 Nexon 마비노기팀과 마이크로소프트는 Azure 클라우드 기반의 아키텍처를 함께 구현했으며, Azure의 Web App과 Azure Function App을 활용해 실시간 분석 및 스케쥴링된 배치 분석 결과를 얻을 수 있었습니다.
Azure Web App을 이용한 실시간 예측 분석
먼저, 실시간 분석 과정을 수립하기 위한 구조로 Azure의 유연한 PaaS 기반 Web App을 활용했습니다. 모바일 개발자들이 현재 C#이나 Node.js 뿐만 아니라, 다양한 언어 및 프레임워크를 이용해 Web Front-End를 개발하고 있으며, 모바일 개발팀에서는 언제든지 자동 크기조정(Auto-scale) 및 손쉬운 배포(연속배포) 가능한 서비스가 필요하기에 Web App은 최선의 선택이었습니다.
Github 리포지토리 링크 : https://github.com/m-duel-project/frontend-realtime
Front-End Azure Web App의 기본적인 역할은 마비노기 게임의 기존 Game Server인 LUA 기반 On-Premise의 서버와 분리되어, 클라우드 기반으로 실시간 분석 요청을 받아 Machine Learning의 Predictive Analytics를 이용해 분석하는 것입니다. 접근 제어를 위한 기본적인 인증과 AES 등으로 암호화되어 들어오는 JSON 데이터를 일차적으로 처리하는 Restful API 서비스로 구성했고, 이 구성은 Hackfest의 편의를 위해 ASP.NET Web API로 구성했으며, Web API는 다음 과정을 수행하게 됩니다.
Front-End Web App의 역할
- Azure Machine Learning 예측 Request/Response
- 예측된 결과를 NoSQL – Azure Table Storage에 적재
- 예측된 결과를 데이터베이스 - Azure SQL Database에 저장
Azure Machine Learning - 게임 사용자 이탈율 예측 모델
Azure Machine Learning 모델 리포지토리 : https://github.com/m-duel-project/azure-hands-on/blob/master/docs/azure_ml_tutorial.md
게임 플레이어인 클라이언트 디바이스에서 실시간으로 예측을 요청하는 데이터를 받아 실시간 예측 분석을 Machine Learning으로 수행하고, 수행한 결과를 Log 목적의 Azure Table Storage에 적재하며, 게임 플레이어의 데이터를 데이터베이스(Azure SQL Database)에 저장하는 작업을 수행합니다.
Azure Table Storage는 Key/Value 기반의 NoSQL 저장소로 C# 뿐만 아니라, node.js 등의 다양한 언어에서 손쉽게 사용 가능한 Proxy 함수를 제공하며, 영구적인(Persistent) 저장소이기 때문에 향후 로그 분석에 활용 가능한 저장소로 최적의 선택이었습니다. 또한, 디바이스의 데이터로 state를 저장하기 위해 Azure SQL Database를 사용했으며, 개발자가 손쉽게 PaaS 기반 서비스로써 SQL 구문 및 저장 프로시져(Stored Procedure)로 데이터를 처리할 수 있다는 장점도 가지고 있습니다.
이 과정을 수행하기 위해 아래의 절차대로 개발 및 테스트를 진행했습니다.
- 클라이언트 디바이스로부터 받을 데이터는 Postman 툴을 이용하여 JSON 데이터를 Restful 방식인 POST로, ASP.NET Web API로 개발된 Azure Web App에 전달하였으며, 테스트는 GET 방식으로 받아 처리하는 루틴으로 넣었다.
- Azure Web App은 전달받은 데이터를 Azure Machine Learning으로 요청해 실시간 Prediction 응답을 받는다.
- 받은 응답을 Azure Table Storage에 Table 로그 데이터로, Entity Insert를 수행한다.
- Web App으로 넘겨 받았다고 가정하고 Table Storage의 Partition Key와 RowKey를 각각 사용자 ID, GUID로 저장한 후 Log 목적으로 Azure Machine Learning으로부터 받은 response JSON을 그대로 Table Storage에 저장하는 작업을 수행한다.
- Azure Table Storage에 적 여부 및 개발/테스트를 위해 Azure Storage Explorer 툴을 사용한다. http://storageexplorer.com/
- Azure Machine Learning으로부터 예측된 결과를 Azure SQL Database의 테이블에 저장하는 과정을 수행한다.
코드 리포지토리 링크 : https://github.com/m-duel-project/frontend-realtime
Web API 코드
// GET api/values
public IEnumerable<string> Get()
{
// azure ml rre start
// InvokeRequestResponseService().Wait();
string result = HttpPostRequestResponseService();
// azure table storage start
// InsertEntity();
// sql database insert
// SqlWrite();
return new string[] { result };
}
Machine Learning 호출 코드
public static string HttpPostRequestResponseService()
{
var request = (HttpWebRequest)WebRequest.Create(
//"http://requestb.in/z2ewgtz2"
"https://asiasoutheast.services.azureml.net/<API_ADDRESS>"
);
var postData = "{\"Inputs\":{\"input1\":{\"ColumnNames\":[\"idx\",\"Age\",\"NumPromotion\",\"idx\",\"AvgPlayMinDay\",\"90DaysItemPurchaseCount\",\"GameLevelRange\",\"Crystals\",\"PromotionPath\",\"Race\",\"Gender\",\"RegCode\",\"PurchaseNum\",\"LogonCountPerWeek\",\"Country\",\"ChurnYN\"],\"Values\":[[\"0\",\"0\",\"0\",\"0\",\"0\",\"0\",\"0\",\"0\",\"value\",\"value\",\"value\",\"0\",\"0\",\"0\",\"value\",\"value\"],[\"0\",\"0\",\"0\",\"0\",\"0\",\"0\",\"0\",\"0\",\"value\",\"value\",\"value\",\"0\",\"0\",\"0\",\"value\",\"value\"]]}},\"GlobalParameters\":{}}";
var data = Encoding.UTF8.GetBytes(postData);
request.Method = "POST";
request.Headers[HttpRequestHeader.Authorization] =
"<Bearer_API_KEY>";
request.ContentType = "application/json; charset=utf-8";
request.ContentLength = data.Length;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
HttpWebResponse response;
try {
response = (HttpWebResponse)request.GetResponse();
}
catch (Exception e)
{
return e.ToString();
}
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
return responseString;
}
Azure Machine Learning으로부터 예측된 데이터를 로그 저장소인 Azure Table Storage에 적재하는 코드
public static void InsertEntity()
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("GameLog");
CustomerEntity customer1 = new CustomerEntity("test", "data");
// add more code data here
TableOperation insertOperation = TableOperation.Insert(customer1);
// execute entity insert opertation
table.Execute(insertOperation);
}
Real-time 분석을 수행하면서 hackfest 팀은 아래와 같은 오류가 발생했고, 이런 형태로 해결하였습니다.
Azure Machine Learning Real-time Request 실패 및 디버깅
ASP.NET Web API에서 Azure Machine Learning API를 호출하는 비동기(Asynchronous) Request가 알 수 없는 이유로 에러가 발생했고, HttpClient object가 아닌 HttpWebRequest로도 테스트 해 보았으나, 역시 400 에러가 발생했습니다.
이를 디버깅 하기 위해 정상 동작하는 request와 HTTP 400 오류가 발생하는 비정상 오류를 모두 HTTP inspection 도구인 http://requestb.in을 이용해 비교 수행해 보았으며, 전송되는 데이터를 확인한 결과, 컬럼들이 깨져서 나왔고, 포스트 바디를 아스키 인코딩하여 문제가 발생한 것이 확인되어 UTF-8인코딩으로 변환하여 문제를 해결 할 수 있었습니다.
Azure Function App을 이용한 ML batch 분석
Azure에서 스케쥴링 작업을 수행할 수 있는 방법은 다양합니다. Azure Scheduler, Web Job, Function App 또는 VM에 quartz 구성 등 다양한 방법이 고려 가능합니다. Hackfest 동안 이런 스케쥴러의 장단점과 주요한 서비스를 검토 했으며, server-less 코딩 및 이런 경우 batch 분석을 위한 blob 트리거 및 timer 트리거, 그리고 그 이후 필요시 수행 가능한 webhook 등을 자동으로 처리해 코드에만 집중 할 수 있는 Function App을 선택하였습니다.
아래의 절차대로 진행했습니다.
- Batch Prediction에 의해 분석될 데이터는 현재 운영 중인 관리자 웹사이트(admin web)로부터 자동, 또는 수동 생성 되는 것으로 가정한다.
- 관리자 웹사이트에서 관리자가 “배치 분석”을 수행하면 Azure Blob Storage의 “input” container에 JSON 형식 파일을 on-demand 또는 주기적으로 업로드 한다고 가정한다.
- Backend Process인 function App은 Azure Blob Storage에서 파일이 업로드 되면 발생하는 트리거를 이용해 파일을 읽는 작업 수행하고, 필요할 경우 webhook을 이용하는 것도 가능하다.
- Azure function App은 Blob Storage에 올라온 파일을 Azure Machine Learning이 제공하는 Batch 분석을 통해 결과를 받는다.
- Azure Machine Learning으로부터 받은 예측 결과를 Blob Storage “output” container에 기록한다.
- Azure Function App은 Blob Storage “output”의 트리거를 통해 실행되어, 예측 결과를 Azure SQL Database 저장하는 과정을 수행한다.
Azure Function App 구현 코드
두 개의 Azure Function App으로 구현되었고, BlobTrigger로 실행합니다.
- FirstTrigger: input path를 BlobTrigger로 감지 → batch job를 생성한 뒤 job id를 이용해서 시작
#r "Microsoft.WindowsAzure.Storage"
using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
public class AzureBlobDataReference
{
// Storage connection string used for regular blobs. It has the following format:
// DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=ACCOUNT_KEY
// It's not used for shared access signature blobs.
public string ConnectionString { get; set; }
// Relative uri for the blob, used for regular blobs as well as shared access
// signature blobs.
public string RelativeLocation { get; set; }
// Base url, only used for shared access signature blobs.
public string BaseLocation { get; set; }
// Shared access signature, only used for shared access signature blobs.
public string SasBlobToken { get; set; }
}
public enum BatchScoreStatusCode
{
NotStarted,
Running,
Failed,
Cancelled,
Finished
}
public class BatchScoreStatus
{
// Status code for the batch scoring job
public BatchScoreStatusCode StatusCode { get; set; }
// Locations for the potential multiple batch scoring outputs
public IDictionary<string, AzureBlobDataReference> Results { get; set; }
// Error details, if any
public string Details { get; set; }
}
public class BatchExecutionRequest
{
public IDictionary<string, AzureBlobDataReference> Inputs { get; set; }
public IDictionary<string, string> GlobalParameters { get; set; }
// Locations for the potential multiple batch scoring outputs
public IDictionary<string, AzureBlobDataReference> Outputs { get; set; }
}
static async Task WriteFailedResponse(HttpResponseMessage response, TraceWriter log)
{
log.Info(string.Format("The request failed with status code: {0}", response.StatusCode));
// Print the headers - they include the requert ID and the timestamp, which are useful for debugging the failure
log.Info(response.Headers.ToString());
string responseContent = await response.Content.ReadAsStringAsync();
log.Info(responseContent);
}
static async Task RunBatch(TraceWriter log)
{
string storageAccountName = "<ACCOUNT_NAME>";
string storageAccountKey = "<ACCOUNT_KEY>";
string storageConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", storageAccountName, storageAccountKey);
string storageContainerName = "input";
string storageOutputContainerName = "output";
using (HttpClient client = new HttpClient())
{
var request = new BatchExecutionRequest()
{
Inputs = new Dictionary<string, AzureBlobDataReference>()
{
{
"input1",
new AzureBlobDataReference()
{
ConnectionString = storageConnectionString,
RelativeLocation = string.Format("{0}/game_data_utf_8.csv", storageContainerName)
}
},
},
Outputs = new Dictionary<string, AzureBlobDataReference>()
{
{
"output1",
new AzureBlobDataReference()
{
ConnectionString = storageConnectionString,
RelativeLocation = string.Format("/{0}/output1results.csv", storageOutputContainerName)
}
},
},
GlobalParameters = new Dictionary<string, string>() { }
};
string apiKey = "<API_KEY>";
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
// WARNING: The 'await' statement below can result in a deadlock if you are calling this code from the UI thread of an ASP.Net application.
// One way to address this would be to call ConfigureAwait(false) so that the execution does not attempt to resume on the original context.
// For instance, replace code such as:
// result = await DoSomeTask()
// with the following:
// result = await DoSomeTask().ConfigureAwait(false)
log.Info("Submitting the job...");
// submit the job
string BaseUrl = "https://ussouthcentral.services.azureml.net/<BATCH_URL>/jobs";
var response = await client.PostAsJsonAsync(BaseUrl + "?api-version=2.0", request);
if (!response.IsSuccessStatusCode)
{
await WriteFailedResponse(response, log);
return;
}
string jobId = await response.Content.ReadAsAsync<string>();
log.Info(string.Format("Job ID: {0}", jobId));
// start the job
Console.WriteLine("Starting the job...");
response = await client.PostAsync(BaseUrl + "/" + jobId + "/start?api-version=2.0", null);
if (!response.IsSuccessStatusCode)
{
await WriteFailedResponse(response, log);
return;
}
}
}
public static void Run(string myBlob, TraceWriter log)
{
//log.Info($"C# Blob trigger function processed: {myBlob}");
RunBatch(log).Wait();
}
- SecondTrigger: output path를 BlobTrigger로 감지 → output blob을 Azure SQL Database table에 insert
#r "System.Data"
using System;
using System.Data.SqlClient;
public static void Run(string myBlob, TraceWriter log)
{
log.Info("C# Blob trigger function processed");
using (var connection = new SqlConnection(
"Server=tcp:<SQL_SERVER>.database.windows.net,1433;Initial Catalog=<DBNAME>;Persist Security Info=False;User ID=<LOGON_NAME>;Password=<LOGON_PWD>;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
))
{
connection.Open();
using (connection)
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = connection;
cmd.CommandText = "INSERT INTO batch(blob) VALUES(@blob)"; // insert some data on Azure SQL Database
cmd.Parameters.AddWithValue("@blob", myBlob);
cmd.ExecuteNonQuery();
}
log.Info("Connected successfully.");
}
}
In-Game Machine Learning 수행
게임 사용자 이탈율 예측 또는 아이템 추천 같은 서비스를 위해 Azure Machine Learning을 통해 예측 분석을 수행하는 전체 과정을 실행해 볼 수 있었습니다. 하지만, 게임 클라이언트 내에서 TCG의 핵심 요소인 카드 덱 추천 또는 AI의 동작 향상을 수행하기 위해서는 클라이언트 게임 내에서 예측 분석 과정 실행이 필요합니다.
이 과정은 머신러닝 및 예측 분석 솔루션 전문 개발사이자 마이크로소프트 파트너사인 디에스이트레이드의 이성희 박사님의 리드로 진행되었으며, Hackfest에서 다양한 Unsupervised Learning에 대한 학습을 진행 했고 특히, R에서 제공하는 여러 분석 알고리즘들 중 in-game AI 및 분석에서 활용 가능성이 높은 여러 머신러닝 및 딥러닝 기법과 알고리즘들을 실행해 볼 수 있었습니다. 이 과정의 목표는 C# 등의 언어에서 R코드를 호출하고, 기본적인 R이 제공하는 알고리즘들에 대한 테스트를 수행하는 것입니다.
이 과정을 수행하기 위해 Iris 데이터를 활용한 in-game 예측 모델 생성을 테스트 했고, in-game에서 Iris 데이터를 예측하는 C# 함수를 생성했습니다.
Iris 데이터 리포지토리 링크 : https://github.com/m-duel-project/ml-data
코드 리포지토리 링크 : https://github.com/m-duel-project/in-game-rl
###################################
##### Logistic Regression Analytics #####
###################################
#install.packages("glm2")
library(glm2)
# select 2 target category variables
adj_iris <- iris
adj_iris$Species <- as.character(adj_iris$Species)
adj_iris <- subset(adj_iris, Species %in% c('versicolor', 'virginica'))
adj_iris$Species <- as.factor(adj_iris$Species)
set.seed(10)
# train / test split
ind <- sample(2, nrow(adj_iris), replace=TRUE, prob=c(0.7,0.3))
trainData <- adj_iris[ind==1, ]
testData <- adj_iris[ind==2, ]
# modeling
formula <- Species ~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width
iris_logit <- glm(formula, data=trainData, family=binomial())
# save modeling result as .rds
flower_func_logit =
function(sl, sw, pl, pw){
testData <- data.frame(Sepal.Length=sl, Sepal.Width=sw,
Petal.Length=pl, Petal.Width=pw)
logitpred_a <- predict(iris_logit, newdata=testData, type="response")
logitpred <- ifelse(logitpred_a>=0.5, "virginica", "versicolor")
logitpred
}
save(iris_logit, flower_func_logit, file = "c:/Users/hcs64648/Desktop/iris_logit.RData")
이렇게 생성한 Logistic Regression Analytics 을 수행하는 RData 함수를 C# 등 코드에서 호출하는 것을 목표로 진행했습니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RDotNet;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
REngine.SetEnvironmentVariables();
REngine engine = REngine.GetInstance();
engine.Initialize();
engine.Evaluate("load('c:/Users/hcs64648/Desktop/iris_logit.RData')");
//var iris_logit = engine.GetSymbol("iris_logit");
//var flower_func_logit = engine.GetSymbol("flower_func_logit");
var rv = engine.Evaluate("flower_func_logit(1,1,1,1)"); // prediction here
Console.WriteLine(rv.ToString());
engine.Dispose();
}
}
}
코드를 이용해 이렇게 생성한 RData 함수를 RDotNet을 이용해 로드할 수 있었습니다.
R.NET 프로젝트 : https://www.nuget.org/packages/R.NET.Community/1.6.5
R 코드는 C#이나 C++ 또는 LUA 등에서 호출이 가능합니다. 이 방식을 이용하여 넥슨 마비노기팀은 머신러닝 관련 기술들에 대한 내재화를 진행하고 있으며, Hackfest를 통해 학습한 Machine Learning 관련 분석 기법과 실제 코드로 활용하는 방안들을 지속 발전시킬 예정입니다.
넥슨이 접하고 있는 문제는 확장 가능한 서비스 구현 및 데이터 과학적인 방법으로 더 많은 매출을 달성하고자 하는 대부분의 모바일 게임 업체들이 모두 접하고 있는 포괄적인 시나리오입니다.
Hackfest 멤버
- 노기태 - 넥슨코리아, 부장
- 김재석 - 넥슨코리아, 전문연구원
- 조정훈 - 넥슨코리아, 리드프로그래머
- 김주복 - 넥슨코리아, 테크니컬 디렉터
- 심예람 - 넥슨코리아, 프로그래머
- 황천섭 - 넥슨코리아, 프로그래머
- 이성희 - 디에스이트레이드, 상무
- 김환덕 - 디에스이트레이드, 이사
- 명대성 - 디에스이트레이드, 차장
- 박고은 - 디에스이트레이드, 대리
- 구예진 - 마이크로소프트, Audience Evangelism Manager
- 김대우 - 마이크로소프트, Sr. Technical Evangelist
- 류혜원 - 마이크로소프트, Audience Evangelism Manager
- 김은지 - 마이크로소프트, Technical Evangelist
Conclusion
Hackfest 기간 동안 넥슨은 실시간 예측 분석 모델과 배치 예측 분석 모델의 전체 솔루션 아키텍처를 구현하고 개발할 수 있었습니다. 원래 넥슨은 최초 AWS IaaS 기반 프론트엔드와 AWS Machine Learning을 이용한 백엔드를 Tensorflow나 Python의 Pandas 라이브러리 그리고 scikit-learn과 같은 Machine Learning 툴킷을 이용한 분석 플랫폼 구축을 계획하고 있었습니다.
그러나, 마이크로소프트와의 Hackfest를 통해 넥슨은 성공적으로 Azure Web App을 이용해 Restful API 프론트엔드를 높은 확장성과 관리 포인트가 줄어든 서비스로 구축 가능했고, 개발팀은 오직 코드에만 집중하여 빠르게 구현할 수 있었습니다.
특히 server-less 코드를 C#과 node.js로 구현했던 hackfest의 경험은 넥슨 개발자들에게 큰 환영을 받았습니다. 이벤트 트리거를 코드 한 줄 없이 구현하여, 백엔드 배치 분석 로직을 인프라 없이 코드로 Azure Function App에서 쉽게 구현할 수 있었기 때문입니다.
Quote: “인더스트리를 초월하여 Machine Learning은 모든 분야의 화두가 되고 있다. 모바일 게임과 온라인 게임 분야 역시 마찬가지이며, 넥슨의 데브캣 본부도 이에 대응하기 위해 여러 방면으로 준비 중이었다. 마이크로소프트의 Hackfest에서 Azure의 PaaS 중 Web App 등과 Azure Machine Learning을 십분 활용해 즉각적인 예측 분석 서비스를 3일간 집중해 완벽한 프로토타입을 개발해 볼 수 있었다. 특히, 게임 내의 AI에도 마이크로소프트의 파트너사인 디에스이트레이드의 기술 조언을 받아 Machine Learning 예측 모델 기술들을 적용해 더 스마트하고 다양한 액션이 가능한 AI 개발의 초석 기술들을 이번 hackfest를 통해 쌓을 수 있어서 큰 도움이 되었다. 넥슨-디에스이트레이드-마이크로소프트 개발자들의 열정적인 hackfest 참여가 모두에게 자극과 큰 동기부여가 되었고, 새로운 기술을 검토하고 협업하기 위한 최적의 방식이라 생각되어, 앞으로 팀내에서도 hackfest를 적극 도입할 것이다.” - 김동건 본부장(넥슨 데브캣 본부)
Hackfest를 통해
Azure App Service - Web App으로 고객은 OSS 언어와 프레임워크를 이용해 대규모의 Restful API 프론트엔드 서비스를 즉시 배포할 수 있었고, 코드에만 집중해 빠른 개발이 가능한 점에서도 매우 만족했습니다. 또한, 인프라스트럭처를 IaaS 기반 가상머신이 아닌, PaaS - App Service 환경에서 연속 배포 및 스테이징 구성을 직접 빌드 할 수 있었습니다.
예전에는, 대량 배치 분석을 Azure Machine Learning에서 수행하기 위해 직접 트리거를 만들어 처리 상태와 업데이트 상태를 DBMS에서 직접 체크해야 했으며, 이를 구현하기 위해 고객은 직접 스케쥴러 프로세스 데몬을 올리고, 상태 등을 지속적으로 polling하는 로직을 구현해야 했습니다.
그러나, Azure Function App은 모든 것을 바꾸었습니다. 타이머와 Blob 컨테이너 뿐만 아니라, 다양한 객체들에 대해 트리거 이벤트를 받을 수 있어서 손쉬운 개발이 가능해졌습니다. Azure Function App이 Blob 컨테이너를 트리거 할 수 있기 때문에, 넥슨은 대량 분석 데이터를 그들의 관리자 도구에서 Blob 컨테이너에 업로드만 하면 되었고, 배치 분석 작업이 종료되면 발생하는 추가 트리거 이벤트를 통해 업데이트 상태를 기존 모니터링 도구와 통합도 가능했습니다.
인프라스트럭처 구축이나 polling 프로세스 또는 데몬 프로세스 트리거 없이 바로 그들이 선호하는 언어로 코드를 작성하기만 하면 됩니다.
예측 분석은 다양한 산업군에 곧 안착할 것으로 예상됩니다. Azure App Service를 이용한 실시간 예측 분석 서비스와 Azure Function App을 활용한 배치 예측 분석 작업 및 스케쥴러 서비스는 훌륭한 솔루션이며, 향후 다양한 예측 분석 솔루션 시나리오에서 최고의 사례가 될 것입니다.