컨텐츠 바로가기

09.28 (토)

노드의 내장형 SQ라이트 모듈 소개

댓글 첫 댓글을 작성해보세요
주소복사가 완료되었습니다
노드 22.5.0에 이제 SQ라이트가 번들로 포함된다. SQ라이트는 부가적인 인프라를 필요로 하지 않으면서 강력한 기능을 갖춘 가벼운 인프로세스 관계형 데이터베이스다. Node.js에 기본적으로 제공되는 새롭고 유용한 이 기능을 살펴보자.

SQ라이트란 무엇인가?

관계형 데이터베이스는 소프트웨어 환경의 중요 구성요소다. SQ라이트는 간소하지만 다양한 사용례에 맞는 다재다능한 데이터베이스다. 노드 22.5에는 런타임과 함께 node.SQ라이트 모듈이 제공된다.

SQ라이트는 로컬 디스크의 일반 파일인 하나의 스토리지 파일을 사용해 실행 가능하다. 마이SQL이나 오라클과 같은 분산 데이터베이스의 오버헤드 또는 정교함이 불필요한 경우 간단한 스토리지를 위해 이 로컬 버전을 사용할 수 있다. 또한 SQ라이트에는 구성할 부분이 거의 없는, 테스트와 프로토타이핑에 유용한 인메모리 구현도 포함된다.

SQ라이트는 매우 단순한 데이터베이스이면서 ACID를 보장하며 충돌 내성을 갖췄다. 또한 속도가 상당히 빠르다. 모든 작업이 간단한 파일 하나에서 이뤄지지만 관계형 데이터베이스의 모든 의미는 그대로 유지되는, 강력한 다이렉트 파일시스템 스토리지 접근 방법이라고 할 수 있다. 매우 인기 있는 데이터베이스다.

새로운 SQ라이트 모듈

SQ라이트 모듈은 아직 실험 단계라고 볼 수 있으며, 이를 사용하는 프로그램을 실행할 때는 --experimental-SQ라이트라는 특수한 플래그를 사용해야 한다. 나중에 나올 예제를 보면 알겠지만 지금까지 노드 SQ라이트 지원은 동기 API를 통해 이뤄진다. 비동기 버전 도입에 대한 논의도 현재 진행 중이다.

노드가 모듈에 직접 SQ라이트를 통합하기로 결정한 데서 이 데이터베이스의 유용성을 짐작할 수 있다. (무종속성 SQ라이트 지원을 두고 노드와 번(Bun)이 경쟁까지 한다.)

예제

예제를 사용해 본격적으로 알아보자. 다음 코드를 사용해 테이블을 만들고 여기에 레코드 몇 개를 삽입한다.
import { DatabaseSync } from 'node:sqlite'; // 1
const database = new DatabaseSync(':memory:'); // 2

database.exec(`
CREATE TABLE IF NOT EXISTS physics (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, quote TEXT)
`); // 3

const insert = database.prepare('INSERT INTO physics (name, quote) VALUES (?, ?)'); // 4

// 5:
insert.run('Albert Einstein', "I believe in Spinoza's God" );
insert.run('Marie Curie', 'Nothing in life is to be feared, it is only to be understood');
insert.run("Richard Feynman", "Nobody understands quantum mechanics.");

const query = database.prepare('SELECT * FROM physics ORDER BY name'); // 6
console.log(query.all()); // 7

이 코드를 실행하려면 실험 플래그인 node --experimental-SQ라이트 physicists.mjs가 필요하다. 예제의 라인을 설명하기 위해 번호별 주석을 추가했다.

(1)에서는 먼저 SQ라이트 module을 가져온다. 노드 네임스페이스는 필수 항목이다. (2)에서는 DatabaseSync 인스턴스를 확보한다. :memory:라는 이름은 SQ라이트에 모든 것을 디스크가 아닌 메모리에 저장하도록 지시하는 특수한 이름이다.

(3)에서는 데이터베이스에 create table 명령을 실행한다. 표준 SQL이며, 테이블이 없는 경우 새 테이블을 만들 수 있게 해준다(여기서는 인메모리 데이터베이스를 사용 중이므로 만들지 않음). physics라는 테이블에는 다음과 같은 3개의 열이 있다.
  • 기본 키 id. autoincrement로 설정된다. 새 행이 삽입될 때마다 데이터베이스는 이 열에 대해 다음으로 높은 정수 값을 자동으로 적용한다. 기본 키는 관계형 데이터베이스에서 레코드의 고유 식별자다.
  • text 형식의 name 열
  • text 형식의 quote 열

SQ라이트는 준비된 문을 지원한다. 이 문을 사용해서 변수가 있는 SQL 문을 정의한다. 그런 다음 준비된 문에 구체적인 값을 제공한다. 이렇게 하면 비슷한 여러 문을 실행하거나 반복 실행할 때 효율성을 높일 수 있다. (4)에서는 name과 quote, 두 개의 변수가 있는 insert 문을 준비한다.

(5)에는 준비된 문에 특정 값을 적용하는 3개의 insert.run() 호출이 있다. 이 호출은 테이블에 3개의 인용구를 삽입한다.

마지막으로 (6)에서는 SELECT 준비된 문을 만든 다음 query.all()로 실행해서 콘솔에 결과를 출력한다.
$ node --experimental-sqlite physicists.mjs
[
{
id: 1,
name: 'Albert Einstein',
quote: "I believe in Spinoza's God"
},
{
id: 2,
name: 'Marie Curie',
quote: 'Nothing in life is to be feared, it is only to be understood'
},
{
id: 3,
name: 'Richard Feynman',
quote: 'Nobody understands quantum mechanics.'
}
]

테이블을 디스크에 저장하려면 데이터베이스 매개변수를 데이터가 저장될 파일 이름으로 바꾸면 된다.
const database = new DatabaseSync('myFile.db');

이 간단한 변경으로 다양한 프로세스와 프로그램에 걸쳐 데이터를 공유하기 위한 지속성 메커니즘을 달성했다. 앞서 살펴본 예제를 이 구성으로 실행하면 디스크에 myFilename.db 파일이 생성된다.
$ cat myFile.db
??P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)mtablephysicsphysicsCREATE TABLEII8+WRichard FeynmanNobody understands quantum mechanics.L#Marie CurieNothing in life is to be feared, it is only to be understood-+AAlbert EinsteinI believe in Spinoza's God

이 예제에서 SQ라이트의 온디스크 파일 형식을 볼 수 있다.

노드에서 SQ라이트를 사용하는 간단한 명령

이제 데이터베이스에 있는 물리학 인용구의 수를 세는 새 파일을 만들어 보자.

// count.mjs
import { DatabaseSync } from 'node:sqlite';

const database = new DatabaseSync('myFile.db');

const query = database.prepare('SELECT COUNT(*) AS count FROM physics');
const result = query.get();

console.log(`There are ${result.count} quotes in the physics table.`);

다음과 같이 이 새 프로그램을 실행할 수 있다.

$ node --experimental-sqlite count.mjs

physics 테이블에는 3개의 인용구가 있다.

새 인용구를 추가하려면 SQ라이트의 명령줄 인터페이스를 사용한다. 예를 들어 name과 quote, 두 개의 인수를 받아서 addQuote.mjs를 사용해 삽입한다면 다음과 같다.
// addQuote.mjs
import { DatabaseSync } from 'node:sqlite';

const [, , name, quote] = process.argv;

// Check for missing args
if (!name || !quote) {
console.error('Please provide both name and quote arguments!');
process.exit(1);
}

const database = new DatabaseSync('myFile.db');

const insert = database.prepare('INSERT INTO physics (name, quote) VALUES (?, ?)');
insert.run(name, quote);

console.log(`Quote added successfully for ${name}`);

database.close();

CLI 앱은 두 개의 인수를 받아서 새 레코드를 추가하는 데 사용한다. 참고로 인수는 큰따옴표로 묶어야 한다. 코드는 다음과 같다.
$ node --experimental-sqlite addUser.mjs "Werner Heisenberg" "Now, this is a very strange result, since it seems to indicate that the observation plays a decisive role in the event"

Quote added successfully for Werner Heisenberg

새 인용구가 있는지 확인하려면 getQuotes.mjs를 실행한다.
// getQuotes.mjs
import { DatabaseSync } from 'node:sqlite';
const database = new DatabaseSync('myFile.db');

const query = database.prepare('SELECT * FROM physics ORDER BY name');
console.log(query.all());

SQ라이트의 트랜잭션

비슷한 명령으로 풍부한 관계와 복잡한 조인을 포함한 SQL의 모든 기능에 액세스할 수 있다. SQ라이트는 여러 작업을 하나의 원자적 그룹으로 묶는 데 사용할 수 있는 트랜잭션도 지원한다. 즉, 어느 하나의 작업이 실패하면 모두 롤백된다.

마지막 예제로, 두 개의 실험이 모두 성공하는 경우에만(여기서 성공은 각 실험이 삽입되는 것을 의미) 데이터베이스에 실험을 추가하려는 경우를 가정해 보자. 두 개의 삽입을 하나의 트랜잭션으로 래핑해서 어느 하나의 삽입이 실패하면 전체 트랜잭션이 롤백되도록 할 수 있다.
// experiments.mjs
import { DatabaseSync } from 'node:sqlite';

const database = new DatabaseSync('experiments.db');

database.exec(`
CREATE TABLE IF NOT EXISTS experiments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
description TEXT
)
`);

const addExperiments = (experiment1, experiment2) => {
database.exec('BEGIN TRANSACTION');

try {
database.prepare('INSERT INTO experiments (name, description) VALUES (?, ?)').run(experiment1.name, experiment1.description);
database.prepare('INSERT INTO experiments (name, description) VALUES (?, ?)').run(experiment2.name, experiment2.description);
database.exec('COMMIT');
console.log('Experiments added successfully');
} catch (error) {
database.exec('ROLLBACK');
console.error('Error adding experiments:', error);
}
};

const experiment1 = {
name: "Newton's Law of Gravity",
description: "An experiment to demonstrate the gravitational force between two objects."
};

const experiment2 = {
name: "Ohm's Law",
description: "An experiment to demonstrate the relationship between voltage, current, and resistance."
};

addExperiments(experiment1, experiment2);

결론

SQ라이트는 다재다능한 툴이며 노드의 기본 프로필에 추가된 것은 환영할 만한 일이다. 데이터 지속을 위한 가벼운 메커니즘이 필요할 때 SQ라이트를 반드시 고려해야 한다.

여기서는 간단한 예제를 살펴봤지만 SQ라이트는 분산 데이터스토어 또는 막대한 처리량이 필요 없다면 더 까다로운 요구사항에도 대응할 수 있다. 많은 소규모 애플리케이션과 웹사이트가 이 요구사항을 충족하므로 SQ라이트를 사용하면 더 복잡한 데이터스토어를 사용하는 데 따르는 부가적인 번거로움을 피할 수 있다. 노드 22.5.0부터는 라이브러리를 포함할 필요도 없다. 기사에서는 NPM 또는 다른 어떤 패키지 관리자도 사용하지 않고 예제를 설정하고 실행할 수 있었다.
editor@itworld.co.kr

Matthew Tyson editor@itworld.co.kr
저작권자 한국IDG & ITWorld, 무단 전재 및 재배포 금지
기사가 속한 카테고리는 언론사가 분류합니다.
언론사는 한 기사를 두 개 이상의 카테고리로 분류할 수 있습니다.