FireBird Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
파이어버드 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
IBPhoenix
FireBird Main site
볼랜드포럼 광고 모집

FireBird 팁&트릭
[85] Firebird에서 고속으로 대용량 INSERT를 하려면
박지훈.임프 [cbuilder] 17631 읽음    2012-08-23 06:48
Firebird는 오픈소스 기반의 완벽한 기능을 갖춘 RDBMS로서, 처음부터 오픈소스로 시작된 프로젝트는 아니고 2000년대 초에 볼랜드에서 상용 데이터베이스인 InterBase 6.0 버전을 오픈소스화 했을 때 파생된 프로젝트입니다. 당연히 ANSI SQL의 모든 기능을 포함한 인터베이스의 모든 기능을 갖추고 있으며, 지속적으로 버전이 올라가면서 기능과 성능이 크게 발전하고 있습니다.

현재 많이 사용되고 있는 파이어버드의 버전은 2.1.x 버전과 2.5.x 버전입니다. 과거 인터베이스가 윈도우에서 SMP(멀티코어 CPU)를 제대로 지원하지 않았던 관계로 파이어버드에서도 SMP가 지원되지 않았었습니다만, 2.5 버전부터는 지원되기 시작했습니다. 대신 2.1.x 버전은 오랫동안 유지보수가 되면서 훨씬 더 안정적이라는 장점이 있지요.

높은 성능과 다양한 기능들 외에도, 파이어버드에는 다른 데이터베이스에서는 보기 힘든 두 가지 눈에 띄는 장점들이 있습니다. 첫번째는 완전한 무료 데이터베이스라는 것입니다. GPL 기반이라 상업적 목적에서는 유료로 구입해야 하는 MySQL과 달리, 파이어버드는 MPL의 변형 라이선스로서 상업적인 목적에도 무료로 사용할 수 있습니다.

또 한가지 장점은, 대단히 가볍다는 것입니다. 배포 크기와 운영시 사용 메모리 양쪽 모두 그렇습니다. 파이어버드의 배포 크기는 수 MB에 불과하고, 메모리 사용도 수십 MB에 불과합니다. GB 단위의 메모리를 예사로 먹어대는 오라클이나 MS SQL에 익숙한 분이라면, 이런 정도로 어떻게 완벽한 RDBMS 동작을 그것도 빠르게 해내는지 이해가 잘 안될 정도죠. 바로 이 볼랜드포럼도 파이어버드와 델파이로 개발되어 10년 가까이 운영중인데, 언제나 30MB 이하의 메모리를 쓰고 있습니다. (덕분에 웹서버와 데이터베이스를 한 머신에서 돌리면서도  메모리 겨우 1GB로도 메모리 공간 널널하게 남은 상태로 쌩쌩하게 돌아갑니다)

한가지 덧붙이자면, 파이어버드는 인터베이스 소스로부터 분기한 프로젝트이기 때문에 인터베이스 6.x 버전 이하의 모든 기능이 호환됩니다. 따라서 델파이와 C++빌더에서 인터베이스를 지원하는 기능들을 이용해서 사용이 가능한 장점이 있습니다. 또한 델파이/C++빌더의 최근 버전에서는 파이어버드를 따로 정식으로 지원하게도 되었죠. 이런 이유로 많은 델파이/C++빌더 기반 프로그램에서 파이어버드를 사용하고 있습니다.

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

Firebird는 고성능에다 표준 SQL을 비롯한 대단히 다양한 기능들을 갖춘 데이터베이스이기는 하지만, 당연히 경쟁 데이터베이스 제품들의 모든 기능을 모두 다 갖추고 있다는 의미는 아닙니다. (물론 오라클이나 MS SQL이라고 해서 파이어버드가 갖춘 뛰어난 기능들을 다 가진 것도 아니지요) 그중 아쉬운 것 하나가, 대용량 insert, 속칭 bulk insert 기능입니다.
물론, INSERT 속도를 높이기 위해선 각각의 INSERT마다 트랜잭션을 커밋하지 않고 많은 양을 인서트한 후 한꺼번에 커밋을 하면 전반적인 인서트 속도가 상당히 빨라집니다. 이렇게 긴 트랜잭션으로 개별 인서트 문들을 날리게 되면 대략 2.5~3배까지 인서트 속도가 빨라지게 됩니다. (한 트랜잭션에서 속도가 더 이상 의미있게 빨라지지 않는 누적 insert 문의 갯수는 대략 500~1000개 정도였습니다)

하지만 이렇게 긴 트랜잭션으로 인서트 속도를 높인다고 해도 충분하지 않은 경우가 적지 않게 있습니다. 수백만 건 이상을 한꺼번에, 그리고 어떻게 쥐어짜든 최대한 짧은 시간에 인서트해야 하는 경우라면 파이어버드의 인서트 속도에 만족할 수가 없지요.

제가 테스트를 했었던 한 케이스에서, 대략 160만건 정도의 데이터를 파이어버드에 인서트할 때 소요되었던 시간은 11분이었습니다. 환경은, 트랜잭션 하나당 120 레코드 인서트 단위로 돌렸구요. 사정상 1000 레코드보단 적게 돌렸지만 성능 면에서 큰 차이가 나진 않습니다. (참고로 조합키로 된 PK가 걸려 있고, 컬럼 수는 16개, 레코드 크기 56바이트입니다)

160만건 인서트에 11분이라는 시간은, 뭐 빠르다면 빠르고 느리다면 느릴 수 있지만, 어쨌든 무조건 최대한 빠른 속도를 내야 하는 대량 인서트 작업에서도 항상 충분한 속도라고 말할 수는 없겠지요.

또한 8개 코어 CPU(하이퍼쓰레딩으로 x2 포함)인 제 개발 서버에서 인서트 작업 동안 내내 파이어버드가 CPU 코어 하나를 70~80% 정도 점유하고 있어서 하드웨어 부하도 상당히 큰 편이었습니다.

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

다행히도, Firebird는 자체적으로 벌크 인서트 전용의 기능을 제공하지는 않지만, 사실상 벌크 인서트의 목적으로 응용할 수 있는 파이어버드만의 특수한 기능이 있습니다. 그것은 외부 파일 테이블(External file table) 기능입니다.

외부 파일 테이블은, 테이블을 생성할 때 단순히 CREATE TABLE 테이블이름 ... 이렇게 하는 대신, 그 뒤에 EXTERNAL 파일이름 이렇게 추가함으로써, 파이어버드 데이터베이스의 fdb 파일과 별도의 외부 파일로 테이블을 만들 수 있는 기능입니다. 즉 아래와 같은 SQL문으로 테이블을 만듭니다.
CREATE TABLE 테이블이름 EXTERNAL '외부파일전체경로' (
  필드1
  필드2
...
  필드n
);

이렇게 외부 파일 테이블을 지정하는 경우, EXTERNAL 뒤에서 지정한 경로의 파일이 존재하지 않으면 새 파일을 만들고, 기존의 파일이 있으면 그 파일을 테이블로 사용하게 됩니다. 즉, 대량으로 INSERT할 레코드 데이터들을 파이어버드 외부 테이블의 파일 포맷대로 기록해서 파일을 먼저 만든 후, 다음으로 위의 CREATE TABLE 문으로 외부 파일 테이블로 지정할 수 있습니다.

하지만 파이어버드의 외부 파일 테이블은 일반 테이블처럼 자유롭게 모든 기능을 사용할 수 없습니다. 특히 인덱스나 키 등을 잡을 수 없고, 트랜잭션도 적용되지 않는 등 여러 제한점이 많습니다. 그래서 이 외부 테이블을 그대로 사용하기보단 원하는 실제 테이블로 데이터를 옮길 필요가 생기지요. 물리적으로는 외부 테이블과 실제 사용할 테이블이 다른 파일로 떨어져 있더라도 Firebird 내부의 논리적으로는 동일 데이터베이스에 속해있기 때문에, 단 하나의 SQL문, INSERT...SELECT 문을 사용하여 아주 초고속으로 데이터를 옮길 수 있습니다.

그럼, 이렇게 외부 텍스트 테이블을 이용한 벌크 인서트를 했을 때, 성능은 얼마나 차이가 날까요? 앞서 11분이 걸렸던 테스트 케이스를 외부 파일을 이용한 벌크 인서트를 했을 때, 외부 파일을 만드는 시간과 insert...select를 실행하는 시간을 합쳐서 모두 65초 정도, 즉 대략 1분 5초 정도가 걸렸습니다. (파일 생성 과정에 43초, INSERT...SELECT 실행에 22초 걸렸습니다)

즉, 외부 파일을 이용한 벌크 인서트 방법에서는, 단순 인서트를 긴 트랜잭션으로 빠르게 한 정도보다는 무려 10배 이상 빠른 인서트 속도를 보여줍니다. (참고로, 이 테스트 결과는 물론 외부 파일을 만드는 과정에서 파일 쓰기 작업이 최대한 빨라지도록 코드를 만든 결과입니다)

또 CPU 점유율에 있어서도, INSERT...SELECT가 일어나는 시점의 초기에 몇초 정도만 코어 하나를 100%를 먹고 곧 거의 0% 수준으로 떨어집니다. CPU 타임 점유율 면에서도 훨씬 유리하죠. 파일을 써넣는 과정에서는 디스크를 많이 사용하지만, 동일 크기의 파일을 탐색기에서 복사하는 정도의 부하에 불과합니다.

다음은, 제가 이 기능을 이용해서 벌크 인서트를 해보던 과정에서 삽질을 통해 알아냈던 중요한 주의사항들입니다.

1. ExternalFileAccess 설정
주의할 것은, 이 기능은 파이어버드의 기본 설정에서 꺼져 있으므로 반드시 설정을 바꿔줘야 한다는 것입니다. 그러려면 파이어버드 서버의 설정 파일인 firebird.conf 파일에서 ExternalFileAccess의 값을 Full 혹은 Restrict로 바꿔줘야 합니다. 이 설정값의 디폴트 값은 None으로서, 외부 파일을 사용할 수 없도록 되어 있습니다. Restrict로 지정한 경우 지정된 디렉토리들만 외부 파일로 지정할 수 있습니다. (저는 그냥 Full을 썼습니다)

2. 바이트 얼라인먼트
파이어버드 외부 파일로 저장되는 포맷은, 바운더리 같은 것 없이 CREATE TABLE 문에서 지정했던 필드 크기만큼을 그대로 바이너리로 써넣으면 됩니다. 그런데 여기서 중요한 점 하나는, 이 포맷에 바이트 얼라인먼트(byte alignment)가 적용된다는 점입니다. 저도 이걸 몰라서 하루 이상 바이트가 안맞아 삽질을 했는데요. 이 얼라인먼트 값은 파이어버드의 문서 등에는 전혀 나오지 않지만, 오픈소스인 파이어버드의 소스에 지정되어 있습니다. 아래는 common.h에 지정되어 있는 내용입니다.
#ifndef ODS_ALIGNMENT
#define ODS_ALIGNMENT           4
#endif

즉, 파이어버드가 파일 저장에 사용하는 얼라인먼트 값은 4입니다. 단위는 바이트구요.

3. 날짜 필드들의 포맷
이 부분은, 제가 볼랜드포럼에 올렸던 아래 링크의 글에서 설명했으니 참고하시기 바랍니다.
파이어버드 날짜 타입의 내부 기준일과 율리우스 역법
http://www.borlandforum.com/impboard/impboard.dll?action=read&db=free&no=21619

4. 외부 파일 테이블의 제한점
다만, 이 외부 파일 테이블을 이용한 벌크 인서트를 할 때는, NULL이나 BLOB 타입 필드는 사용할 수 없고 고정폭 타입의 필드만 사용할 수 있다는 제한이 있습니다. 이건 외부파일의 구조를 추측해보면 이유를 짐작하실 수 있으실 거 같고요. 하지만 대량의 초고속 인서트가 필요한 데이터의 경우, 사실 BLOB 등의 필드를 사용할 일은 흔치 않지요.

5. 외부 파일이 서버 로컬 머신에 있어야
짐작하시겠지만, 외부 파일 테이블 기능은 서버의 로컬 머신에서만 가능합니다. 네트워크를 건너 원격 머신의 파일을 외부 파일 테이블로 지정할 수는 없습니다. 즉 서버 로컬에서 작업하는 경우가 아니라 서버와 클라이언트가 별도로 있는 경우, 파일을 서버쪽에 생성할 방법을 따로 마련해야 합니다. 이런 불편함은 있어도, 압도적인 성능상의 이점을 생각한다면 충분히 그만한 가치가 있습니다.
박지훈.임프 [cbuilder]   2012-08-23 09:36 X
약간 응용을 하자면... 이런 케이스를 가정해봅시다. 만약 어떤 데이터베이스가, 항상 당일치 데이터는 조회할 일이 없고 전날까지의 데이터만 분석한다고 하고, 벌크 인서트는 아니지만 로그 성격의 데이터가 실시간으로 대단히 많이 들어와서 데이터베이스에 부하를 준다면 말이죠.

이런 경우에도 외부 파일 테이블 기능을 활용하면 대단히 환상적인 결과가 나올 수 있을 겁니다. 실시간으로 들어오는 데이터는 외부 파일에 저장해놓고 하루종일 업무 시간에는 전혀 데이터베이스에 반영하지 않는 거죠. 그리고 자정이 넘은 직후에 하룻동안 쌓인 외부 파일의 데이터를 INSERT...SELECT SQL로 한방에 데이터베이스에 반영하는 거죠.

이렇게 하면, 일상 시간 동안에는 로그 데이터를 남기는 일이 데이터베이스에 전혀! 부하를 주지 않게 되므로 이전날까지의 데이터를 조회하는 데에 CPU 타임을 100% 활용할 수 있게 되겠지요.

아주 극단적으로 하드한 케이스를 가정해보지요. 하룻동안 1억건의 로그 데이터가 들어오는 경우. 만약 하룻동안 1억건이 균등하게 들어왔다면, 초당 무려 1157건이 들어온 것입니다. 이런 상황이 얼마나 되겠어, 하실 수도 있지만 저는 제 잡에서 이런 작업을 해본 적도 있고 주위에서도 아주 여러번 봤습니다.

이렇게 하드하게 인서트가 몰려들어오는 상황이라면, 서버 머신이 상당한 고성능 머신이라고 해도 레코드 INSERT 처리에만 상당히 버벅거리게 될 것입니다. 데이터베이스가 오라클이든 MS SQL이든 파이어버드이든 무관하게 말이죠. 그럼에도, 그렇게 많은 비용을 들이고도 조인 등으로 복잡한 조회 쿼리를 제대로 처리하기 어려울 수도 있습니다.

하지만 하루 종일 로그 데이터는 외부 파일에 쌓아두고 있다가, 그것을 자정이 지나면 일괄 인서트하는 경우라면, 위 본문에서 예를 들었던 케이스대로라면 파일 데이터로서는 초당 겨우 64kB 정도의 쓰기 작업에 불과하므로 초당 디스크 부하는 대단치 않게 되고요. 또 자정이 지나서 일괄 인서트할 때도 160만건 정도를 INSERT 하는 데에 22초 정도 걸리는 정도였으니, 1억건의 데이터라고 해도 INSERT에 걸리는 시간은 20분 남짓에 불과하게 됩니다.

요약하자면... 이렇게 벌크 인서트 기능을 잘 활용하면, 경우에 따라서는 고가의 데이터베이스와 고성능 서버 머신을 쓰고도 버벅거리던 환경을, 저가의 싸구려 서버로도 훨씬 쌩쌩하게 동작하는 환경으로 바꿀 수 있게 되는 거죠. 파이어버드 만쉐이~~
노진 [youngalex]   2015-08-22 00:01 X
뒤늦게나마 좋은글 감사합니다.

+ -

관련 글 리스트
85 Firebird에서 고속으로 대용량 INSERT를 하려면 박지훈.임프 17631 2012-08-23
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.