2022-08-06-TIL
2022-08-06-TIL
Today I Learned
MySQL 전문검색
MySQL의 전문 검색(Full-Text Search)은 CHAR, VARCHAR, TEXT 컬럼에 FULLTEXT 인덱스를 만들고 MATCH (컬럼들) AGAINST ('검색어' 모드)로 질의하는 기능이다. InnoDB와 MyISAM에서 쓸 수 있다. 검색 모드는 세 가지다.
- 자연어 모드(기본값): 검색어를 자연어 문장으로 해석해서 관련도 점수를 계산한다.
- 불리언 모드:
+단어(반드시 포함),-단어(제외),"구문"같은 연산자로 조건을 건다. - 쿼리 확장 모드: 한 번 검색한 뒤 관련도가 높은 행의 단어를 검색어에 더해 다시 검색한다.
MATCH()의 검색어는 쿼리 실행 중 상수여야 해서, 다른 테이블의 컬럼 값을 검색어로 쓸 수는 없다. SELECT MATCH(...) AGAINST(...)처럼 SELECT 절에 쓰면 행마다 관련도 점수를 돌려준다(MySQL 8.0: Full-Text Search Functions).
한국어에서는 파서가 문제다. 기본 파서는 공백으로 단어를 나누는데, 한국어는 조사가 붙어 “검색엔진을”과 “검색엔진”이 다른 단어가 된다. MySQL 5.7부터 제공하는 ngram 파서는 텍스트를 n글자 단위로 잘라 색인한다. 기본 ngram_token_size는 2(bigram)이고 1부터 10까지 설정할 수 있다. 자연어 모드에서는 검색어가 ngram들의 합집합으로, 불리언 모드에서는 ngram 구문 검색으로 바뀐다(MySQL 8.0: ngram Full-Text Parser).
1
2
3
4
5
ALTER TABLE article ADD FULLTEXT INDEX ft_title_body (title, body) WITH PARSER ngram;
SELECT id, title
FROM article
WHERE MATCH (title, body) AGAINST ('검색엔진' IN BOOLEAN MODE);
- https://gngsn.tistory.com/162
- https://gywn.net/2017/04/mysql_57-ngram-ft-se/
- https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html#:~:text=MATCH()%20takes%20a%20comma,is%20constant%20during%20query%20evaluation.
- https://stackoverflow.com/questions/13893737/what-does-select-match-mean
@Transactional
Spring의 @Transactional은 프록시가 메서드 호출 전후로 트랜잭션을 시작하고 커밋·롤백하는 선언적 트랜잭션이다. 기본 전파 속성은 REQUIRED로, 이미 진행 중인 트랜잭션이 있으면 참여하고 없으면 새로 시작한다. 같은 물리 트랜잭션에 참여한 안쪽 메서드가 롤백 전용으로 표시하면, 바깥쪽이 커밋을 시도할 때 UnexpectedRollbackException이 난다. REQUIRES_NEW는 항상 별도 물리 트랜잭션을 열어서 바깥 트랜잭션이 커넥션을 쥔 채로 커넥션을 하나 더 쓰므로, 커넥션 풀이 작으면 고갈될 수 있다(Spring Framework: Transaction Propagation).
격리 수준은 DB가 동시에 실행되는 트랜잭션끼리 얼마나 서로를 보지 않게 할지를 정한다. MySQL InnoDB의 기본값은 REPEATABLE READ다(MySQL 8.0: Transaction Isolation Levels).
CockroachDB 블로그 글은 Stanford의 Warszawski와 Bailis가 SIGMOD 2017에 발표한 ACIDRain 연구를 소개한다. 12개 이커머스 애플리케이션에서 트랜잭션 관련 버그 22개를 찾았고, 그중 5개 애플리케이션에서는 결제 중에 다른 탭에서 장바구니에 상품을 추가하면 그 상품이 무료로 주문에 포함될 수 있었다고 한다. 약한 격리 수준을 보완하려고 SELECT ... FOR UPDATE를 쓴 애플리케이션 중 올바르게 쓴 곳은 3분의 1뿐이었다는 결과를 근거로, 글은 SERIALIZABLE을 기본으로 쓰자고 주장한다. 이것은 CockroachDB의 입장이고, 성능과의 균형은 서비스마다 판단할 문제다.
- https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth
- https://nesoy.github.io/articles/2019-05/Database-Transaction-isolation
- https://www.cockroachlabs.com/blog/acid-rain/
MySQL 데이터를 Elasticsearch 검색
Logstash JDBC input으로 MySQL을 주기적으로 조회해 Elasticsearch에 색인하는 방법은 7월 18일 TIL에 정리했다. 전문 검색을 MySQL ngram 인덱스로 할지 Elasticsearch로 옮길지는 데이터 규모, 형태소 분석 필요 여부, 동기화 지연을 얼마나 허용할 수 있는지로 판단한다.
- https://velog.io/@broccoliindb/mysql-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-elasticsearch%EB%A1%9C-%EA%B2%80%EC%83%89%ED%95%98%EA%B8%B0
- https://cyworld.tistory.com/815
톰캣 커넥션 풀
Tomcat JDBC Connection Pool의 주요 속성을 정리한 글이다. 문서 기준으로 각 속성의 의미는 다음과 같다(Tomcat 9: The Tomcat JDBC Connection Pool).
initialSize: 풀 시작 시 만드는 커넥션 수maxActive: 동시에 빌려줄 수 있는 최대 커넥션 수maxIdle,minIdle: 풀에 유지할 유휴 커넥션의 상한과 하한maxWait: 커넥션이 없을 때 예외를 던지기 전까지 기다리는 시간(ms)testOnBorrow,testWhileIdle,validationQuery: 빌려줄 때 또는 유휴 상태에서 커넥션이 살아 있는지 검사하는 설정validationInterval: 검사를 너무 자주 하지 않도록 이 간격 안에 검사한 커넥션은 다시 검사하지 않는다removeAbandoned,removeAbandonedTimeout: 애플리케이션이 반납하지 않은 커넥션을 회수하는 설정
DB나 방화벽이 유휴 커넥션을 먼저 끊는 환경이라면 유휴 커넥션 검사를 켜 두어야, 끊긴 커넥션을 빌려 쓰다 에러가 나는 일을 줄일 수 있다.
- https://earth-95.tistory.com/137
Kafka Exactly-once
Kafka 문서는 메시지 전달 보장을 at-most-once(유실 가능, 중복 없음), at-least-once(유실 없음, 중복 가능), exactly-once 세 가지로 설명한다. 0.11.0.0 이전에는 프로듀서가 응답을 못 받으면 재전송할 수밖에 없어서 at-least-once였다. 0.11.0.0부터 멱등 프로듀서가 생겨 브로커가 프로듀서 ID와 시퀀스 번호로 중복 쓰기를 걸러 내고, 여러 파티션에 원자적으로 쓰는 트랜잭션도 지원한다. 컨슈머의 오프셋도 토픽에 저장되므로, 처리 결과와 오프셋 커밋을 같은 트랜잭션에 묶으면 Kafka 토픽에서 읽어 Kafka 토픽으로 쓰는 처리에서 exactly-once를 얻는다. 이때 읽는 쪽은 isolation.level=read_committed여야 중단된 트랜잭션의 메시지를 보지 않는다(Kafka 3.2 Design: Message Delivery Semantics).
- https://www.joinc.co.kr/w/man/12/Kafka/exactlyonce
Kafka Outbox Pattern
DB 업데이트와 Kafka 메시지 발행을 함께 해야 할 때, 둘은 서로 다른 시스템이라 한쪽만 성공할 수 있다. 트랜잭셔널 아웃박스 패턴은 메시지를 바로 발행하지 않고, 비즈니스 데이터를 바꾸는 같은 DB 트랜잭션 안에서 outbox 테이블에 저장한다. 그 뒤 별도의 릴레이가 outbox를 읽어 브로커로 발행한다. 릴레이 방식에는 outbox를 주기적으로 조회하는 polling publisher와, DB 트랜잭션 로그를 읽는 transaction log tailing(Debezium 같은 CDC)이 있다.
DB 커밋이 된 경우에만 메시지가 나간다는 것이 보장되지만, 릴레이가 발행 후 기록 전에 죽으면 같은 메시지를 다시 보낼 수 있다. 그래서 컨슈머는 멱등하게 처리해야 한다(microservices.io: Transactional outbox).
- https://medium.com/contino-engineering/publishing-events-to-kafka-using-a-outbox-pattern-867a48e29d35
- https://dzone.com/articles/implementing-the-outbox-pattern
댓글
아직 댓글이 없습니다