인덱스 바이너리

마지막 업데이트: 2022년 5월 15일 | 0개 댓글
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 카카오스토리 공유하기
그림 1. 이진 탐색 예시

인덱스 바이너리

[ 인덱스(Index)의 개념/종류/주의사항/활용,관리 ]

1. 인덱스(Index)란.

: 어떤 데이터가 HDD(하드디스크)의 어디에 있는지 위치 정보를 가진 주소록과 같은 개념.

-> (데이터 - 위치주소(ROWID)) 쌍으로 저장하고 관리됨

: 빠르게 쿼리 검색을 하오기 위함

데이터 위치 정보(주소) : ROWID -> 총 10Byte

-- 데이터 튜플의 rowid를 검색해 보기

select rowid, empno, ename from scott.emp where empno = 7521;

ROWID EMPNO ENAME

AAAE+3AAEAAAAFfAAC 7521 WARD

-- [ ROWID의 구조 ] : AAAE+3AAEAAAAFfAAC

-AAAE+3 : 데이터 오브젝트의 번호

-AAAAFf : 블럭 번호

-AAC : ROW(튜플) 번호

2. [ 인덱스의 생성 원리 ]

: 전체 테이블을 스캔(Table Full Scan) -> PGA내의 Sort Area에서 정렬(Sort) 공간 부족시 Temporary tablespace 이용해 정렬

-> 정렬한 데이터를 기반으로 HDD Block에 기록

-> 인덱스는 데이터가 ""정렬"" 되어 들어간다!!

3. [ 인덱스 구조와 작동 원리(B-TREE 인덱스 기준) ]

: 테이블(Table)과 인덱스(Index)의 비교

- 테이블은 컬럼이 여러개, 데이터가 "정렬되지 않고" 입력된 순서대로 들어간다.

- 인덱스(Index)는 컬럼이 "Key컬럼(사용자가 인덱스를 지정하라고 지정한 컬럼)"과 "ROWID컬럼" 두개로 이루어져 있다.

(오름차순, 내림차순으로 정렬 가능)

select * from emp where empno = 7902; -- 를 찾을 때

데이터 파일의 블록이 10만개가 있다고 할 때 sql문을 수행한다면,

1) 서버 프로세스가 구문파싱 과정을 마친 후 DB Buffer 캐시에 empno가 7902인 정보가 있는지를 먼저 확인한다.

2) 해당 정보가 캐시에 없다면 디스크 파일에서 7902정보를 가진 블럭을 찾아서 DB Buffer 캐시로 가져온 뒤 해당 정보를 사용자에게 보여줌

- Index가 없는 경우 -> 7902정보가 디스크 어떤 블럭에 있는지 모름으로 10만개 전부 DB Buffer 캐시로 복사한 뒤 Full Scan으로 찾게 됨.

- Index가 있는 경우 -> where절의 조건으로 준 컬럼이 Index의 Key로 생성되어 있는지 확인한 뒤, 인덱스에 먼저 가서 7902정보가

어떤 ROWID를 가지고 있는지 확인한 뒤 해당 ROWID에 있는 블럭만 찾아가서 db Buffer 캐시에 복사하게 됨.

4. [ 인덱스의 종류 ]

1) B-TREE 인덱스(Index)

: OLTP(Online Transaction Processing : 실시간 트랜잭션 처리)

-> 실시간으로 데이터 입력과 수정이 일어나는 환경에 많이 사용함.

2) BITMAP 인덱스(Index)

: OLAP(Online Analytical Processing : 온라인 분석 처리)

-> 대량의 데이터를 한꺼번에 입력한 뒤 주로 분석이나 통계 정보를 출력할 때 많이 사용함.

-> 데이터 값의 종류가 적고 동일한 데이터가 많을 경우에 많이 사용함

(1) [ B-Tree 인덱스 ]

B : binary, balance 의 약자

ROOT block(branch block에 대한 정보)

Branch Block(Left Block에 대한 정보)

Leaf Block(실제 데이터들의 주소)

(1-1) [ B-Tree 인덱스의 종류 ]

(A) 인덱스 바이너리 Unique Index

: 인덱스 안에 있는 컬럼Key값에 중복되는 데이터가 없다.(성능이 좋음)

-> 마치 Unique 제약조건과 유사하다. 따라서, Unique 제약조건 사용시에도 자동으로 UNIQUE INDEX가 생성된다.

또한, 기본키를 생성해도 오라클은 자동으로 UNIQUE INDEX를 생성 하게 되는데 이때 UNIQUE나 기본키 객체명과 동일하게 생성된다.

SQL > create unique index 인덱스명

on 테이블명(key로지정할컬럼명 1 ASC|DESC, 컬럼명2. );

ASC : 오름차순 정렬(기본값)

-- dept테이블과 같은 테이블 하나 생성

SQL > create table dept2 as select * from dept;

-- dname을 key로하는 unique index를 생성

SQL > create unique index idx_dept2_dname on dept2(dname);

SQL > insert into dept2 values(50,'개발부','서울');

-- 위의 추가한 튜플과 dname값이 일치하는 다른 튜플을 하나 추가할라하면 unique인덱스이기 때문에

SQL > insert into dept2 values(60,'개발부','인천');

-- 이미 들어가 있는 dname이기 때문!!

(B) Non Unique Index

: 중복되는 데이터가 들어가야 하는 경움(key로 지정한 필드의 중복된 값이 들어갈 수 있다.)

SQL > create index 인덱스명 on 테이블명(컬럼명1 ASC|DESC, 컬럼명2. );

SQL > create table dept3 as select * from dept;

-- dname을 Key로하는 Non-Unique index 생성

SQL > create index idx_dept3 on dept3(dname);

SQL > insert into dept3 values(50,'개발부','서울');

-- 중복되는 dname을 가진 튜플이 삽입이 가능하다.

SQL > insert into dept3 values(60,'개발부','인천');

(C) FBI 인덱스( Function Based Index ) : 함수기반 인덱스

: - 인덱스 바이너리 인덱스는 where절에 오는 조건 컬럼이나 조인에 쓰이는 컬럼으로 만들어야 한다.

- 인덱스를 사용하려면 where절의 조건을 절대로 다른 형태로 가공해서 사용하면 안된다.

where절의 조건이 sal + 100 > 200 이러한 조건일 경우

단순히 index컬럼을 sal로만 지정하게 되면 인덱스가 적용되지 않고 검색쿼리가 수행되게 됨

-> 따라서, 인덱스도 테이블명(sal+100)의 형태로 "함수기반 인덱스"로 사용해야 함

SQL > create index idx_dept_fbi on emp(sal+100);

-- 이때, emp테이블에는 sal+100 컬럼은 없지만 인덱스를 만들 때 저 연산을 수행해서 인덱스를 만듬

- 임시적인 해결책은 될 수 있어도 근본적인 처방은 아니다.

- sal + 100을 인덱스로 생성했는데 쿼리 조건이 변경되면 인덱스를 다시 생성해야 한다.

- FBI는 기존의 인덱스를 활용할 수 없다.(단점)

(D) Descending Index : 내림차순으로 인덱스를 생성한다.

: 큰 값을 많이 조회하는 SQL에 생성하는 것이 좋다.인덱스 바이너리

ex) 최근 날짜부터 조회, 회사 매출 조회

SQL > create index idx_emp_sal on emp(sal desc);

: 하나의 메뉴에 오름차순과 내림차순을 한번에 조회할 경우

-> 오름차순, 내림차순 두 개의 인덱스를 만들면 DML의 성능에 악영향을 미침

-> 이때는, 힌트를 사용한다.(아래나 위에서부터 읽도록 할 수 있다.)

(E) 결합 인덱스(Composite Index)

: 인덱스 생성시에 두개 이상의 컬럼을 합쳐서 인덱스를 생성하는 인덱스

-> 주로 where 절의 조건이 되는 컬럼이 2개 이상으로 and로 연결되는 경우 사용된다.

-> 잘못 생성하게 되면 성능에 영향을 미칠 수 있다.

(컬럼의 순서에 따라 효율에 차이가 있다.) -> 보통, 자주 사용하는 컬럼을 앞에 위치시키는 것이 좋다.

SQL > create index 인덱스명 on 테이블명(컬럼명1, 컬럼명2);

EX) (성별, 이름) 두개의 컬럼으로 인덱스를 생성하는 경우

SQL > create index idx_emp_composite on emp(gender,dname);

-- 생성된 인덱스는 정렬되어 데이터를 저장시키게 되는데

-- 이때, 인덱스 생성시 기술한 컬럼순으로 정렬이 된다.

-- 즉, gender(성별)을 기준으로 asc로 정렬한 뒤 같은 성별일 때

-- dname을 asc로 정렬해 인덱스를 생성하는 것

-- 생성된 인덱스가 어떤 모습으로 저장되어 있는지 검색해 보자.

EX) emp테이블의 empno와 deptno 두개의 컬럼을 조건으로 하는 인덱스 생성

SQL > create table emp3 as select * from emp;

SQL > create index idx_emp3_composite on emp3(deptno,empno);

-- 정렬된 상태로 저장되어있는 인덱스 모습을 보기

SQL > select * from emp3 where empno > 0 and deptno > '0';인덱스 바이너리

-- 이때, 검색 쿼리가 deptno가 20이면서 empno가 7902인 사원정보를 검색한다면

SQL > select * from emp3 where deptno = 20 and empno = 7902;

-- 인덱스 생성시 dept 컬럼을 앞에 인덱스 바이너리 기술했기 때문에 dept=20을 먼저 찾기위해

-- 4번 검사를 하고 이때 20이 나옴으로 empno 7369검사(1번) 다음 deptno 검사 1번

-- 다시 empno 검사(1번) 식으로해서 최종 찾는 데이터까지

-- 총 9번의 검사를 해 찾게된다.

-- 이런식으로 검사가 이루어지기 때문에 주의할 사항이 생기는데

(deptno, empno)로 생성할지 (empno, deptno)로 생성할지에 따라 검사 효율이 다르게 나타날 수 있다.

이때는 평균적인 검사 효율을 어느정도 계산을 해 바서 인덱스를 생성하는 것이 중요하다!!(신중히 생성하자)

(2-1) [ BITMAP 인덱스의 종류 ]

: 데이터 값의 종류가 적고 동일한 데이터가 많을 경우에 많이 사용된다.

Bitmap Index를 생성하려면 데이터의 변경량이 적어야 하고, 값의 종류도 적은 곳이 좋다.

일반적으로 OLAP환경에서 많이 생성하게 되는대

Bitmap Index는 어떤 데이터가 어디에 있다는 지도정보(MAP)를 Bit로 표기하게 된다.

데이터가 존재하는 곳은 1로 표시하고, 데이터가 없는 곳은 0으로 표기한다.

정보를 찾을 때 1인 값만 찾게 된다!

SQL > create bitmap index 인덱스명 on 테이블명(컬럼);

bitmap index를 생성하면 성별 컬럼 값의 종류대로 map이 생성된다.

남자 : 1 0 1 0 0 -> 남자데이터가 1,3번 튜플에 있다.

여자 : 0 1 0 1 1 -> 여자데이터가 2,4,5 튜플에 있다.

bitmap index사용하고 있는 상태에서 만약 컬럼 값이 새로 하나 더 생긴다면?

기존의 Bitmap Index를 전부 수정해야한다.

-> B-Tree Index는 관련 블럭만 벽여되지만 Bitmap Index는 모든 맵을 다 수정해야 한다는 문제점!

-> Bitmap Index는 블럭 단위로 lock을 설정해서 같은 블럭에 들어있는 다른 데이터도 수정작업이 안되는 경우가

5. [ 인덱스 주의사항 ]

- 인덱스를 사용하면 무조건 효율이 좋을까? NO

-> [ 인덱스는 DML에 취약 ]

1) 인덱스 바이너리 INSERT 작업의 경우

: " index split "현상이 발생할 수 있다.

- Index Split이란? : 인덱스의 Block들이 하나에서 두개로 나누어지는 현상

-> 인덱스는 데이터가 순서대로 정렬되어 저장되게 되는데, 기존 블럭에 여유 공간이 없는 상황에서

그 블럭에 새로운 데이터가 입력되어야 하는 경우

오라클은 기존 블럭의 내용 중 일부를 새 블럭에다가 기록한 다음 기존 블럭에 빈 공간을 만들어서

새로운 데이터를 추가하게 된다.

--> 따라서, 성능면에서 매우 불리하다.

a)Index Split은 새로운 블럭을 할당 받고 key를 옮기는 복잡한 작업을 수행

b)Index Split이 이루어지는 동안 해당 블럭에 대해 키 값이 변경되면 안되므로 DML이 블로킹된다.

enq:TX-index contention 대기 이벤트 발생(RAC-gc current split)

2) DELETE 작업의 경우

: 일반적인 테이블에서 데이터가 delete될 경우 해당 위치 데이터가 지워지고 그 공간을 사용 가능하다.

하지만, Index에서 데이터가 delete될 경우 -> "데이터는 지워지지 않고, 사용하지 않는다는 의미의 표시만 해두게 된다!"

--> 즉, 테이블에 2만건의 데이터가 있었는데 1만건을 삭제해도 Index에는 데이터가 2만건이 존재한다는 말이된다.

-> 인덱스를 사용해도 수행속도를 기대하기는 힘들다.

3) UPDATE 작업의 경우

: 인덱스에는 UPDATE란 작업이 존재하지 않기 때문에

기존의 데이터를 DELETE한 다음 새로운 값의 데이터를 INSERT하는 두번의 과정으로 작업이 발생하는데

따라서, 다른 DML작업보다 더 큰 부하를 주게 된다.

[ 최종적으로 인덱스 생성시 고려 해야할 사항 ]

1 일반적으로 테이블 전체 로우 수의 15%이하 의 데이터를 조회할 때 인덱스를 생성한다.

2 테이블 건수가 상당히 적다면 굳이 인덱스를 만들 필요가 없다. -> 테이블 건수가 적으면 인덱스를 경유하기보다 테이블 전체를 스캔하는 것이 더 빠르다.

3 인덱스 생성시 컬럼은 유일성 정도가 좋거나 범위가 넓은 값을 가진 컬럼을 지정하는 것이 좋다. (NULL값을 많이 갖는 컬럼은 피하는 것이 좋다.)

4 결합 인덱스 생성시에는 컬럼의 순서 가 중요하다.

-> 보통, 자주 사용하는 컬럼 을 앞에 지정한다.

5 테이블에 만들 수 있는 인덱스의 수는 제한이 없으나, 너무 많이 만들면 오히려 성능 부하가 발생한다.

why? -> 인덱스 생성을 해 놓으면 해당 테이블에 DML 작업 (insert, delete, update) 시 인덱스에도 수정작업이 동시에 발생하기 때문 에 과도하게 많은 인덱스를 생성해 놓으면

오히려 성능 부하가 걸릴 수 있다.

-> 일반적으로, 테이블 당 최대 5개 를 넘지 않는 것이 좋다.

6 데이터의 검색보다 수정, 삭제, 삽입 작업이 빈번한 테이블 에는 인덱스를 생성하지 않는 것이 좋다.

-> 인덱스는 DML작업에는 성능이 좋지 않기 때문에 검색을 위주로 하는 테이블에 생성하는 것이 좋다.(위에서 언급한 성능 이슈들이 발생할 수 있다.)

7 인덱스 생성시 무엇보다 가장 중요한 점은 SQL 쿼리가 인덱스를 잘 활용할 수 있게끔 짜여져야 한다는 것이다.(쿼리를 잘 짜서 만들자!)

인덱스 바이너리

이진 탐색은 쉬운듯 하면서도 어려운 알고리즘이다.

경계를 잘못 설정하면 while 문을 빠져나오지 못 하게 된다. 그렇다고 여러 테스트 케이스를 매번 고려하면서 짜기에는 시간이 너무 오래 걸린다. 어느 조건에 =을 넣어줘야 하며 left, right 갱신 시 mid로 할지 mid -1 혹은 mid + 1로 할지 고민하는 건 꽤나 골치 아픈 일이다.

그래서 나름의 템플릿이 있으면 빠르게 이진 탐색 코드를 짤 수 있겠다는 생각이 들어서 정리하는 중이다.

left = mid 는 사용하면 안 된다.

조건에 따라 될 수도 있겠지만 위 조건에서 left = mid를 사용하면 left, right 사이에 두 원소만 남았을 때 left가 움직이지 못 하므로 무한루프에 빠진다.

mid 계산 시 정수로 내림하기 때문에 주의해야 할 점이다.

left = mid는 안 되는데 right = mid는 왜 되는가?

mid 계산 특성 (정수 내림) 때문이다.

두 원소만 남은 경우 mid는 left랑 같고 조건식을 만족하면 left가 right로 이동하고 조건식을 만족하지 않으면 right가 mid(이 경우에는 left랑 같음)로 이동하므로 left == right여서 반복문을 빠져나온다.

while 조건에 equal 포함 형태

right = mid를 사용할 경우 left == right, arr[mid] == target 일 때 반복문을 못 빠져나오는 경우가 생긴다. 그래서 while 조건에서 등호가 포함되어 있을 경우 right = mid - 1을 사용한다.

right = mid -1 을 사용하면 [left, right] 사이에 답이 없는 경우가 생기는데 괜찮나?

간단하게 arr = [1, 3, 3, 5], target = 3 을 살펴보자.

첫 번째 루프에서 mid = index 1이 되고 arr[mid] == target이기 때문에 right = index 0이 된다.

left = right = index 0인 상태, 즉 arr[left : right+1] = [1]

target 값인 3이 포함되어 있지 않다.

하지만 걱정할 필요가 없다. right는 최대로 왼쪽으로 가봤자 가장 왼쪽에 있는 (target 이상의 값) 인덱스 - 1 이다.

이 명제가 중요하다. 그리고 while의 등호 때문에 left == right여도 한번 더 left 또는 right의 움직임이 있다는 점 또한 중요하다.

인덱스 바이너리

본 포스팅에서는 이진 탐색(Binary Search) 알고리즘에 대해 알아봅니다.

1. 이진 탐색이란?

이진 탐색탐색의 범위를 절반씩 좁혀가며 데이터를 탐색하는 알고리즘입니다. 이진탐색 알고리즘은 리스트 내 데이터가 어느 정도 정렬되어 있어야만 사용 가능하며 데이터가 무작위로 정렬되어 있다면 사용할 수 없습니다. 이진 탐색 알고리즘은 입력 데이터가 많거나(e.g., 1,000만 개 이상) 탐색 범위의 크기가 매우 넓을 때(e.g., 1,000억 이상) 효과적으로 문제를 해결할 수 있습니다.

2. 이진 탐색의 동작 과정

이진 탐색의 동작 과정에 대해 이해하기 위해서는 순차 탐색(Sequential Search)에 대해 먼저 이해할 필요가 있습니다. 순차 탐색에 대해서는 이전 포스팅 내용을 참고해 주시길 바랍니다 :)

[알고리즘] 순차 탐색(Sequential Search)에 대해 알아보자!(+Python 구현)

안녕하세요, 오늘은 순차 탐색(Sequential Search)에 대해 알아보겠습니다. 그럼 바로 시작하죠! 목차 1. 순차 탐색(이란? 2. 동작 과정 3. 구현(Python) 4. 시간 복잡도 1. 순차 탐색이란? 순차 탐색(Sequential

순차 탐색에 대해 이해하셨다면 본격적으로 이진 탐색의 동작 과정에 대해 알아보겠습니다. 이진 탐색은 특정 데이터를 찾기 위해 리스트 내 기준점으로서 시작 인덱스, 중간 인덱스, 마지막 인덱스를 사용합니다. 이진 탐색의 동작 순서는 아래와 같습니다.

1️⃣ 시작 인덱스와 마지막 인덱스 사이의 중간 값에서 소수점 이하를 버려 중간 인덱스를 정합니다.

2️⃣ 중간 인덱스에 해당하는 데이터와 현재 찾으려는 데이터가 같은지 비교합니다.

3️⃣ 중간 값이 더 크면 중간 인덱스 이후의 값은 확인하지 않고, 마지막 인덱스를 중간 인덱스로부터 한 칸 앞으로 옮깁니다.

반대로, 중간 값이 더 작다면 시작 인덱스를 중간 인덱스로부터 한 칸 뒤로 옮깁니다.

4️⃣ 중간 인덱스의 값과 찾으려는 값이 같아질 때까지 2️⃣ 과정과 3️⃣ 과정을 반복합니다.

이제 구체적인 예시를 통해 동작 과정을 알아보겠습니다. 아래 그림 1 과 같이 리스트 내 10개의 데이터 중에서 값이 4인 원소를 이진 탐색을 통해 찾아보겠습니다.

그림 1. 이진 탐색 예시

편의상 찾고자 하는 데이터(target data)파란색 으로 표현하고, 탐색할 필요가 없는 데이터회색 으로 채우겠습니다.

(1) 리스트 내 원소가 총 10개이므로 시작 인덱스는 [0]이고 마지막 인덱스는 [9]입니다. 중간 인덱스는 시작 인덱스와 마지막 인덱스의 중간 지점인 [4]가 됩니다. [4.5]에서 소수점 이하를 버리기 때문이죠.

(2) 중간 인덱스의 값인 8은 찾으려는 값 4보다 크기 때문에, 중간 인덱스 이후의 값은 이제 따로 탐색하지 않습니다. 이제 마지막 인덱스를 중간 인덱스 한 칸 앞으로 옮깁니다. 즉, 기존 중간 인덱스가 [4]였기 때문에 마지막 인덱스는 [3]이 됩니다. 중간 인덱스는 [0]과 [3] 중간 값인 [1]이 됩니다(실제 중간 값인 [1.5]에서 소수점 이하를 제거하였습니다).

(3) 중간 인덱스의 값인 2는 찾으려는 데이터 4보다 작기 때문에 2 이하의 데이터는 탐색할 필요가 없습니다. 따라서 시작 인덱스를 중간 인덱스의 한 칸 뒤인 [2]로 옮깁니다. 이제 새로운 중간 인덱스는 [2]와 [3] 사이의 [2]가 됩니다([2.5]에서 소수점 이하 절사). 중간 인덱스에 위치한 데이터 4는 찾으려는 데이터 4와 같기 때문에 이 시점에서 탐색을 종료합니다.

3. 이진 탐색의 시간 복잡도

앞서 살펴본 예시에서는 리스트 내 데이터가 총 10개였지만, 이진 탐색을 이용하면 총 3회의 탐색을 통해 원하는 데이터를 찾을 수 있었습니다. 이진 탐색은 탐색 횟수별로 확인하는 데이터의 개수가 절반씩 줄어들기 때문에 시간 복잡도가 O(log N) 입니다. 이처럼 확인하는 데이터의 개수가 절반씩 줄어드는 특징은 퀵 정렬과 동일합니다.

그렇다면 이진 탐색은 언제 사용하면 좋을까요?

탐색해야 하는 데이터의 개수나 값이 1,000만 단위 이상일 경우 이진 탐색과 같이 시간 복잡도가 O(log N) 수준으로 빠른 속도를 낼 수 있는 알고리즘을 활용하는 것이 좋습니다.

4. 이진 탐색의 구현(Python)

파이썬 기반에서 이진 탐색은 2가지 방법으로 구현이 가능합니다. 첫 번째는 재귀 함수를 사용하는 것이고, 다른 한 가지 방법은 반복문을 이용하는 것입니다. 각 방법별로 구현 방법을 알아보도록 하겠습니다.

인덱스 바이너리

검색은 파일에서 가장 빈번하게 발생하는 연산이다. 따라서, 파일구조를 설계할 때는 파일 검색 연산을 신중히 구현해야 한다. 파일을 검색하기 위한 방법에는 크게 순차 탐색(sequential search)과 이진 탐색(binary search)이 있다. 순차 탐색의 시간 복잡도는 $O(n)$이며, 이진 탐색 트리에서 이진 탐색을 수행할 때의 시간 복잡도는 $log_2(n)$이다.

일반적으로 순차 탐색보다 이진 탐색의 효율이 더 좋지만 이진 탐색은 데이터가 정렬되어 있어야 한다는 제약 조건이 존재한다. 또한, 가변 길이 레코드 파일에서 이진 탐색을 수행하기 위해서는 파일의 중간 지점을 찾는 방법에 대한 구현도 필요하다.

이진 탐색을 이용하기 위해서는 반드시 데이터를 정렬해야한다. 파일의 경우에는 일반적으로 메모리에서 다루어지는 자료보다 데이터의 양이 많기 때문에 효율적인 정렬 알고리즘이 매우 인덱스 바이너리 중요하다.

파일은 데이터의 양이 매우 많기 때문에 메모리에 데이터를 모두 적재할 수 없는 경우가 존재한다. 이러한 경우에는 external sorting을 수행해야 하는데, external sorting은 전체 데이터 중에서 일부만 메모리에 적재하여 정렬하고 정렬이 끝나면 디스크에 접근하여 다시 데이터를 메모리에 적재하여 정렬을 수행하기 때문에 메모리에서 정렬을 수행하는 internal sorting에 비해 속도가 매우 떨어진다. 파일 정렬 시, 정렬을 위해 필요한 데이터의 양을 최소화하여 internal sorting을 수행하는 방법으로 key sorting이 있다.

Key sorting의 기본 개념은 데이터 파일 자체를 정렬하는 것이 아니라, 데이터 파일에 대해 키-상대 레코드 번호(RRN)으로 구성된 KEYNODE array를 정렬하는 것이다. 일반적으로 데이터 파일에 대한 KEYNODE array는 모든 데이터가 충분히 메모리에 적재될 수 있기 때문에 external sorting 대신 internal sorting을 이용할 수 있다. Key sorting의 구현은 아래와 같다.

위의 코드에서 알 수 있듯이 key sorting을 위해서는 입력 파일을 두 번 읽어야 한다. 하나의 입력 파일을 읽기 위해 디스크에 두 번 접근하는 것은 바람직하지 않다. 또한, 정렬된 KENODE를 참조하여 파일을 정렬된 형태로 다시 쓸 때 입력 파일을 임의 접근(random access) 하기 때문에 정렬 알고리즘의 성능이 떨어지는 단점이 있다.

Key sorting의 이러한 단점을 극복하기 위한 방법으로는 인덱스 파일(index file)을 이용하는 방법이 있다. 인덱스 파일을 이용하여 key sorting의 단점을 극복하는 방법은 매우 단순하다. 바로 인덱스 파일만 정렬하고 원래 데이터 파일은 정렬되지 않은 그대로 놔두는 것이다.

인덱스 바이너리

이진 탐색은 탐색 알고리즘 중 하나로, 이분 탐색이라고도 한다.

이진탐색 인덱스 바이너리 혹은 이분 탐색은 말 그대로 탐색하고자 하는 데이터를 반으로 나누어 탐색해간다는 의미이다.

위와 같은 데이터에서 17이라는 데이터를 찾는다고 가정하자.

for 혹은 while과 같은 반복문을 통해서도 빠른 시간 내에 값을 찾을 수 있겠지만, 이진 탐색을 적용해 찾는 과정을 살펴보자.

이진 탐색을 적용하기 위해 array 데이터를 정렬해주었다.

이진 탐색은 반드시 정렬된 데이터 에서만 사용할 수 있다.

그리고 우리는 이 array에서 17이라는 데이터를 찾고자 한다고 가정하자.

먼저 위에서 언급한 대로 반복문을 통해 0번 인덱스부터 탐색을 한다면 9번만에 찾을 수 있다.

지금은 array 데이터의 크기가 작아 충분히 작은 시간이지만, 데이터가 만약 100만개, 1억개라면 최악의 경우 100만번, 1억번의 연산을 수행해야 한다.

이진 탐색을 적용하기 위해서는 위에서 언급한 듯이 데이터를 반으로 나누어야 한다. 정렬된 데이터의 중앙 값을 기준으로 데이터를 나눈다.

데이터의 중앙 값을 찾기 위해 (0 + 9)//2를 통해 중앙 인덱스 값을 찾아주었다.

중앙 값의 인덱스를 찾을 때에는 시작점과 끝점의 인덱스의 중간 인덱스를 구하면 된다. (정수부만 이용한다.)

우리가 찾고자 하는 값은 17이므로 파란색으로 표시한 중앙 값과 17을 비교하면, 17이 중앙 값보다 더 큰 것을 알 수 있다.

따라서 중앙 값과 그보다 작은 값들은 이제 탐색에서 제외하고 다시 탐색을 시작한다.

회색으로 표시한 부분은 이전 탐색에서 더이상 탐색하지 않기로 한 부분이다.

새로 탐색하기로 한 부분에서 다시 같은 동작을 반복하므로, 17과 새로운 중앙 값인 15를 비교한다.

이번에도 17이 중앙 값인 15보다 크므로, 중앙 값 15와 그보다 작은 값들은 모두 탐색에서 제외한다.

이전 탐색에서 탐색하지 않기로 한 부분을 다시 회색으로 표시했다.

남은 데이터에서 새 시작점과 끝점을 표시하고 중앙 값을 구한다.

중앙 값과 찾고자 하는 값을 비교해보니, 일치함을 알 수 있고 이로서 탐색이 끝났다.

정렬된 데이터 기준으로, 반복문을 사용했을 때에는 총 9번의 반복을 진행해야 했지만 이진 탐색을 이용해 3번만에 끝낼 수 있었다.

데이터가 고작 10개밖에 되지 않은 상황에서도 유의미한 탐색 횟수의 감소를 얻었고 데이터가 커질수록 이 효과는 증가할 것이다.

정렬된 데이터 기준으로, 반복문을 사용하면 O(N)의 시간 복잡도를 가지지만 이진 탐색을 사용하면 O(logN)의 시간 복잡도를 통해 값을 구할 수 있다.


0 개 댓글

답장을 남겨주세요