2. 내용
• 리뷰1 (오늘)
• 시작: 기능 목록 초안, 초기 컴포넌트 식별
• 0.1 개발: 핵심 기능 구현, CLI 구현
• 일부 설계 과정, 일부 구현 결과물
• 리뷰2 (다음)
• 0.2 개발: 웹 구현
• 일부 설계/구현 결과물
• 테스트 관련 생각할 거리: 단위 vs 통합, E2E 테스트
2
3. 이야기의 시작, 반복(중복)
[커맨드라인]
행렬 분해
[커맨드라인]
사용자용 데이터
생성
[커맨드라인]
아이템용 데이터
생성
[자바]
데이터 변환 후
저장
반복
여러 알고리즘에 대해 과정 유사
3
4. 자동화 욕구
4
[커맨드라인]
행렬 분해
[커맨드라인]
사용자용 데이터 생성
[커맨드라인]
아이템용 데이터 생성
[자바]
데이터 변환 후
저장
반복
자동화
[프로세스]
경로, 타입, 숫자 등 많은 설정
단계별 실패 확인
데이터 변환
과정, 실패 처리 등
프로그램
설정 정보
경로, 타입, 숫자 등
5. 기능 목록
버전 내용
0.1 1개 알고리즘에 대한 서비스 구현
콘솔에서 서비스 실행: 설정 파일 사용
5
0.2
웹 인터페이스
- 작업 설정 생성
- 작업 설정을 이용해서 백그라운드로 작업 실행
- 실행 내역 추적
0.3 다른 알고리즘 추가
7. 주요 모델
• 분석 실행(프로세스)
• 입력: 실행에 필요한 데이터
• 알고리즘 실행: 입력을 이용해서 특정 알고리즘 수행
• 결과 저장: 실행 결과를 원하는 형태로 보관
7
8. 0.1 버전 상위 수준 설계 초안
입력 데이터
경로 제공 결과 보관
8
주요 인터페이스
핵심 컴포넌트
알고리즘 실행
설정 파일을 이용해서
분석 기능 실행
9. 요구 상세
• Mahout을 이용해서 분석 1개 실행
• 로컬에 설치된 Mahout을 사용
• 기능 실행은 쉘스크립트를 이용해서 실행
• Mahout 실행 환경
• 로컬: 로컬 실행, 로컬 입력 파일
• 하둡: 하둡 클러스터(MR) 실행, HDFS 입력 파일
• 로컬에 설치된 하둡을 사용
• 결과 저장
• 로컬에 JSON 파일, (단순 테스트 목적) 콘솔 출력
9
10. 요구 상세
• Mahout을 이용해서 분석 1개 실행
• 로컬에 설치된 Mahout을 사용
• 기능 실행은 쉘스크립트를 이용해서 실행
10
• Mahout 실행 환경
JVM에서 외부 쉘을 실행
사용자가 쉘을 마음대로 변경하면
안 되므로, 런타임에 쉘을 동적 생성
• 로컬: 로컬 실행, 로컬 입력 파일
• 하둡: 하둡 클러스터(MR) 실행, HDFS 입력 파일
• 로컬에 설치된 하둡을 사용
• 결과 저장
• 로컬에 JSON 파일, (단순 테스트 목적) 콘솔 출력
Mahout 실행시, 하둡 클러스
터 설정 가능하도록
입력 파일로 로컬 경로와 HDFS
경로를 사용할 수 있도록 하둡 결과 파일을 읽어와 처리
12. 핵심 부분 설계 시작
12
class RecAnalyticsServiceSpec
extends Specification {
def “생성”() {
def RecAnalyticsService service =
new RecAnalyticsService()
}
}
public class RecAnalyticsService {
!}
* 공간의 제약으로 이름 등 일부 변경
class RecAnalyticsServiceSpec
extends Specification {
def service = new RecAnalyticService()
!
def “로그가 없을 경우”() {
when:
service.createRecommendation()
then:
thrown(NoDataException)
}
}
public class RecAnalyticsService {
public void createRecommendation() {
throw new NoDataException();
}
}
!
public class NoDataException extends RuntimeEx…{
}
13. 13
def service = new RecAnalyticService()
!
def “로그가 없을 경우”() {
when:
service.createRecommendation()
then:
thrown(NoDataException)
}
!
def “로그가 있다면”() {
setup:
def ActivityStorage activityStorage = Mock()
service.setActivityStorage(activityStorage)
when:
service.createRecomendation()
then: “로그가 있으면 NoDataException이 발생하지 않음”
1 * activityStorage.hasActivityData() >> true
notThrown(NoDataException)
}
!
public interface ActivityStorage {
boolean hasActivityData();
}
!
public class RecAnalyticsService {
private ActivityStorage activityStorage;
!
public void createRecommendation() {
if (!activityStorage.hasActivityData())
throw new NoDataException();
}
!
public void setActivityStorage(ActivityStorage …) {
this.activityStorage = …;
}
}
14. 14
def service = new RecAnalyticService()
def ActivityStorage mockActivityStorage = Mock()
!
def setup() {
service.setActivityStorage(mockActivityStorage)
}
!
def “로그가 없을 경우”() {
when:
service.createRecommendation()
then:
thrown(NoDataException)
}
!
def “로그가 있다면”() {
setup:
mockActivityStorage.hasActivityData() >> true
when:
service.createRecomendation()
then: “로그가 있으면 NoDataException이 발생하지 않음”
notThrown(NoDataException)
}
15. 15
def service = new RecAnalyticService()
def ActivityStorage mockActivityStorage = Mock()
def MahoutRunner mockMH = Mock()
!
def setup() {
service.setActivityStorage(mockActivityStorage)
service.setMahoutRunner(mockMH)
}
!
def “로그가 있다면”() {
setup:
mockActivityStorage.hasActivityData() >> true
!
when:
service.createRecomendation()
then: “로그가 있으면 NoDataException이 발생하지 않음”
notThrown(NoDataException)
!
when:
service.createRecomendation()
then: “Mahout 이용 분석 실행 실패하면, 익셉션 발생”
1 * mockMH.run() >> { throw new MREx() }
thrown(AnalyticServiceException)
!
}
public interface MahoutRunner {
public void run();
}
!
public class RecAnalyticsService {
private ActivityStorage activityStorage;
private MahoutRunner mahoutRunner;
!
public void createRecommendation() {
if (!activityStorage.hasActivityData())
throw new NoDataException();
try {
mahoutRunner.run();
} catch(MREx ex) {
throw new AnalyticServicException(ex);
}
}
… // setter 추가
}
!
public class MREx extends RuntimeException {}
public class AnalyticServiceException … {…}
16. 16
def service = new RecAnalyticService()
def ActivityStorage mockActivityStorage = Mock()
def MahoutRunner mockMH = Mock()
def ResultSaver mockSaver = Mock()
… // setup()
def “로그가 있다면”() {
setup:
mockActivityStorage.hasActivityData() >> true
def RecResult recResult = Mock()
def UserItemSource uiSource = Mock()
def SimItemSource siSource = Mock()
recResult.getUserItem() >> uiSource
recResult.getSimItem() >> recResult
…// 다른 when-then
when:
service.createRecomendation()
then: “Mahout 이용 분석 실행 실패하면, 익셉션 발생”
1 * mockMH.run(_) >> { throw new MREx() }
thrown(AnalyticServiceException)
!
when:
service.createRecomendation()
then: “Mahout 이용 분석 실행 성공하면, 결과 저장 시도”
mockMH.run(mockActivityStorage) >> recResult
1 * mockSaver.saveUserItem(uiSource)
1 * mockSaver.saveSimItem(siSource)
}
public interface ResultSaver {
void saveUserItem(UserItemSource source);
void saveSimItem(SimItemSource source);
}
public interface UserItemSource {}
public interface SimItemSource {}
!
public interface RecResult {
UserItemSource getUserItem();
SimItemSource getSimItem();
}
!
public interface MahoutRunner {
public void run(ActivityStorage activityStorage);
}
public class RecAnalyticsService {
…
private ResultSaver resultSaver;
!
public void createRecommendation() {
if (!activityStorage.hasActivityData())
throw new NoDataException();
RecResult result;
try {
result = mahoutRunner.run(activityStorage);
} catch(MREx ex) {
throw new AnalyticServicException(ex);
}
resultSaver.saveUserItem(result.getUserItem());
resultSaver.saveSimItem(result.getSimItem())
}
… // setter
23. 사용자 입장에서 메서드 도출
UserItemSource와 SimItemSource의 사용자
23
▼
“ResultSaver의 콘크리트 클래스”
public class ConsoleResultSaver implements … {
!
public void saveUserItem(UserItemSource source) {
List<UserItem> userItems = source.getUserItems();
for (UserItem ui: userItems) { … }
}
!
public void saveUserItem(UserItemSource source) {
Iterator<UserItem> userItems = source.getIterator();
while(userItems.hasNext()) {
UserItem ui = userItems.next();
…
}
}
1안
2안
대량 결과 데이터를 처리할 수 있어야 하기 때문에,
2안 선택
24. 2안으로 구현
쉘 실행 결과로 만들어진 파일로부터
데이터를 읽어오는 Iterator 구현 필요
24
public interface UserItemSource {
Iterator<UserItem> iterator();
}
25. 2안, Iterator 구현 예
25
private class FileSystemSimItemIterator
implements Iterator<SimItem> {
private final SequenceFile.Reader reader;
private final IntWritable key;
private final VectorWritable value ;
private SimItem nextItem;
!
public FileSystemSimItemIterator(
FileSystem fileSystem, Path simItemFile) {
reader = createSequenceFileReader(
fileSystem, similarItemFile, new Configuration());
key = new IntWritable();
value = new VectorWritable();
moveNext();
}
!
@Override
public boolean hasNext() {
return nextItem != null;
}
!
@Override
public SimilarItems next() {
checkNextItemExists();
SimItem result = nextItem;
moveNext();
return result;
}
private void checkNextItemsExists() {
if (nextItem == null) throw new NoSuchElementException();
}
!
private void moveNext() {
boolean isNextRead = readNextKeyValue();
if (!isNextRead) {
closeReader();
nextItem = null;
return;
}
createNextItems();
}
!
private boolean readNextKeyValue() {
try {
return reader.next(key, value);
} catch (…) {…}
}
!
private void createNextItem() {
List<ItemValue> allSimilarItems = new ArrayList<>();
for (Vector.Element ele : value.get().nonZeroes())
allSimilarItems.add(new ItemValue(ele.index(), ele.get()));
nextItem = new SimItem(key.get(), allSimilarItems);
}
private void closeReader() { … }
}
33. 역할?
33
1. 명령행 기본 옵션 검증
및 도움말 출력
2. 설정 파일 읽어와 분석
기능 실행
34. 역할 분리
34
1. 명령행 기본 옵션 검증
및 도움말 출력
2. 설정 파일 읽어와 분석
기능 실행
35. 설정 파일 읽기 구현(계속)
35
{
rec: {
fileSystem: {
type: "local"
},
activityDataStorage: {
activityDataPath: "/some/path/viewlog"
},
analyticService: {
type: "mahout"
},
mahoutRunner: {
type: "shellScript",
javaHome: "${env.JAVA_HOME}",
…
outputPathPrefix: "/some/path/result"
},
resultStorage: {
type: "console"
}
}
}
public class ConfigLoaderImpl implements ConfigLoader {
@Override
public RecConfig loadRecConfig(String configPath) {
File file = new File(configPath);
if (!file.exists())
throw new ConfigLoaderException();
!
RecConfig recConfig = new RecConfig();
!
ConfigValueHelper configValue = new ConfigValueHelper(file);
configValue.set("rec/fileSystem/type", recConfig::setFileSystemType);
configValue.set(“rec/fileSystem/namenode",
value -> recConfig.setHdfsNameNode(value));
configValue.setBoolean("rec/mahoutRunner/mahoutLocal",
recConfig::setMahoutRunnerMahoutLocal);
!
…
return configValue;
}
}
36. 36
public class ConfigValueHelper {
!
private final JsonNode rootNode;
!
public ConfigValueHelper(File configFile) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
try {
rootNode = mapper.readTree(configFile);
} catch (IOException e) { throw new ConfigLoaderException(e); }
}
!
public void set(String path, Consumer<String> consumer) { // setBoolean, setInt 등 유사하게 정의
getJsonNodeValueByPath(path, jsonNode -> json.asText()).ifPresent(consumer);
// getJsonNodeValueByPath(path, JsonNode::asBoolean).ifPresent(consumer); 예, setBoolean의 구현
}
!
private <T> Optional<T> getJsonNodeValueByPath(String path, Function<JsonNode, T> valueGetter) {
String[] fields = path.split("/");
JsonNode currentNode = rootNode;
for (String field : fields) {
JsonNode childNode = currentNode.get(field);
if (childNode == null) {
currentNode = null;
break;
}
currentNode = childNode;
}
return currentNode == null ? Optional.<T>empty() : Optional.of(valueGetter.apply(currentNode));
}
}
37. Factory
설정 정보의 값에 따라
모든 객체를 생성하고 조립
public class RecAnalyticServiceFactoryImpl implements RecAnalyticServiceFactory {
@Override
public RecommendationAnalyticService create(RecommendationConfig config) {
checkAnalyticServiceType(config);
MahoutRecAnalyticService service = new MahoutRecAnalyticService();
ServiceColleboratorFactory factory = ServiceColleboratorFactory.create(config);
service.setActivityDataStorage(factory.createActivityDataStorage());
service.setMahoutRunner(factory.createMahoutRunner());
service.setResultSaver(factory.createResultSaver());
return service;
}
37
38. 기타
• analytic-service
• 런타임에 쉘 생성: Velocity 이용해서 템플릿 처리
• CLI
• 설정 파일 로딩시 플레이스홀더 변환 처리
• ${env.환경변수}, ${자바시스템프로퍼티}
• e2e 테스트 (로컬, 하둡 클러스터 환경)
• 메이븐 이용 멀티 프로젝트 사용
• API 위주 서브 프로젝트: spi-*
• 주요 서브 프로젝트
• mahout-analytic-service, simple-data-storage
• cli
• 배포판 생성 서브 프로젝트: zip 파일로 생성
38