문득 데이터베이스를 공부하다가 실제로 얼마나 유의미하게 검색기능이 차이가 나는지 궁금하기도 해서 성능을 비교해보고자 한다.
데이터베이스는 두 가지로 postgresql과 elasticsearch(opensearch)로 하고자 한다.
두 데이터베이스의 비교를 위해 같은 cpu, memory 환경의 호스트에서 진행했으며 도커 컴포즈로 postgres 레플리카 세개와 opensearch도 클러스터링 하여 노드 세개로 진행하였다. (이렇게 진행한다해서 완벽히 같은 리소스를 요구하진 않지만 그래도 맞춰주는 것이 좋다 생각했다.)
그렇다면 검색 성능에서 주로 보이는 ELK 스택에서의 elasticsearch의 검색기능이 얼마나 차이가 나는지 대충 비교해보자면,
Elasticsearch
그렇다면 postgres 혹은 mysql같은 rdb에서는 keyword 검색할 때, Like %keyword%를 쓰는 경우가 많은데 이러한 기능을 쓰면 full table scan으로 인해 데이터가 많으면 많을 수록 많은 테이블들을 스캔하기때문에 성능이 저하되기도 한다.
elasticsearch는 _bulk로 데이터를 넣으면 훨씬 빨리 넣을 수 있다.
추가적으로 깔려있는 것이 OPENSEARCH이기때문에 테스트는 opensearch로 진행했다.
const fs = require("fs");
const csv = require('fast-csv');
const {Client : ElasticClient } = require("@opensearch-project/opensearch");
const CSV_FILE_PATH = "./10_________________.csv";
const esClient = new ElasticClient({
node: "https://호스트:9200",
auth: {
username: "사용자 계정",
password: '패스워드'
},
ssl: {
rejectUnauthorized: false,
},
});
async function pushElasticSearch(){
let count = 0;
const stream = fs.createReadStream(CSV_FILE_PATH).pipe(csv.parse({ headers: true }));
for await (const row of stream) {
const { title, content } = row;
// OpenSearch 삽입
await esClient.index({
index: "articles",
body: { title, content },
});
count++;
if (count % 1000 === 0) console.log(`Inserted ${count} records...`);
}
console.log(" 데이터 삽입 완료!");
}
pushElasticSearch().catch(console.error)
const fs = require("fs");
const csv = require("fast-csv");
const { Client } = require("pg");
const CSV_FILE_PATH = "./10_________________.csv";
const pgClient = new Client({
user: "계정",
host: "호스트",
database: "데이터베이스",
password: "비밀번호",
port: 5432,
});
async function pushPostgres() {
try {
await pgClient.connect();
console.log(" PostgreSQL 연결 성공!");
await pgClient.query(`
CREATE TABLE IF NOT EXISTS articles (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT
);
`);
console.log(" 테이블 생성 완료!");
let count = 0;
const stream = fs
.createReadStream(CSV_FILE_PATH)
.pipe(csv.parse({ headers: true }));
for await (const row of stream) {
const { title, content } = row;
await pgClient.query("INSERT INTO articles (title, content) VALUES ($1, $2)", [title, content]);
count++;
if (count % 1000 === 0) console.log(`Inserted ${count} records...`);
}
console.log(" 모든 데이터 삽입 완료!");
} catch (error) {
console.error(" PostgreSQL 오류 발생:", error);
} finally {
await pgClient.end();
console.log(" PostgreSQL 연결 종료");
}
}
pushPostgres().catch(console.error);
테스트 코드
const { Client } = require("pg");
const { Client : ElasticClient} = require("@opensearch-project/opensearch");
const pgClient = new Client({
user: "계정",
host: "호스트",
database: "데이터베이스",
password: "비밀번호",
port: 5432,
});
const esClient = new ElasticClient({
node: "https://호스트:9200",
auth: {
username: "사용자 계정",
password: '패스워드'
},
ssl: {
rejectUnauthorized: false,
},
});
async function searchPostgresILike(keyword) {
try {
await pgClient.connect();
console.log(" PostgreSQL 연결 성공!");
const start = Date.now();
const res = await pgClient.query("SELECT * FROM articles WHERE content ILIKE $1", [`%${keyword}%`]);
const duration = Date.now() - start;
console.log(` PostgreSQL ILIKE 검색 (${keyword}): ${res.rowCount}개 결과, ${duration}ms`);
return duration;
} catch (error) {
console.error(" PostgreSQL 검색 오류:", error);
} finally {
await pgClient.end();
console.log(" PostgreSQL 연결 종료");
}
}
async function searchElasticsearchWildcard(keyword) {
try {
const start = Date.now();
const res = await esClient.search({
index: "articles",
body: {
query: {
wildcard: {
content: {
value: `*${keyword}*`,
case_insensitive: true // 대소문자 구분 없음
}
}
}
}
});
const duration = Date.now() - start;
console.log(` Elasticsearch wildcard 검색 (${keyword}): ${res.body.hits.total.value}개 결과, ${duration}ms`);
return duration;
} catch (error) {
console.error(" Elasticsearch 검색 오류:", error);
}
}
searchElasticsearchWildcard("single");
searchPostgresILike("single");
pg_trgm확장기능을 사용하면, gin index를 사용하면 빠른 검색을 할 수 있다.
해당 인덱스를 적용하면 index 스캔을 사용한다.
pg_trgm을 사용하면, 문자 단위의 검색을 사용할 수 있기 때문에 속도가 향상된다.
정확한 단어가 아니더라도 검색이 가능하고, 짧은 단어에 유리하다.
-- 1. 확장 기능 활성화
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- 2. GIN 인덱스 생성
CREATE INDEX idx_articles_content_trgm ON articles USING GIN (content gin_trgm_ops);
-- 3. LIKE, ILIKE 성능 테스트
EXPLAIN ANALYZE SELECT * FROM articles WHERE content ILIKE '%검색어%';
왜 요즘은 글 안 올리시나요ㅠ