
안녕하세요! 에이플랫폼 Support Bulletin의 열일곱 번째 이야기입니다. 😊
데이터베이스에서 빠르고 유연한 텍스트 검색 기능을 구현하는 것, 많은 분들이 고민하는 지점일 텐데요. SingleStore의 Full Text Search 2(FTS2)는 이런 강력한 전문 검색 기능을 제공하는 핵심 기술입니다.
그런데 막상 FTS2를 도입하려고 할 때 "왜 제대로 동작하지 않지?"라며 예상치 못한 난관에 부딪히는 경우가 종종 있습니다. 😥
최소 요구 조건을 충족하지 못했거나, 일부 하위 버전 환경에서는 FTS기능이 안정화 되지 않았습니다. 그리고 특정 조건에서 인덱스가 생성되지 못하는 'Broken Index' 문제가 발생하기도 합니다.
이번 Support Bulletin에서는 저희가 직접 경험하고 해결했던 FTS2 관련 이슈들을 공유합니다.
FTS2 도입 전 꼭 확인해야 할 최소 사양부터, 기능이 안정화된 권장 버전 안내, 그리고 Broken Index를 진단하고 복구하는 방법까지 실용적인 팁들을 자세히 살펴보겠습니다.
FTS2 사용을 위한 최소 조건
SingleStore의 FTS2 기능을 사용하기 위해서는 몇 가지 전제 조건이 충족되어야 합니다.
하나라도 누락되면 기능이 정상적으로 동작하지 않거나, 뒤에서 다룰 노드 재시작 지연과 같은 예기치 않은 문제가 발생할 수 있습니다.
💡FTS2 도입 전, 다음 사항들을 반드시 확인하세요.
1. 서버 버전 ✅
SingleStore 서버 버전이 8.9.25 이상이어야 합니다.
이유는 다음 섹션에서 자세히 설명하겠지만, 이전 버전에서는 노드 재시작 시 지연이 발생할 수 있습니다.
2. Java 설치 ✅
모든 노드(Aggregator 및 Leaf)에 Java 21 이상이 설치되어 있어야 합니다.
FTS2는 내부적으로 Java 기반의 Lucene 엔진을 사용하기 때문입니다.
3. 엔진 변수 설정✅
각 노드에서 FTS2가 사용할 Java 경로를 지정하는 엔진 변수를 설정해야 합니다
- fts2_java_home: $JAVA_HOME 환경 변수에 저장된 Java 설치의 최상위 디렉터리 경로
- fts2_java_path: java 실행 파일(java)의 전체 경로 (일반적으로 $JAVA_HOME/bin/java)
[중요] 동기화 변수가 아닙니다
이 두 변수는 동기화 변수(sync variable)가 아닙니다.
반드시 FTS2를 사용하는 모든 노드에서 개별적으로 설정해야 합니다.
설정 예시
모든 노드의 java 설치 경로가 동일하다면, sdb-admin update-config 명령어의 '-a' 옵션으로 일괄 설정할 수 있습니다.
# --all 옵션으로 모든 노드에 설정
# 1. fts2_java_home 설정
sdb-admin update-config --all --set-global \
--key "fts2_java_home" \
--value "/usr/lib/jvm/jre-21-openjdk" -y
# 2. fts2_java_path 설정
sdb-admin update-config --all --set-global \
--key "fts2_java_path" \
--value "/usr/lib/jvm/jre-21-openjdk/bin/java" -y
# 3. 설정 적용을 위해 모든 노드 재시작
sdb-admin restart-node -a -y
노드마다 Java 경로가 다르다면 --all 옵션 대신 각 노드 ID를 지정하여 개별적으로 설정해야 합니다.
4. 기타 제약 사항
- FTS2 인덱스는 Columnstore 테이블에서만 지원됩니다.
- 테이블당 하나의 Full-Text 인덱스만 생성할 수 있습니다.
FTS2 안정화 버전 안내
앞서 최소 조건에서 서버 버전을 8.9.25 이상으로 강조한 이유는, 제가 이전 버전에서 노드 재시작 지연 경험했기 때문입니다.
문제 현상
- 발생 버전: 8.9.25 미만
- 핵심 원인: FTS2를 사용하는 노드가 종료될 때, /tmp 디렉터리에 생성되었던 FTS Java 소켓(socket) 파일이 정상적으로 정리(close)되지 않는 버그
- 장애 증상: 노드가 비정상 종료되거나 재시작을 시도할 때, 이전에 사용했던 소켓 파일이 /tmp에 그대로 남아있습니다. 노드는 이를 '이미 사용 중인 소켓'으로 인식하여 정상적으로 시작하지 못하고 Startup 과정에서 지연이 발생합니다.
해결 및 권고
이 문제는 SingleStore 8.9.25 버전에서 공식적으로 해결되었습니다.
Bugfix: Fixed a potential replay failure on a table that uses full-text version 2.
[Release Note 8.9.25]
FTS2는 내부적으로 Java 프로세스와 통신하며 동작하기 때문에, 소켓 처리가 매우 중요합니다.
FTS2의 안정적인 운영을 위해서는 반드시 8.9.25 이상의 버전을 사용할 것을 권장합니다.
FTS2 Broken Index 진단 및 복구
FTS2를 안정적으로 도입하는 것도 중요하지만, 문제가 발생했을 때 빠르게 진단하고 복구하는 것은 더욱 중요합니다.
FTS2 인덱스는 특정 조건에서 'Broken' 상태가 될 수 있는데, 저는 FTS2 토큰의 최대 길이(32,766 bytes) 제한을 초과하는 데이터를 인덱싱하려 할 때 이 현상을 재현할 수 있었습니다.
사전 정보: FTS2의 주요 특징
- FTS2 인덱스는 Columnstore 테이블의 세그먼트(segment) 단위로 생성됩니다 (세그먼트당 1개).
- FTS2가 처리하는 토큰의 최대 길이는 32,766 bytes로 고정되어 있으며 이 값은 수정할 수 없습니다.
Broken Index 발생 재현
이 최대 길이 제한을 테스트하기 위해 32,767 bytes(제한 + 1 byte)의 데이터를 테이블에 삽입하고 인덱싱(Flush)을 시도했습니다.
-- 테이블 생성 예시
CREATE TABLE IF NOT EXISTS tb1 (
c1 INT DEFAULT NULL,
c2 mediumtext,
FULLTEXT USING VERSION 2 KEY idx_fts_c2 (c2)
INDEX_OPTIONS '{
"analyzer": {
"custom": {
"tokenizer": {
"korean": {}
},
"token_filters": [
"korean_reading_form",
"korean_number"
]
}
}
}'
);
-- 32,766 bytes를 초과하는 데이터 삽입
insert into tb1 values ('0', LPAD('', 32767, '1'));
-- 세그먼트를 닫고 FTS2 인덱싱 시도
OPTIMIZE TABLE tb1 FLUSH;
-- 에러 예시
ERROR 2578 (HY000): Leaf Error (rocky1:3307): Document contains at least one immense term
in field="c2" (whose UTF8 encoding is longer than the max length 32766), all of which were
skipped. Please correct the analyzer to not produce such terms.
The prefix of the first immense term is: '[49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49]...',
original message: bytes can be at most 32766 in length; got 32767
오류가 발생했더라도 데이터 세그먼트 자체는 생성됩니다. 하지만 FTS2 인덱스의 상태는 어떨까요?
Broken Index 진단 방법
information_schema.MV_FULLTEXT_INDEXES 뷰를 통해 인덱스의 상태를 확인할 수 있습니다.
-- 'Broken' 상태(broken=1)인 인덱스 조회
SELECT *
FROM information_schema.MV_FULLTEXT_INDEXES
WHERE broken;
조회 결과
+---------------+------------+------------+---------+-----------+--------+
| DATABASE_NAME | TABLE_NAME | SEGMENT_ID | NODE_ID | PARTITION | BROKEN |
+---------------+------------+------------+---------+-----------+--------+
| test | tb1 | 1 | 2 | 1 | 1 |
+---------------+------------+------------+---------+-----------+--------+
1 row in set (0.19 sec)
특정 세그먼트의 BROKEN 컬럼이 1로 설정된 것을 확인할 수 있습니다.
이 상태에서 FTS2 인덱스를 사용하는 쿼리(MATCH AGAINST, BM25 등)를 실행하면, SingleStore는 이 '깨진' 인덱스를 처리하지 못하고 쿼리를 실패시킵니다.
데이터 저장과 세그먼트 생성은 정상적으로 가능합니다. 하지만 Broken 세그먼트를를 조회 시 애플리케이션 에러가 발생할 수 있습니다. 반대로 에러를 피하기 위해 FTS 인덱스를 사용하지 않도록 설정하면 검색 성능에 문제가 생길 수 있습니다.
SELECT * FROM tb1 WHERE c2 like '1%'; -- 인덱스 사용 없이 조회
-- 결과 예시
-- FTS2 인덱스를 사용하지 않을 경우, 정상적으로 조회 가능
|c1 |c2 |
|---|-------|
|0 |11...11|
SELECT * FROM tb1 WHERE MATCH (TABLE tb1) AGAINST ('c2:1*'); -- 인덱스 사용하여 조회
-- 쿼리 실행 시 오류 발생
ERROR 2925 (HY000): Leaf Error (rocky1:3307): The full-text index on segment 1 is
incomplete and needs to be repaired.
Please run the following command to attempt to rebuild it,
and then retry your query: OPTIMIZE TABLE `test`.`tb1` FIX_FULLTEXT
Broken Index 복구 시도 (및 실패)
SingleStore는 오류 메시지에서 안내하는 것처럼, Broken 인덱스를 수동으로 복구할 수 있는 명령어를 제공합니다.
OPTIMIZE TABLE tb1 FIX_FULLTEXT;
--에러 예시
ERROR 2578 (HY000): Leaf Error (rocky1:3307): Document contains at least one
immense term in field="c2" (whose UTF8 encoding is longer than the max length 32766),
all of which were skipped. Please correct the analyzer to not produce such terms.
The prefix of the first immense term is: '[49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49]...',
original message: bytes can be at most 32766 in length; got 32767
하지만 근본 원인(32,766 bytes를 초과하는 원본 데이터)이 세그먼트 내에 여전히 존재하기 때문에, 인덱스를 재구축하려다 또다시 동일한 오류를 발생시키며 복구에 실패합니다. 😥
"복구 불가능한" Broken Index 조치 방법
FIX_FULLTEXT 명령으로도 복구가 불가능할 때, 다음 두 가지 방법을 고려할 수 있습니다.
방법 1: 문제 Row 식별 및 데이터 수정/삭제 (권장)
가장 좋은 방법은 문제가 되는 데이터를 찾아 수정하거나 삭제하는 것입니다. MV_FULLTEXT_INDEXES와 COLUMNAR_SEGMENTS 뷰를 조인하여 문제가 발생한 세그먼트의 메타 정보(예: min_value, max_value)를 확인하고, 이를 토대로 문제 데이터를 추정할 수 있습니다.
-- Broken 인덱스가 속한 세그먼트의 메타 정보 확인
WITH ftsidx AS (
SELECT *
FROM information_schema.MV_FULLTEXT_INDEXES
WHERE broken
)
SELECT *
FROM information_schema.COLUMNAR_SEGMENTS AS a
JOIN ftsidx AS b
ON a.database_name = b.database_name
AND a.segment_id = b.segment_id
AND a.table_name = b.table_name
AND a.node_id = b.node_id
AND a.column_name = 'c2'; -- FTS2 인덱스 대상 컬럼
-- 결과 예시
+------+-----+-----+--------+--------+-----------------+--------+-------+------+-----------+-----------+---------------------+--------+
| DB | TBL | COL | SEG_ID | COL_ID | FILE | HOST | PORT | ROWS | MIN_VALUE | MAX_VALUE | TIME | BROKEN |
+------+-----+-----+--------+--------+-----------------+--------+-------+------+-----------+-----------+---------------------+--------+
| test | tb1 | c2 | 1 | 3 | blobs/.../17_24 | rocky1 | 3,307 | 1 | 111...111 | 111...111 | 2025-11-28 10:51:46 | 1 |
+------+-----+-----+--------+--------+-----------------+--------+-------+------+-----------+-----------+---------------------+--------+
[참고] 현재 버전(9.0.10) 기준으로, Broken Row를 정확히 특정하는 기능은 제공되지 않아, 세그먼트의 메타 정보를 기반으로 추정해야 합니다.
문제가 되는 데이터를 수정한 뒤, 다시 OPTIMIZE TABLE tb1 FIX_FULLTEXT;를 실행하면 해당 세그먼트의 인덱스만 정상적으로 재생성됩니다.
방법 2: FTS2 인덱스 전체 삭제 및 재생성 (예방 조치 포함)
문제 데이터 식별이 어렵거나 테이블 전체의 인덱스를 재생성해도 무방한 경우, 인덱스를 삭제하고 다시 만드는 방법이 있습니다.
이때 향후 동일한 문제가 발생하지 않도록 예방 조치를 추가하는 것이 중요합니다. 인덱스 옵션에 truncate_token 필터를 추가하면, 최대 길이를 초과하는 토큰을 자동으로 잘라내어 인덱싱 오류를 방지할 수 있습니다.
-- 1. 기존 인덱스 삭제
ALTER TABLE tb1 DROP INDEX idx_fts_c2;
-- 2. truncate_token 필터를 추가하여 인덱스 재생성
ALTER TABLE tb1
ADD FULLTEXT USING VERSION 2 idx_fts_c2 (c2)
INDEX_OPTIONS='{
"analyzer": {
"custom": {
"tokenizer": {
"korean": {}
},
"token_filters": [
"korean_number",
{
"truncate_token": {
"prefixLength": 32000
}
}
]
}
}
}';
-- 3. (필요시) 전체 인덱스 빌드
OPTIMIZE TABLE tb1 FIX_FULLTEXT;
truncate_token 옵션을 적용하면, 32,766 bytes를 초과하는 비정상적인 데이터가 유입되더라도 인덱싱 오류(Broken Index) 없이 FTS2 기능을 안정적으로 사용할 수 있습니다.

이번 Support Bulletin에서는 SingleStore의 강력한 전문 검색 기능인 FTS2를 도입하고 운영하는 과정에서 마주칠 수 있는 세 가지 핵심 이슈를 다뤄보았습니다.
- FTS2 사용 조건: Java 21 이상 설치 및 정확한 엔진 변수 설정
- 안정화 버전: 8.9.25 미만 버전에서 발생하는 소켓 정리 문제와 버전 업그레이드의 중요성
- Broken Index: 최대 토큰 길이 초과로 인한 인덱스 손상 진단 및 복구 방법
FTS2는 강력한 기능인 만큼, 내부 동작 원리와 제약 사항을 명확히 이해하고 준비할 때 비로소 그 성능을 안정적으로 활용할 수 있습니다. 특히 Broken Index는 데이터 유입 시 언제든 발생할 수 있으므로, truncate_token 필터 적용과 같은 예방 조치를 통해 장애 상황을 미리 방지하는 것이 중요합니다.
저희가 겪었던 시행착오와 해결 경험이 FTS2를 도입하거나 운영 중인 많은 분께 실질적인 도움이 되기를 바랍니다.
앞으로도 에이플랫폼 Support Bulletin은 여러분이 마주하는 복잡한 기술 이슈를 함께 고민하고 해결해 나가는 유용한 정보로 계속 찾아뵙겠습니다. 감사합니다! 😊
'SingleStoreDB > Support Bulletin' 카테고리의 다른 글
| SingleStore vs PostgreSQL: 이스케이프 문자 처리 방식 차이 - [Support Bulletin 16] (0) | 2025.10.27 |
|---|---|
| MinIO 설치부터 SingleStore USD 연동까지 - [Support Bulletin 15] (0) | 2025.09.09 |
| HikariCP, SingleStore 연동 테스트 - [Support Bulletin 14] (0) | 2025.08.21 |
| SingleStore, 유휴 세션으로 인한 데이터베이스 성능 저하 해결하기 - [Support Bulletin 13] (0) | 2025.08.19 |
| SingleStore에서 secure_file_priv 설정 방법 및 NULL값의 보안 위험 - [Support Bulletin 12] (4) | 2025.07.18 |




