RAM에서만 살아 숨 쉬는 데이터베이스
SQLite에는 :memory:라는 특별한 파일 이름이 있습니다. 이 이름으로 데이터베이스를 열면 SQLite는 디스크를 아예 거치지 않고, 데이터베이스 전체를 RAM 위에 올려둡니다. 테이블, 인덱스, 트랜잭션, 외래 키 등 모든 기능은 평소와 똑같이 동작합니다. 다른 점은 딱 하나, 연결을 닫는 순간 데이터베이스도 함께 사라진다는 것뿐이죠.
명령줄에서 이렇게 실행해 볼 수 있습니다:
sqlite3 :memory:
이제 SQLite 프롬프트에 들어왔고, 메모리에만 존재하는 비어 있는 새 데이터베이스가 준비된 상태입니다. 평소처럼 테이블을 만들고, 행을 넣고, 조회해 보면 됩니다:
세션을 종료하면 데이터는 그대로 증발합니다. 파일이 남지 않는 이유는 애초에 파일을 만든 적이 없기 때문입니다.
인메모리 DB가 필요한 순간
재시작도 못 버티는 데이터베이스라니, 기능이라기보단 버그처럼 들립니다. 그런데 다음 세 가지 상황에서는 오히려 이 점이 무기가 됩니다.
테스트. 모든 테스트가 밀리초 단위로 깨끗한 데이터베이스를 받아서 시작합니다. 임시 파일을 정리할 필요도 없고, 이전 실행에서 남은 상태가 끼어들 일도 없으며, 공용 픽스처 파일이 잠기는 문제도 없습니다. SQLite를 쓰는 Python, Node, Go 테스트 스위트 대부분이 :memory:를 여는 이유가 바로 이것입니다.
일회성 분석. CSV를 한 번 불러와서 쿼리 몇 개 돌리고 버리는 작업. 진짜 DB를 띄우는 것보다 빠르고, 매번 코드로 파일을 파싱하는 것보다 간편합니다.
캐시와 임시 작업 공간. 오래 실행되는 프로그램 안에서 SQLite 인메모리 DB는 이미 메모리에 올린 데이터를 즉석에서 SQL로 다루기에 의외로 훌륭한 쿼리 엔진이 됩니다.
공통점은 단 하나, SQL은 쓰고 싶지만 영속성은 필요 없다는 것입니다.
성능: 빨라지긴 하지만, 마법은 아닙니다
인메모리 데이터베이스는 디스크를 거치지 않기 때문에, 평소라면 파일시스템에 닿았을 쓰기 작업이 단순한 메모리 갱신으로 끝납니다. 그래서 I/O 바운드 작업은 체감될 만큼 빨라집니다. 반면 복잡한 쿼리 플래닝이나 대규모 정렬처럼 CPU 바운드인 작업은 거의 차이가 없습니다. SQLite는 이미 자주 쓰는 페이지를 메모리에 캐싱하고 있었기 때문이죠.
문법이 얼마나 똑같은지 짧은 예제로 확인해 봅시다:
방금 실행한 쿼리는 인메모리 데이터베이스를 대상으로 한 거지만, 파일 DB에서 돌리는 SQL과 완전히 똑같습니다. SQLite 엔진 입장에서는 차이가 없거든요.
SQLite 인메모리 vs 파일 DB, 언제 뭘 써야 할까?
선택 기준은 단순합니다. 그래도 짚고 넘어갈 만한 가치가 있죠:
- 파일 데이터베이스 (
mydata.db): 재시작해도 데이터가 남습니다. 여러 프로세스가 동시에 열 수 있고, WAL 모드를 쓰면 크래시가 나도 (대체로) 살아남습니다. 무언가를 기억해야 하는 모든 작업에는 이걸 쓰세요. - 인메모리 데이터베이스 (
:memory:): 연결을 닫는 순간 사라집니다. 기본적으로 해당 연결 안에서만 접근할 수 있고요. 한 번 쓰고 버릴 작업이라면 쓰기 처리량이 더 빠릅니다. 테스트, 임시 작업, 짧게 살다 가는 캐시에 적합합니다.
확신이 안 선다면 그냥 파일 DB를 쓰세요. 인메모리는 어디까지나 특수한 경우입니다.
연결마다 별도의 DB가 만들어진다
은근히 헷갈리는 포인트가 하나 있습니다. :memory:를 두 번 열면 _완전히 다른 두 개의 데이터베이스_가 생깁니다. 테이블도 공유 안 되고, 데이터도 공유 안 되고, 서로의 존재 자체를 모릅니다.
-- ターミナル 1
sqlite3 :memory:
sqlite> CREATE TABLE t (x); INSERT INTO t VALUES (1);
-- ターミナル 2
sqlite3 :memory:
sqlite> SELECT * FROM t;
Error: no such table: t
그건 버그가 아니라 원래 그렇게 설계된 겁니다. :memory:는 "이 커넥션 전용 데이터베이스"라는 뜻이거든요. 같은 프로그램 안에서도 마찬가지입니다. 코드에서 :memory:로 두 개의 커넥션을 열면, 각각 완전히 격리된 별개의 데이터베이스가 만들어집니다.
여러 커넥션에서 sqlite 인메모리 DB 공유하기
여러 커넥션이 같은 인메모리 데이터베이스를 바라보게 하고 싶다면, SQLite는 URI 파일명과 shared cache를 통해 이를 지원합니다. 핵심은 file::memory:?cache=shared라는 문자열입니다:
sqlite3 'file::memory:?cache=shared'
같은 프로세스 안에서 이 URI를 정확히 똑같이 열어 연결하는 다른 코드들은 모두 동일한 데이터베이스에 붙게 됩니다. 모든 연결이 닫히는 순간 데이터베이스도 함께 사라집니다.
이름을 지정한 인메모리 데이터베이스를 만들 수도 있는데, 서로 다른 공유 데이터베이스를 여러 개 따로 두고 싶을 때 유용합니다:
sqlite3 'file:mydb?mode=memory&cache=shared'
여기서 mydb는 단순한 식별자일 뿐, 실제 파일이 만들어지는 건 아닙니다. 두 연결이 모두 file:mydb?mode=memory&cache=shared로 열면 같은 데이터베이스를 공유하고, file:other?mode=memory&cache=shared로 여는 연결은 별개의 데이터베이스를 갖게 됩니다.
인메모리 DB를 디스크에 저장하기
작업을 전부 메모리에서 처리하다가 결과만 파일로 남겨두고 싶을 때가 있죠. CLI에는 이런 용도로 쓸 수 있는 .backup 닷 명령어가 준비되어 있습니다:
sqlite3 :memory:
sqlite> CREATE TABLE results (id INTEGER, score REAL);
sqlite> INSERT INTO results VALUES (1, 0.91), (2, 0.87);
sqlite> .backup snapshot.db
sqlite> .quit
snapshot.db는 이제 동일한 내용을 담은 일반 파일 DB가 됐습니다. 나중에 sqlite3 snapshot.db로 열면 작업하던 시점부터 그대로 이어갈 수 있죠.
반대 방향도 가능합니다. .restore를 쓰면 파일 DB를 현재 연결의 인메모리 DB로 불러올 수 있습니다:
sqlite3 :memory:
sqlite> .restore snapshot.db
sqlite> SELECT * FROM results;
애플리케이션 코드에서도 SQLite C API를 통해 sqlite3_backup_init과 동일한 기능을 그대로 사용할 수 있고, 대부분의 언어 바인딩이 이를 래핑해서 제공합니다. 예를 들어 파이썬 sqlite3 모듈에는 Connection.backup() 메서드가 준비되어 있습니다.
자주 빠지는 함정
가끔 인메모리 데이터베이스를 "저장"하겠다고 파일을 ATTACH한 뒤 데이터를 복사하는 방식을 시도하는 경우가 있습니다.
단순한 테이블 복사라면 이걸로 충분하지만, 이 방법은 인덱스, 트리거, 뷰, 외래 키까지 원본 그대로 보존해 주지는 않습니다. 데이터베이스 전체를 정확하게 복제하고 싶다면 .backup(또는 backup API)을 쓰세요. 페이지 단위로 바이너리까지 똑같이 복사해 줍니다.
핵심 정리
:memory:는 파일 없이 RAM 위에 데이터베이스를 만드는 SQLite의 특별한 파일명입니다.- SQL 문법은 파일 기반 DB와 완전히 동일합니다. 테이블, 쿼리, 제약 조건 모두 똑같이 동작해요.
:memory:로 연결할 때마다 각 커넥션은 서로 격리됩니다. 여러 커넥션이 같은 인메모리 DB를 공유해야 한다면 shared-cache URI(file::memory:?cache=shared)를 사용하세요.- 테스트, 일회성 데이터 분석, 짧게 쓰고 버리는 캐시에는 더없이 좋은 선택입니다. 다만 재시작 후에도 살아남아야 하는 데이터에는 적합하지 않습니다.
- 인메모리 DB의 내용을 디스크로 옮겨 보관하고 싶다면
.backup으로 파일 DB로 승격시키면 됩니다.
다음 글: 테이블 만들기
지금까지 예제에서 CREATE TABLE이 슬쩍슬쩍 등장했죠. 다음 글에서는 이걸 차근차근 풀어서 살펴봅니다. 컬럼 정의, 데이터 타입, 함께 붙일 수 있는 제약 조건, 그리고 나중에 두고두고 편한 스키마를 만드는 작은 선택들까지 다뤄볼게요.
자주 묻는 질문
SQLite 인메모리 데이터베이스는 어떻게 만드나요?
파일 경로 대신 특수 파일명 :memory:를 넘겨서 SQLite를 열면 됩니다. CLI라면 sqlite3 :memory:, 라이브러리에서는 connect 함수에 파일명으로 :memory:를 넣어주면 돼요. 이 데이터베이스는 RAM 위에 올라가고, 커넥션이 닫히는 순간 같이 사라집니다.
SQLite의 :memory:가 정확히 뭔가요?
:memory:는 SQLite가 인식하는 일종의 매직 파일명입니다. "파일 만들지 말고 전부 메모리에서 처리해"라는 뜻이죠. 테이블, 인덱스, 트랜잭션 등 SQLite의 기능은 그대로 다 쓸 수 있지만, 디스크에는 아무것도 기록되지 않습니다. 그리고 :memory:로 연결한 커넥션마다 각자 독립된 DB를 갖게 됩니다.
두 개의 커넥션이 같은 인메모리 DB를 공유할 수 있나요?
기본적으로는 안 됩니다. :memory:로 열린 커넥션은 서로 격리돼 있거든요. 공유하려면 file::memory:?cache=shared 같은 URI 형식으로 열고 shared cache를 활성화해야 합니다. 같은 프로세스 안에서 정확히 똑같은 URI로 연결한 커넥션들끼리는 동일한 DB를 바라보게 됩니다.
인메모리 SQLite DB를 디스크에 저장할 수 있나요?
네, 가능합니다. CLI에서는 .backup 명령을, 라이브러리에서는 backup API를 사용해서 인메모리 DB를 파일로 복사할 수 있어요. 또는 파일 DB를 ATTACH한 뒤 INSERT INTO file.table SELECT * FROM main.table 같은 쿼리로 데이터를 옮기는 방법도 있습니다.