본문 바로가기

Dev

속도 비교 : For문 비교하여 값 얻어내기 vs 디비 접속하여 값 얻어내기

728x90
작업 중에 정말 궁금한 것이 생겼다.

필요한 데이터를 매번 디비에 가서 가져오는 것이 빠를까,
아니면 모든 데이터를 다 가져온 뒤, 메소드 안에서 for문으로 돌려서 비교하여 필요한 값을 가져오는 것이 ㄷ ㅓ 빠를까.

#. 테스트 소스 : 테스트라 빠른 결과값을 얻기 위해 컨트롤러에서 모든 걸 구현.
1. 언어 : JAVA 
2. Framework : Spring 3.0.5
3. 기능 : Depth를 가지는 트리형 카테고리 리스트를 뿌릴 때, 최상단 부모까지 정보를 모두 표시 해 준다.
예 ) Entertainment > Game > Puzzle
4. 문제 : 최말단 카테고리 정보를 가져와 상단의 부모 카테고리의 이름을 얻어 올 때, 전체 리스트 정보를 가지고 하나하나 디비에 접속해서 값을 얻어 오는 것이 빠른가, 최말단 카테고리 정보를 비교할 전체 리스트를 디비에 한번만 접속해서 가져온 후 계속 For 문으로 비교하여 값을 얻는 것이 빠른가.
(※ 참고 사항 : 전체 Row는 50개 정도.)
5. 사용 DB 엔진 : MySQL

 
public class TestController {
@Autowired
private TestService testService;

@Autowired
private CategoryCommonService categoryCommonService;
 
@Autowired
private StatisticsMapper statisticsMapper;

/* URL 주소 http://localhot/test로 요청을 보내면 결과값을 확인 할 수 있다. */
@RequestMapping("")
public String test(HttpServletRequest request)  throws Exception{

HttpSession session = request.getSession();

List<StatisticsModel> statisticsList = new ArrayList<StatisticsModel>();

/* 쿼리 날릴 때 필요한 파라미터 설정 */
Map<String, Object> map = new HashMap<String, Object>();
map.put("mppLevel",AppStateUtil.USER_MAPPING);
map.put("avrLevel",AppStateUtil.USER_APPROVAL);
map.put("memId",(Integer)session.getAttribute("memberId"));
map.put("isAdmin", AppStateUtil.SUPER_ADMIN.equals((String)session.getAttribute("memberLevel")));
map.put("appCategoryState", AppStateUtil.APPSTATE_COMPLETE_APPROVAL);
map.put("type", "predefined");
map.put("appMappingState", AppStateUtil.APPSTATE_WAITING_APPROVAL);
map.put("appApprovalState", AppStateUtil.APPSTATE_COMPLETE_APPROVAL);

/* 최말단 카테고리 리스트 가져오기 */
List<StatisticsModel> tempNewCategoryList = new ArrayList<StatisticsModel>();
int maxLevel = categoryCommonService.getMaxLevel();      //전체 카테고리에서 가장 큰  Depth값.
for(int t=1; t<=maxLevel; t++){
map.put("catLevel", t);     
List<StatisticsModel> tempList = statisticsMapper.getMemberNewCategory(map);
if(tempList != null)
tempNewCategoryList.addAll(tempList);
}

/* 디비에 접속 : 시작 ***************************************************** */ 

 // 디비 접속 반복으로 카테고리 정보 얻는 과정 시작 시간
long sTime = System.currentTimeMillis();    

Iterator<StatisticsModel> iNew = tempNewCategoryList.iterator();
while(iNew.hasNext()){
StatisticsModel statisticsInfo = (StatisticsModel)iNew.next();
        statisticsInfo.setCatName(categoryCommonService.makeNewCategoryPathName(statisticsInfo.getCatId()));
statisticsList.add(statisticsInfo);
}
         
System.out.println("1. ==============================  BEFOR : " + sTime ); 
       
long eTime = System.currentTimeMillis();
System.out.println("1. ============================== AFTER : " + eTime);
System.out.println("1. HOW MANY : " +( eTime - sTime) );
/* 디비에 접속 : 끝 ***************************************************** */ 
 
/* for문으로 비교 : 시작 ****************************************************** */ 

sTime = System.currentTimeMillis();        // for문을 통해 카테고리 경로 얻는 과정 시작 시간
 
maxLevel = categoryCommonService.getMaxLevel();
for(int t=1; t<=maxLevel; t++){
map.put("catLevel", t);      
List<StatisticsModel> tempList = statisticsMapper.getMemberNewCategory(map);
if(tempList != null)
tempNewCategoryList.addAll(tempList);
}
 

Map<String, List<NewCategoryModel>> allNewCategoryMap = categoryCommonService.getAllCategory(null);

// 전체 카테고리 가져와서, 카테고리 이름을 경로까지 표시 해 줄 때 비교 기준으로 쓴다.   
List<NewCategoryModel> allNewCategoryList = (List<NewCategoryModel>)allNewCategoryMap.get("newCategoryList");
 
iNew = tempNewCategoryList.iterator();
while(iNew.hasNext()){
StatisticsModel statisticsInfo = (StatisticsModel)iNew.next();
String catId = statisticsInfo.getCatId();
String categoryPathName = "";
String parentName = "";
String parentId = "";
int categoryLevel = 1;
int t=0;

//각 말단 카테고리마다 해당 카테고리의 이름과 부모 카테고리 아이디를 얻는다.
for(t=0; t<allNewCategoryList.size(); t++) {
NewCategoryModel s = (NewCategoryModel)allNewCategoryList.get(t);
if( catId.equals(s.getCatId())){
parentId = s.getParentCatId();
categoryLevel = s.getCatLevel();
categoryPathName = s.getCatName();
}
}

//얻어온 부모 카테고리 아디디를 이용하여 반복문을 통해 카테고리 경로를 얻는다
while(categoryLevel > 1) { //자기자신은 이미 Path 에 추가 했으므로 1은 제외됨
for(t=0; t<allNewCategoryList.size(); t++) {
NewCategoryModel s = (NewCategoryModel)allNewCategoryList.get(t);
if(parentId != null && parentId.equals(s.getCatId())){
parentName = s.getCatName();
parentId = s.getParentCatId(); //다음 부모 아이디 설정
}
}
categoryPathName = parentName+ " > " + categoryPathName;
categoryLevel--;
}
         
        statisticsInfo.setCatName(categoryPathName);
        statisticsList.add(statisticsInfo);
        }
         System.out.println("2. ==============================  BEFOR : " + sTime ); 
         eTime = System.currentTimeMillis();     // for문을 통해 카테고리 경로 얻는 과정 끝나는 시간
         System.out.println("2. ============================== AFTER : " + eTime);
 System.out.println("2. HOW MANY : " +( eTime - sTime) );

return "test/test";
}

#. 결과

1. ==============================  BEFOR : 1312423551746
1. ============================== AFTER : 1312423552899
1. HOW MANY : 1153

2. ==============================  BEFOR : 1312423552900
2. ============================== AFTER : 1312423553099
2. HOW MANY : 199
 
#. 결론
디비에 여러번 접속하는 것보다 전체 데이터를 한번에 가져와서 for문으로 비교하는 것이 훨씬 빨랐다.

#. 파생되는 의문
1. 현재는 50건의 데이터만 가지고 테스트 하였기 때문에 문제가 안 되는 것 같지만, 수백만건의 데이터라고 가정한다면 그 많은 데이터를 메모리에 올려서 비교하는 것이 괜찮을까?
2.  다른 데이터베이스 (Oracle , MSSQL server 등)를 사용해도 같은 결과가 나올까?

================ KSUG에서 얻은 배움 ====================================================

by 신승한 님
 

#. 파생되는 의문 
1. 현재는 50건의 데이터만 가지고 테스트 하였기 때문에 문제가 안 되는 것 같지만, 수백만건의 데이터라고 가정한다면 그 많 
은 데이터를 메모리에 올려서 비교하는 것이 괜찮을까? 

전부 메모리에 올리는건 불가능하기때문에 
코드성 데이터 / 메뉴정보 처럼 자주 사용되고 양이 적은것은 전체 캐싱을 하거나, 
자주 접근되는 데이터만 메모리에 올려놓고  활용할 수 있도록 캐싱엔진을 사용합니다. 

2.  다른 데이터베이스 (Oracle , MSSQL server 등)를 사용해도 같은 결과가 나올까? 

네 


--------------------------------------------------------------------------------------------------------------------

by Sungchul Park 님

메모리야 요즘 싸니까 데이터 수백만건을 올릴 수 있다고 해도 왜 메모리에서 
는 1차월 배열에 담고 for 문으로 탐색하시나요? 알고리즘과 자료구조 시간에 
배우는 지식을 활용할 좋은 기회인데요. ^^ 

그리고 직접 자료 구조 잡고 탐색 알고리즘 구현하기 어렵다면 hsql이나 
javadb 같은 내장형 DB를 메모리만 사용하도록 설정
하고 쓰는 방식도 생각해 
볼만 합니다.