S/W 프로젝트를 하다보면 꼭 한번쯤은 기능점수(Function Point) 계산을 해 달라는 요구가 있습니다. 그런데... 기능점수 막상 계산하려고 보면, 낯선 용어들만 잔뜩 들어 있고, 도대체 뭘 해야 할 지도 모르겠고 어지러운 경험, 다들 한번씩은 있을 것입니다. 그래서 준비했습니다.
5분만에 배우는 기능점수 계산.
S/W 개발 규모/공수를 산정하는 정부의 권고안이 Man/Month 기준에서 기능점수(Function Point) 기준으로 바뀌어 대부분의 공공과제는 Function Point를 사용합니다.
결론만 말씀 드리죠. 이것만 계산하면 끝입니다.
(1) 간편 추정법: 기능 목록을 작성하고, 그 갯수 x 4.7을 하면 기능점수 추정치가 나옵니다. 끝. 쉽죠? (국내 공공프로젝트 분석 결과에 기반하면 그렇다는 뜻입니다.)
(2) 정식 계산법: 개발 기능목록을 먼저 확보 해야 합니다.
기능 하나하나에 대해서, 다음의 적용 여부를 판단하고, 적용되면 +1 해주면 됩니다.
해당 기능이 내부 데이터 참조가 있느냐? +1 증가: 내부논리파일(ILF)
해당 기능이 외부 데이터 연계가 있느냐? +1 증가: 외부연계파일(EIF)
해당 기능이 외부 입력/수정/삭제가 있느냐? 입력/수정/삭제 각각에 대해서 +1씩 증가: 외부입력(EI)
해당 기능이 외부 출력기능이 있느냐ㅑ? +1 증가: 외부출력(EO)
해당 기능에 보고서/도표 형태의 외부 조회가 있느냐? +1증가: 외부조회(EQ)
결과적으로 모든 카운트값을 모두 더한 값이 기능점수 총점임.
여기까지만 하면 끝입니다. 이 이후에 더 많은 내용들이 많이 있지만, 여기까지만 기억하시면 됩니다. 이후에 나오는 내용은 "보정계수"라고 해서, 규모에 따라서, 사용 언어에 따라서, 위험도에 따라서, 대상 분야에 따라서 0.5 ~ 2.0 범위의 숫자를 보정계수로 가중치 곱이 들어갑니다. (자세한 내역은 전혀 몰라도 상관없습니다.)
주의(1). 기능이라 함은, 구현하는 개발자 입장이 아니라, 요청자가 인식하는 기능을 말니다. 따라서 고객의 입장에서 식별되는 기능을 말합니다. (아마도 대부분 UI로 반영되고, 기능 목록으로도 표현됩니다.)
따라서 UI 화면 하나당 기능 하나씩이라고 볼 수도 있으니, 각각의 화면당, 그 한 화면을 구현하려면 내부참조/외부연계/외부입력,수정,삭제/외부출력/외부조회가 있는지 없는지를 0/1로 표기하기만 하면 끝입니다.
주의(2). 내부/외부 구분 기준은 구축하는 시스템 자체에서 쌓인 데이터를 보느냐 아니면 구축 범위 밖에 있는 자료 참조를 하느냐로 판단합니다. 사용자는 시스템 밖에 있습니다.
주의(3). 결국 ILF, EIF, EI, EO, EQ 카운트 값 5개를 알아내는 것이 핵심인 것이죠. 기능목록을 확보하고 여기까지만 오면 사실은 일은 다 끝난 것입니다.
그 다음부터는 다음 사이트를 들어가서 5개의 값을 입력하고 차례로 다음/다음 클릭해 가면서 상식적인 판단으로 5~6단계에 걸쳐서 적당한 보정계수를 선택하는 화면을 거치면, 최종적으로 개발 원가를 출력해 줍니다.
프로젝트를 수행하다 보면 고객의 요구사항을 최대한 수용해주고 싶지만 그럴 수 없는 상황을 만나곤 한다. 이번 프로젝트에서도 그러한 상황이 발생했는데 바로 빅데이터를 오라클에 저장하고 싶은 고객의 요청이 그러했다. 빅데이터 분석 프로젝트를 진행하면서 여러가지 상황을 접해 보았고 여러가지 기술들을 비교해보고 다양한 방법으로 빅데이터를 분석해 보았다. 그렇게 빅데이터 분석에 자신감이 붙었지만 위의 고객의 요구사항만큼은 시스템적으로 수용될지 확신이 서지 않았다. 이를 확인해 보기 위해서 과연 오라클이 얼마나 많은 데이터를 하루에 수용할 수 있는지 프로토 타입을 구성해서 테스트 해보기로 결정했다.
빅데이터 스토리지에 대해서 간략하게 알아보자
흔히 부르는 Big Data는 데이터가 크기가 큰 것만을 이야기 하는 것은 아니다. 데이터의 양(Volume), 형태(variety), 생성속도(velocity)도 Big Data영역에 포함된다. 이러한 Big Data를 다루는 작업은 무엇보다 저장된 데이터를 효율적으로 분석할 수 있어야 한다. 이러한 요건들을 만족시키지는 여러 가지 스토리지가 나와있는데 NoSql군에 속하는 저장소가 그러한 것들이다. NoSQL은 “Not Only SQL” 약어다. NoSQL은 데이터가 크기가 크거나 데이터가 정형화 되어 있지 않은 경우가 많아 SQL로 모든 데이터 조회가 쉽지 않으므로 자체적으로 데이터를 조회 및 분석 할 수 있는 매커니즘을 제공하는데 대표적인 것이 우리가 흔히 부르는 Map/Reduce라는 Functional한 함수 형태의 분석 매커니즘이다. 또한 NoSQL 군의 스토리지는 대부분 다루는 데이터양이 많다. 따라서 분산해서 데이터를 저장할 필요가 있으므로 분산모드를 제공하는 경우가 많다.
고객의 요구사항과 설계사이에서 갈등이 발생
정형화된 데이터가 단지 용량만 어느 정도 많이 차지할 때 RDB와 Big Data 스토리지 사이에 어느 것을 사용할지 갈등이 발생하게 된다. 하루에 정확히 얼마나 많은 데이터를 RDB가 수용될 지 모르는 상황에서 우리는 고객의 요구사항을 들어줄 수 있는지 없는지 확신이 서지 않았고 이를 해결하기 위해 과연 RDB가 얼마나 많이 데이터를 수용할 수 있는지 프로토타입을 만들어 보기로 했다. 아래는 우리가 겪은 문제점을 구체적으로 적어보았다.
고객의 목표는 약 14~15개 테이블에 약 600만~16억개씩의 Row를 매일 적재하는 것이 목표였고 하루에 적재되는 모든 테이블의 Row수를 합치면 대략 20억개의 Row가 되었다. 고객은 RDB에 데이터를 적재하기를 원했다. 정형화 되어 있는 데이터는 SQL로 얼마든지 데이터를 조회 해 볼 수 있을 것이라 생각했었던 것 같고 따라서 데이터를 24시간 안에 모든 데이터를 적재할 수 있으면 목표를 달성 할 수 있으리라 생각했던 것 같다.
Big Data를 RDB(오라클)로 적재 성능 테스트
약 14~16개의 테이블에 매일 총 20억개의 row를 쌓는 일은 리스크가 많았다. 따라서 개인적은 소견으로는 RDB보다 NoSQL군의 스토리지를 사용하기를 원했으나 뜻대로 할 수 있는 부분은 아니였다.
프로토타입 구성
PM과 협의하여 20억개의 Row를 어떻게 처리할지 기초 설계를 수립하였다. 기초안은 Hadoop의 HDFS에 데이터가 적재되고 Hadoop의 Map/Reduce를 이용해서 테이블에 적재될 데이터를 만들고 이를 스프링 배치에서 Spring Data, Mybatis를 연동하여 데이터를 읽고 저장하도록 애플리케이션을 구성하기로 했다.
위의 프로토타입은 인력부족으로 대부분 홀로 수행하게 되었는데 약 3주정도가 지나고 나서 위의 모습대로 프로토타입을 만들 수 있었다. 완성된 애플리케이션은 생각보다 굉장히 만족도가 높았다
스프링 배치와 하둡의 연동(Map/Reduce 및 HDFS 연동)은 Spring For Apache Hadoop과 Spring Data를 사용해서 처리하였는데 특별히 버그를 발견할 수 없었고 스프링과 Hadoop을 잘 연계시켜 주었다.
위의 애플리케이션 수행절차는 다음과 같다.
HDFS에 적재된 로그를 기반으로 Map/Reduce를 수행하여 14개 테이블에 적재될 수 있는 데이터를 HDFS에 생성한다.
HDFS 생성된 데이터를 스프링 배치에서 읽어 MyBatis 배치모드로 데이터를 적재한다.
RDB에 적재테스트 환경
DB는 오라클이고 대기업의 실제 운영DB(사양을 정확히 공개하지는 않겠다)를 기준으로 테스트를 수행.
적재에 필요한 기술셋으로는 Spring Data, Mybatis, Spring Batch를 적용
DB는 적재 성능 측정은 단일 쓰래드를 사용함
commit Interval은 10000을 적용
테스트 결과 정리
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 6,260,526 row를 7개의 컬럼을 가지는 테이블에 적재하는데 총 6분 27초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 6,260,526 row를 24개의 컬럼을 가지는 테이블에 적재하는데 총 8분 55초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 9,794,449 row를 5개의 컬럼을 가지는 테이블에 적재하는데 총 6분 1초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 6,286,093 row를 14개의 컬럼을 가지는 테이블에 적재하는데 총 5분 31초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 393,778 row를 6개의 컬럼을 가지는 테이블에 적재하는데 총 16초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 6,260,526 row를 17개의 컬럼을 가지는 테이블에 적재하는데 총 6분 5초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 6,260,513 row를 9개의 컬럼을 가지는 테이블에 적재하는데 총 8분 1초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 9,794,410 row를 8개의 컬럼을 가지는 테이블에 적재하는데 총 6분 33초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 9,794,575 row를 17개의 컬럼을 가지는 테이블에 적재하는데 총 8분 42초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 9,794,565 row를 8개의 컬럼을 가지는 테이블에 적재하는데 총 6분 52초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 25,580 row를 8개의 컬럼을 가지는 테이블에 적재하는데 총 1초 소요되었다.
적재 대상 테이블
적재대상 데이터
내용
결과
단일 쓰래드로 총 276,179,101 row를 9개의 컬럼을 가지는 테이블에 적재하는데 약 5~6 시간 소요된다.
적재대상 데이터
결과
컬럼이 31개인 16억개의 row는 앞서 테스트한 TSD_LINK의 최소 20배 이상의 데이터 크기를 가진다. 이는 단일 쓰래드로 테스트가 불가능하고 의미조차 없다. TSD가 약 6시간정도 소요되는걸 감안했을 때 16억개의 row는 처리가 불가능하다. 따라서 테스트는 생략했다.
테스트를 마치면서
프로토타입을 마친 후 PM과 고객이 함께 모여서 위의 결과를 놓고 토론를 했다. 하루 20억 Row를 오라클에 적재하는 일은 여러가지 문제점이 많다는 사실을 고객이 이해했고 다른 방법으로 위의 문제를 풀기로 결정을 내렸다. 위의 프로토타입을 수행함으로서 고객의 요구사항을 들어줄 수 없다는 결정을 이끌어 내게 되어서 개인적으로 좀 안타까웠다. 하지만 이렇게 테스트를 수행함으로써 고객과 원할한 소통을 할 수 있었고 이 내용을 공유함으로써 다른 사람이 나와 같은 고민을 하지 않아도 된다고 생각하니 조금은 마음의 위안이 된다.
프로토 타입 구성 방법을 아래에 간략히 정리한다.
필요한 라이브러리 설정
스프링과 Hadoop을 연계하기 위해서 Maven에 아래와 같은 dependency를 추가
<!-- spring for apache hadoop --> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-core</artifactId> <version>1.0.1</version> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <version>1.8.5</version> </dependency> <!-- spring data --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-hadoop</artifactId> <version>1.0.1.RELEASE</version> </dependency>
Hadoop에서 제공하는 HDFS 및 Map/Reduce를 사용하기 위해서 Hadoop 정보를 스프링에 설정
요즘 개인용 컴퓨터도 멀티코어를 장착하고 나올 정도로 멀티프로세서(Multiprocessor)가 흔해졌습니다. 덕분에 멀티프로세스(Multiprocess) 프로그래밍으로 개발한 데몬과 같은 것을 실행시켜 보면 여러 프로세서에 적당히 나뉘어 실행되는 것을 쉽게 확인 할 수 있습니다.
그런데 여기에 한가지 욕심을 더 내보자면 특정한 작업을 수행하는 프로세스를 특정한 프로세서에 할당하고 싶다는 생각이 드는 경우가 있습니다. 네트워크 데이터 및 DB 처리는 0번 CPU, 데이터 처리는 1번 CPU 식으로 말입니다.
기본적으로 OS에서 프로세스를 어떤 CPU에 할당하는지는 OS가 가진 자체적인 스케쥴링에 따르도록 되어 있습니다.
int main(int argc, char *argv[]) { unsigned int i = 0; pid_t pid;
if ( (pid = fork()) == 0 ) { for ( i = 0; i < UINT_MAX; i++) { } } else { int status; waitpid(pid, &status, 0); }
return EXIT_SUCCESS; }
위 소스는 1개의 자식 프로세스를 생성하고 더하기 연산을 반복적으로 수행하여 CPU를 100% 사용하게 만드는 예제입니다.
터미널을 하나 더 열어 top을 실행시킨 후 '1'번 키를 눌러 CPU 별로 사용량을 지켜볼 수 있게 준비를 합니다. 그리고, 소스를 컴파일 해서 여러번 실행시켜 보면 사용량이 100%에 달하는 CPU가 고정적이지 않고 변하는 것을 확인 할 수 있습니다. 물론 OS의 스케쥴링 정책에 영향을 받기 때문에 특정한 CPU에 고정적으로 할당되는 것처럼 보일 수도 있으나 많은 횟수를 실행시켜보면 변한다는 것을 확인 할 수 있습니다.
{ printf("Usage: %s -n -c < 선호CPU>\n\n", cmd); printf(" CPU 개수 : CPU 코어 개수\n"); printf(" 선호 CPU : CPU 코어 번호 (0 부터 시작)\n\n"); printf(" 예 : 쿼드코어 CPU에서 3번째 코어를 사용하는 경우\n"); printf(" $ %s -n 4 -c 2\n", cmd); }
int main(int argc, char *argv[]) { unsigned int i = 0; pid_t pid; int max_cpu = -1; int cpu = -1; int opt;
while ( (opt = getopt(argc, argv, "n:c:")) != -1 ) { switch ( opt ) { case 'c' : cpu = atoi(optarg); break; case 'n' : max_cpu = atoi(optarg); break; case '?' : default : print_help(argv[0]); exit(EXIT_FAILURE); break; } }
if ( max_cpu < 1 || cpu < 0 || cpu >= max_cpu ) { print_help(argv[0]); exit(EXIT_FAILURE); }
if ( sched_setaffinity(pid, sizeof(mask), &mask) ) { fprintf(stderr, "%d 번 CPU를 선호하도록 설정하지 못했습니다.\n", cpu); exit(EXIT_FAILURE); } else { printf("%d 번 CPU를 선호하도록 설정했습니다.\n", cpu); }
for ( i = 0; i < UINT_MAX; i++) { } } else { int status; waitpid(pid, &status, 0); }
return EXIT_SUCCESS; }
위 소스 코드는 sched.h에서 제공하는 sched_setaffinity 함수를 사용하여 특정한 CPU에서 프로세스가 실행되도록 한 것입니다.
sched_setaffinity 함수는 3개의 매개변수를 받는데 첫번째는 프로세스 ID(pid)입니다. pid 대신 0을 넘기면 자동으로 현재 동작중인 프로세스로 설정됩니다. 두번째는 cpusetsize 입니다. 보통은 sizeof(cpu_set_t)로 설정하면 됩니다. 세번째는 mask 포인터입니다. mask 포인터는 아래의 매크로 함수들을 사용해서 편리하게 설정 할 수 있습니다.