‘이길저길’ 서비스 내에서 친구 검색 시 Member의 닉네임(문자열)으로 검색을 수행하는 API가 있고 기존 Like 검색은 데이터의 개수가 많아질수록 성능에서 부족함이 있었다.
Member 데이터를 500만 row 저장하고 특정 닉네임을 검색하는 쿼리를 Like 연산을 이용하여 수행하였다.
DELIMITER $$
DROP PROCEDURE IF EXISTS twtw.insertLoop$$
CREATE PROCEDURE twtw.insertLoop()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 5000000 DO
INSERT INTO twtw.MEMBER(id, created_at, updated_at, auth_type, nickname, profile_image, role, client_id)
VALUES (UNHEX(REPLACE(uuid(), '-', '')), now(), now(), 'APPLE', concat('n', i), concat('profile_image_', i), 'ROLE_USER', concat('client_id', i));
SET i = i + 1;
END WHILE;
END$$
DELIMITER $$
CALL twtw.insertLoop;
$$
SELECT * FROM member WHERE nickname like '%123456%';
Like 쿼리의 성능이 좋지 않아 효과적인 문자열 탐색에 있어 Full Text Index를 적용하기로 했다.
CREATE FULLTEXT INDEX idx_member_nickname ON member (nickname) with parser ngram;
SELECT * FROM member WHERE MATCH(nickname) AGAINST('123456' IN BOOLEAN MODE);
문자열을 모두 [디폴트 최소 길이: 2] 만큼 다 분할해 저장했기 때문에 500만 데이터를 쿼리하는데 드는 비용이 커져 쿼리 캐시 공간이 부족했다.
캐시 사이즈를 늘린다면 메모리 부담이 발생하므로 최적의 방법이라 판단하지 않았다 !
- 다른 해결 방법인 [인덱스 분할], [ElasticSearch 도입], [하드웨어 리소스 확장]은 오버 엔지니어링이라 판단하고 다른 해결방법 고민해보았다 !
현재 서비스 요구사항에서 닉네임의 길이 제한이 없다는 것에 의문을 두고 팀원들과 회의를 거쳐 닉네임 길이의 최대치를 정하기로 결정
- 최대치 8로 선정 ( 선정 과정에 있어 다른 서비스 모델을 참고 )
쿼리 Profiling으로 실행 계획 확인
검색을 하는 문자열의 길이가 기존 N-gram의 길이 2보다 크기 때문에 더 세세한 검색 과정을 거치기 때문이다.
- 2씩 나눠진 토큰을 포함하면서 동시에 해당 문자열의 순서까지 일치하는 데이터를 찾아야 한다.
- N-gram의 길이로 나뉘어진 문자열은 즉 자체로 인덱스를 의미하고 해당 인덱스로만의 탐색이 어렵다는 것이다.
ngram parser의 최소 단위인 2만큼 문자열을 나눈 후 이 나눈 문자열이 모두 포함된 부분을 검색하면 더 빠른 검색이 가능할거라 예상
SHOW GLOBAL VARIABLES LIKE "ngram_token_size"; ( 2인걸 확인 )
SELECT * FROM member WHERE MATCH(nickname) AGAINST('+12 +23 +34 +45 +56' IN BOOLEAN MODE);