-
[Node.js & MySQL] 도전과제: 검색 - 색인기능 살펴보기생활코딩/WEBn 2021. 1. 20. 23:11
자주 검색하는 컬럼이라면 색인을 넣어주자. 검색 속도가 빨라진다.
아래처럼 인덱스를 만들어주고, 기존대로 WHERE 컬럼명 = ?으로 검색하면 된다.
다만 데이터가 꽤 많아야(테스트했을 때는 몇십만건 정도) 유의미한 차이를 느낄 수 있다.
CREATE INDEX index_category ON 테이블명(컬럼명)
생활코딩 Node.js - MySQL 수업은 이고잉님이 추가 도전과제를 던져주는 것으로 끝난다.
검색, 정렬(저자 이름 순, 시간 순), 페이지 기능을 구현해보면 좋겠다. 그리고 팁을 몇 가지 준다.
1. 검색
<form>의 get 방식을 이용해서 검색 요청을 처리하고,
db단에서 SQL구문 SELECT * FROM topic WHERE title = "keyword"로 데이터를 찾는다.
이때 데이터가 많으면 느려질 수 있으니 index 기능을 사용하는 것이 좋다.
2. 정렬
SELECT * FROM topic ORDER BY id DESC
3. 페이지
SELECT * FROM topic LIMIT 0 OFFSET 20
순서대로 하나씩 해볼 예정인데, 먼저 그전에 검색에서 색인 기능이 왜 필요한지를 알아보고 가기로 했다.
색인이 내가 아는 그 id 컬럼을 말하는 건 아닐 것 같아서, 검색을 좀 해본 결과
[database] DB 인덱스(INDEX)란? 라는 고마운 글을 찾았다.
책 뒤의 찾아보기 페이지에 빗대어서 db의 색인이 어떻게 구현하는지 설명해준 다음, 쿼리를 알려준다.
CREATE INDEX index_category ON 테이블명(컬럼명)
사용시에는 기존과 똑같이 기존 컬럼명을 사용하면 된다.
SELECT * FROM 테이블명 WHERE 컬럼명 = '?'
효과가 어느 정도인지 궁금해져서 속도 테스트를 해보기로 했다.
INDEX ON 속도 테스트
속도 테스트를 하려면 데이터가 꽤 많아야 하고, 그걸 직접 작성할 수는 없으니 dummydata를 구해야 한다.
자바스크립트도 연습할 겸 직접 만들어보기로 했다.
id, alphar, number
alpha: A부터 Z까지 알파벳을 조합해서 3글자 길이의 문자열을 만든다. AAA ~ BBC ~ ZZZ.
26**3이면 17576개의 데이터를 만들 수 있다!
number: 알파벳의 자릿수를 더한다. A면 0, Z면 0. 0 ~ 112 ~ 252525.
일단 DB와 테이블을 만들고
mysql> CREATE DATABASE test; Query OK, 1 row affected (0.09 sec) mysql> USE test; Database changed mysql> CREATE TABLE dummy( id INT(11) NOT NULL AUTO_INCREMENT, alpha CHAR(5) NOT NULL, numbers BIGINT NOT NULL, PRIMARY KEY(id)); Query OK, 0 rows affected (0.22 sec)
데이터를 만들어본다.
var mysql = require('mysql'); // 비밀번호는 별도의 파일로 분리해서 버전관리에 포함시키지 않아야 합니다. var db = mysql.createConnection({ host : 'localhost', user : 'root', password : 'password', database : 'test' }); db.connect(); module.exports = db; var letter = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; for (var i in letter) { for (var j in letter) { for (var k in letter) { var a = `${letter[i]}${letter[j]}${letter[k]}`; var n = i + j + k; db.query('INSERT INTO dummy (alpha, numbers) VALUES (?, ?)', [a, n], function(err, result){ if (err) throw err; }); }; }; };
그렇게 오래 걸리지는 않았다. 1~2분 정도? 중간중간 진행상황을 확인했다.
mysql> SELECT COUNT(*) FROM dummy;
그리고 비교용으로 dummy2를 만들고, dummy에만 인덱스를 추가해준다.
mysql> CREATE INDEX index_alpha ON dummy(alpha);
그리고 목록 제일 끝에 있는 ZZZ를 찾아본다.
17,576건(26**3)일 때
mysql> SELECT * FROM dummy WHERE alpha = 'ZZZ'; +-------+-------+--------+ | id | alpha | number | +-------+-------+--------+ | 17576 | ZZZ | 252525 | +-------+-------+--------+ 1 row in set (0.00 sec) mysql> SELECT * FROM dummy2 WHERE alpha = 'ZZZ'; +-------+-------+--------+ | id | alpha | number | +-------+-------+--------+ | 17576 | ZZZ | 252525 | +-------+-------+--------+ 1 row in set (0.01 sec)
거의 차이가 나지 않는다... 아무래도 데이터 양이 부족한 것 같다. 데이터를 늘려보자.
데이터가 몇 만개가 되니까 그냥 돌리면 타임아웃 에러가 떠서 BULK INSERT 방식으로 변경했다
var mysql = require('mysql'); // 비밀번호는 별도의 파일로 분리해서 버전관리에 포함시키지 않아야 합니다. var db = mysql.createConnection({ host : 'localhost', user : 'root', password : 'password', database : 'test' }); db.connect(); module.exports = db; var letter = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; var _promise = function (one, place){ return new Promise(function (resolve, reject) { var text = ''; for (var i in letter){ for (var j in letter){ for (var k in letter){ text += `('${one}${letter[i]}${letter[j]}${letter[k]}', ${place+i+j+k}), `; }; }; }; resolve(text); }); }; for (var i in letter){ _promise(letter[i], i).then(function(text){ query = 'INSERT INTO dummy (alpha, number) VALUES ' + text.slice(0, -2); query2 = 'INSERT INTO dummy2 (alpha, number) VALUES ' + text.slice(0, -2); db.query(query, function(err, result){ if (err) throw err; }); db.query(query2, function(err, result){ if (err) throw err; }); }); };
Promise를 적용해서 for문으로 작성한 데이터를 벌크로 입력했다. 충격적으로 빨리 진행되었다!!
26**4면 데이터가 45만개가 넘는데 30초가 채 안 걸렸다. 위에서 1.7만개에 2분 가까이 걸린게 얼마나 비효율적이었는지 알겠다. 앞으로는 꼭 벌크 인서트를 써야겠다.
데이터가 준비되었으니 다시 테스트를 해본다. 일단 dummy에만 인덱스를 추가해준다.
mysql> CREATE INDEX index_alpha ON dummy(alpha); Query OK, 0 rows affected (4.55 sec)
4.55초가 걸렸다! 이번에는 차이가 좀 날 것 같다. 목록 마지막에 있는 'ZZZZ'를 검색해본다.
456,976건(26**4)일 때
mysql> SELECT * FROM dummy WHERE alpha = 'ZZZZ'; +--------+-------+----------+ | id | alpha | number | +--------+-------+----------+ | 456976 | ZZZZ | 25252525 | +--------+-------+----------+ 1 row in set (0.02 sec) mysql> SELECT * FROM dummy2 WHERE alpha = 'ZZZZ'; +--------+-------+----------+ | id | alpha | number | +--------+-------+----------+ | 456976 | ZZZZ | 25252525 | +--------+-------+----------+ 1 row in set (0.35 sec)
0.2초대 0.35초로 인덱스의 압승이다! 과연 천만개가 넘어가면 어떻게 될까?
천만개는 데이터 만드는 것부터 어려웠다. 벌크로 넣어도 mysql 타임아웃이 나와서 connectTimeout 옵션을 늘려주었다. 총 소요시간은 5분 언저리였던 것 같다. 데이터 생성은 1분 내로 되는데 들어가는 게 오래 걸린다.
어쨌든 데이터를 확보했으니 다시 확인해본다. 이번에도 한쪽에만 인덱스를 추가한다.
mysql> CREATE INDEX index_alpha ON dummy5(alpha); Query OK, 0 rows affected (1 min 16.64 sec) Records: 0 Duplicates: 0 Warnings: 0
1분 좀 더 걸린다. 각각 'ZZZZZ'를 검색해본다.
11,881,376건일 때
mysql> SELECT * FROM dummy5 WHERE alpha = 'ZZZZZ'; +----------+-------+------------+ | id | alpha | numbers | +----------+-------+------------+ | 11881376 | ZZZZZ | 2525252525 | +----------+-------+------------+ 1 row in set (0.05 sec) mysql> SELECT * FROM dummy6 WHERE alpha = 'ZZZZZ'; +----------+-------+------------+ | id | alpha | numbers | +----------+-------+------------+ | 11881376 | ZZZZZ | 2525252525 | +----------+-------+------------+ 1 row in set (6.49 sec)
0.05초대 6.49초로 상당히 차이가 나기 시작했다. 천만개에서 이정도니까 1억개가 넘어가면 어마어마한 차이가 될 듯하다.
<결론>
INDEX ON의 중요성을 깨닫긴 했지만, 데이터 45만개까지는 그다지 큰 차이가 없는 듯해서 이번 검색 기능 구현에서는 생략하려고 한다.
'생활코딩 > WEBn' 카테고리의 다른 글
[Node.js & MySQL] 코드 리팩토링, 이슈 정리 (0) 2021.01.24 [Node.js & MySQL] 도전과제: 검색/페이징/정렬 (2) 2021.01.24 [Node.js & MySQL] 보안: SQL injection, Escaping (0) 2021.01.13 [Node.js & MySQL] 테이블 생성 (0) 2021.01.13 [Node.js & MySQL] main.js 정리 - DB접속 정보/쿼리 분리 (0) 2021.01.12