지금까지 DI가 뭔지 살펴봤습니다. 

그렇다면 실제 코드를 보면서 의존성 문제를 외부 주입으로 해결할 때 장점을 살펴봅시다. 

외부 주입없이 코드를 짠다고 했을 때 어떤 문제가 생길까요?

public class GasEngine {
    public void start() {
        System.out.println("Gas engine starts.");
    }
}

public class ElectricEngine {
    public void start() {
        System.out.println("Electric engine starts.");
    }
}

public class GasCar {
    private GasEngine engine;

    public GasCar() {
        this.engine = new GasEngine(); // GasEngine에 직접적으로 의존하고 있음
    }

    public void startEngine() {
        engine.start();
    }
}

public class ElectricCar {
    private ElectricEngine engine;

    public ElectricCar() {
        this.engine = new ElectricEngine(); // ElectricEngine에 직접적으로 의존하고 있음
    }

    public void startEngine() {
        engine.start();
    }
}

 

외부 주입을 하지 않고 엔진을 종류별로 만들려면 각자의 클래스가 무한대로 필요하게 될것입니다. 

하지만 외부주입을 할 수 있다면?

 

public interface Engine {
    void start();
}

public class GasEngine implements Engine {
    @Override
    public void start() {
        System.out.println("Gas engine starts.");
    }
}

public class ElectricEngine implements Engine {
    @Override
    public void start() {
        System.out.println("Electric engine starts.");
    }
}

public class Car {
    private Engine engine;

    public Car(Engine engine) { // DI를 통해 의존성 주입
        this.engine = engine;
    }

    public void startEngine() {
        engine.start();
    }
}

public class Main {
    public static void main(String[] args) {
        Engine gasEngine = new GasEngine();
        Car gasCar = new Car(gasEngine);
        gasCar.startEngine(); // "Gas engine starts."

        Engine electricEngine = new ElectricEngine();
        Car electricCar = new Car(electricEngine);
        electricCar.startEngine(); // "Electric engine starts."
    }
}

 

Car에 대한 Class를 하나로 해결할 수 있게됩니다. 

메인 메서드에서 필요한 엔진을 Car에 넣어서 만들 수 있게됩니다. 

무제한으로 늘어나야하는 클래스를 줄일 수 있는 것입니다. 

 

이렇게된다면 중복되는 코드도 줄일 수 있고

Car클래스를 재활용하기 때문에 코드의 효율도 높일 수 있습니다. 

만약 스마트팩토리 자동차 공장에서 DI없이 자동차를 만들어야하는 코드를 작성한다고 생각하면 개발자가 얼마나 하드코딩을 해야하는 것일까요? 하지만 우리는 DI를 통해 이 문제를 쉽게 해결할 수 있습니다. 

 

 

@Query(value = "SELECT * FROM test.books WHERE MATCH(AUTHR_NM) AGAINST (:query IN BOOLEAN MODE)", nativeQuery = true)
List<Book> findBooksByAuthor(@Param("query") String query);

불리언 모드로 검색하는 경우 많은 데이터들 중에서 양질의 결과만 불러올 수 있지만 사실 누락되는 책들이 많은 것도 사실이다. 그런데 검색어에 따라서 리턴값이 empty인 경우도 있다. 이럴때는 검색범위를 넓혀 검색결과를 보내주는 방법이 필요하다.

이 경우에 서비스단에서 처음 돌려받은 참조변수의 값을 확인하여 null인 경우 다시 정보요청을 하도록 하면 어떨까?

 

이 방법의 단점은 같은 검색을 쿼리를 두번 보내서 해야한다는 것이다.

사용자 경험을 개선하는 것과 데이터베이스의 리소스를 사용하는 것. 이 둘의 밸런스가 중요한것같다. 

 

추가 작성예정

ngram parser를 통해 한글로된 책 제목 검색속도는 엄청난 개선이 있었다.

그런데 이제 영어 제목으로 된 책 검색을 하는 경우 문제가 생긴다.

단순히 apple로만 검색해도 시간이 엄청나게 걸린다. 

 

문제상황 정의 : ngram은 토큰 사이즈가2라서 영어검색을 할때 서칭 민감도가 높아 검색 시간이 오래걸린다.

해결 방안 고민

1. 프런트나 백엔드에서 정규식으로 들어온 검색어가 영어인지 한글인지 구분한다.

2. 토큰사이즈를 4로 만들 인덱스와 ngram으로만든 인덱스를 만들어서 영어는 토큰사이즈4에서 검색하고 한글은 ngram으로 만든 인덱스에서 검색하게 한다. 

 

이 방법이 데이터를 구분해서 따로 테이블을 만드는것보다 인덱스를 만드는 것이 더 효율적인 방법이 될것같다. 기존의 500만건의 데이터를 나눈다는 것은 비용과 시간이 너무 많이 들어가는 일이다. 

테스트는 내일 한다....

도서 통합검색 API를 만드는데 인덱스를 불러오지 못하는 에러가 발생했다. 

에러 : Can't find FULLTEXT index matching the column list

분명 제목, 작가에 대한 인덱스가 각각 존재하는데 왜 불러오지 못하는걸까?

혹시 두개의 자료를 함께 묶어서 인덱스 작업을 했어야하는게 아닐까?

 

통합검색이라면 보통 포함되어야하는 것이 제목, 작가, 출판사 정도이다. 

일단 테스트를 위해 제목과 작가를 하나로 묶어 인덱스 작업을 하는 것이 가능한지 체크해보기로 했다. 

# 인덱스화 같이 만드는 것이 필요
ALTER TABLE books ADD FULLTEXT INDEX idx_authr_nm_title_nm (AUTHR_NM, TITLE_NM);

두개의 컬럼이 묶여서 인덱스가 되어있지 않으면 컬럼 단순히 더하는 방식으로는 Full-Text Search를 못하는것 같다.

 

에러 : Duplicate entry 'NULL-NULL' for key 'books.idx_authr_nm_title_nm'

FullText 인덱스에는 NULL값을 허용하지 않는다. NULL값을 가진 레코드를 인덱스에 추가하려고 할 때 오류가 발생한다고 한다.

해결방법

1. NULL값을 가진 레코드를 삭제한다.

2. 인덱스 생성시 NULL값을 제외하도록 설정한다. 

 

NULL값을 제외하기 위해 찾아보니 coalesce 함수를 사용해보기로 했다. -> 실패

 

일단 통합검색이 실행되도록 프로토타입을 만들기 위해 네이티브 쿼리로 작성해서 시간을 측정해보기로 했다. 

@Query(value = "SELECT * FROM books WHERE TITLE_NM LIKE %:query% OR AUTHR_NM LIKE %:query%", nativeQuery = true)
List<Book> findAllByAuthorAndTitle(@Param("query") String query);

책제목 : 자바 (ㅇ)

작가 : 남궁성(x)

출판사 : 믿음사(x)

 

자바는 검색결과가 나왔지만 작가와 출판사에 대한 검색결과는 나오지 않았다. 

문제점을 찾지 못해 일단은 JPQL 쿼리로 작성하여 재시도

@Query("select b from Book b where b.title like %:title% or b.author like %:title% or b.publisher like %:title%")
List<Book> findAllByTitleOrAuthorOrPublisher(@Param("query") String query);

책제목 : 자바 (ㅇ)

작가 : 남궁성(x)

출판사 : 믿음사(x)

 

JPA쿼리와 마찬가지로 JPQL역시 자바는 검색결과가 나왔지만 작가와 출판사에 대한 검색결과는 나오지 않았다. 

도대체 뭐가 문제인지 모르겠다.

 

좀 쉬다와서 다시 고민해봐야겠다. 

2023.04.12 오후 06:22

 

왜 제목만 검색되고 작가와 출판사에 대한 정보는 검색하지 못하는가?

 

TEXT관련된 검색은 Full-Text 검색으로 속도가 많이 향상되었다.

그러나 숫자로만 이루어져있는 isbn에 대한 검색이 같은 쿼리로는 검색할 수 없다.

isbn 자료는 b트리 인덱스로 만들어뒀기 때문에 JPQL로 수정하여 검색할 수 있게 하였다.

@Query("SELECT b FROM Book b WHERE b.isbn = :isbn")
List<Book> findBooksByIsbn(@Param("isbn") String isbn);

속도도 나쁘지 않게 나오는 성능을 보여준다.

문제점 : isbn에서 한글자라도 틀리거나 덜 적으면 검색결과가 나오지 않는다.

비슷한 결과라도 나올 수 있게 하는 방법은 뭐가 있을까?

일단 Like절을 활용하여 쿼리를 수정해 적용했다. 

// ISBN 검색 Like사용 (B-tree 인덱스)
@Query("SELECT b FROM Book b WHERE b.isbn LIKE :isbn")
List<Book> findBooksByIsbn(@Param("isbn") String isbn);

Like절을 사용하고나니 검색시간이 100배 이상 걸렸다.

사용자 경험을 개선하기 위해서는 isbn이 제대로 입력되지 않아도 비슷한 결과를 찾아올 수 있어야 한다.

뭐가 문제일까?

 

B-tree 인덱스는 주어진 키 값이 정확하게 일치하는 레코드를 찾는 것을 목적으로 설계되었다. 

따라서 isbn을 정확하게 검색한다면 정보를 보다 빨리 찾아올 수 있지만 일부만 일치하는 경우는 아예 검색결과가 나오지 않게 되는 것이다. 사용자 입장에서는 빠르게 찾아오는 것을 선호할까? 아니면 비슷한 숫자를 입력했을 때에도 비슷한 자료라도 보여주는 것을 선호할까?

인덱스를 사용하는것보다 일반 검색이 더 낫지 않을까?

차이가 미비하지만 검색이 빠른경우도 있고 더 느린경우도 있었다.

인덱스를 지우고 일반 쿼리를 짜서 한번 실험해보았다. 

// ISBN 검색 Like미사용 (B-tree 인덱스)
@Query("SELECT b FROM Book b WHERE b.isbn = :isbn")
List<Book> findBooksByIsbn(@Param("isbn") String isbn);

인덱스 없이 검색하는 것은 정말 오랜 시간이 걸린다.

인덱스 없이 검색하는 것은 정확히 입력하더라도 500만건의 데이터안에서 정확한 자료를 찾아오는 것은 쉽지 않은 일이었다. 

인덱스를 사용하면서도 비슷한 자료를 찾아오기 위해서는 b트리인덱스의 장점을 포기해야한다.

 

그렇다면 다른방식의 인덱싱 방법은 없을까?

1. 해시테이블 : 해시 함수를 이용하여 값을 (Key) 연결하여 저장하는 자료구조다. ISBN 해시 함수에 적용하여 해시 (Hash Value) 계산하고, 해당 해시 값에 대응하는 버킷(Bucket) ISBN 저장하는 방식이다. 

 

ngram 인덱스는 full-text검색만 가능하다

 

실험해보고 싶은것. 토큰사이즈가 크면 속도는 어떻게 변할까? 

innodb_ft_min_token_size = 4

토큰사이즈 변경 후 인덱스 다시 만들기

시간 검색 확인 

1. isbn 정확하게 입력했을 경우

@Query("SELECT b FROM Book b WHERE b.isbn = :isbn")
List<Book> findBooksByIsbn(@Param("isbn") String isbn);

이전과 비슷하다

2. Like를 사용했을 경우

// ISBN 검색 Like사용 (B-tree 인덱스)
@Query("SELECT b FROM Book b WHERE b.isbn LIKE :isbn")
List<Book> findBooksByIsbn(@Param("isbn") String isbn);

토큰 사이즈를 4로 늘리자 유의미한 속도향상이 이뤄졌다.

그렇다면 일부만 입력했을 때도 검색이 가능할까?

검색이 되지 않는다. 어째서...

제목같은 경우는 일부만 일치해도 나왔는데 isbn은 왜 안나오는걸까.

 

문제는 ISBN컬럼이 Long타입으로 되어있어서 토큰사이즈에 맞춰 인덱싱할 수 없다는 것이었다.

토큰사이즈를 4로 변경하고 익덱싱 작업을 해서 그렇게 처리 됐을것이라고 생각했는데 그게 아니었다. 

int나 Long 타입은 두가지로 인덱싱 작업이 가능하다. 

 

Long타입은 b-tree 인덱싱, Hash 인덱스 가능하다.

  • B-tree 인덱스는 범위 검색(Range scan)에 적합
  • HASH 인덱스는 등호 검색(Exact match)에 적합  : 따라서, LONG 타입의 컬럼이 등호 검색을 많이 하는 컬럼이라면, HASH 인덱스를 사용하는 것이 더 효율적일 수 있다.

검색을 위해 Long을 String으로 바꾸는 것이 가능하다.

SELECT CAST(ISBN_THIRTEEN_NO AS CHAR) AS ISBN_THIRTEEN_NO_STR FROM books;

검색에 최적화를 위해서 데이터타입을 변경하는 것은 어떨까?

단점이 있을까?

메모리 부하에 대한 문제점?

서버 사용량의 증가?

그냥 정확하게 입력하게 해서 검색하는게 나을까?

 

선택의 연속이다

 

 

테스트 ISBN 검색 자료

9791185390116

9788968306273

9788954820400

 

 

 

 

 

스레드 :

  • 스레드(thread)는 컴퓨터 프로그래밍에서 실행 중인 프로세스 내에서 실행되는 작업의 가장 작은 단위. 각각의 스레드는 프로세스 내에서 별도의 실행 경로를 가지며, 여러 개의 스레드가 동시에 실행될 수 있다.
  • 스레드는 프로그램의 성능을 향상시키는 데 중요한 역할을 한다. 예를 들어, 한 프로세스 내에서 여러 개의 스레드를 사용하면 여러 작업을 동시에 처리하거나, 한 작업을 분할하여 병렬적으로 처리함으로써 처리 속도를 높일 수 있다. 스레드를 사용하면 프로그램의 응답성도 향상다. 스레드를 사용하지 않고 하나의 실행 경로로 처리하면, 하나의 작업이 끝날 때까지 다른 작업을 처리할 수 없다. 하지만 스레드를 사용하면 여러 작업을 동시에 처리할 수 있기 때문에, 사용자 입력에 더 빠르게 응답할 수 있게된다.
  • 스레드는 프로그래밍 언어에 따라 구현 방법이 다를 있지만, 일반적으로 스레드 생성, 스레드 동기화, 스레드 스케줄링 등을 지원한다고 한다.

멀티스레드

  • 멀티 스레드(multi-thread)는 하나의 프로세스에서 여러 개의 스레드를 동시에 실행하는 것이다. 즉, 멀티 스레드 프로그래밍은 여러 개의 작업을 동시에 수행할 수 있는 병렬처리(parallel processing) 방식 중 하나.
  • 멀티 스레드를 사용하면, 프로그램이 더욱 효율적으로 동작할 수 있다. 예를 들어, 네트워크 서버 프로그램에서는, 클라이언트의 요청을 처리하는 스레드와 동시에 다른 클라이언트의 요청을 대기하는 스레드를 사용하여, 여러 클라이언트 요청을 동시에 처리할 수 있다. 이렇게 하면, 대기 시간이 줄어들고, 서버의 처리 속도와 응답성이 향상된다.
  • 멀티 스레드 프로그래밍은 스레드 간의 동기화와 관련된 문제들을 해결해야 하는 등의 어려움도 있지만, 성능과 응답성을 향상시키는 등의 장점이 있기 때문에, 현대의 프로그래밍에서는 매우 중요한 기술 하나입니다.

프로젝트 적용 방법

  • 지도에서 위치 정보를 표시하는 프로그램은 보통 많은 양의 데이터를 처리하고, 또한 사용자와의 상호작용도 처리해야 하기 때문에 멀티스레드를 활용하는 것이 필요하다.
  • 상황1 : 사용자가 지도를 확대하거나 축소하거나 이동할 때마다, 지도를 다시 그리는 작업이 필요.
    • 이 때, 이 작업을 담당하는 스레드를 따로 두면, 다른 스레드에서는 데이터를 처리하거나 사용자 입력을 처리하는 등 다른 작업을 계속할 수 있습니다. 이렇게 하면, 지도를 다시 그리는 작업 때문에 다른 작업이 느려지거나 멈추는 일이 없이, 지도 화면이 부드럽게 갱신될 수 있습니다.
  • 상황2 : 지도 데이터를 다운로드하거나 처리하는 작업 역시 많은 시간이 소요될 수 있음
    • 이러한 작업들은 별도의 스레드에서 수행함으로써, 지도를 표시하는 작업과 동시에 수행될 수 있다.
    • 이를 통해, 지도를 빠르게 표시할 수 있습니다.
  • 상황3 : 사용자의 위치와 도서관의 위치 받아와서 표시하기
    • 다수의 사용자들이 각자의 위치가 표시된 자료를 각각 다운받고 가장 가까운 도서관에 대한 위치정보도 다운받아야 한다.
    • 이때 각자의 위치를 표시하는 스레드 하나, 도서관 정보에 대한 스레드 하나 등 따로 구현하면 더 빠르게 작업할 수 있지 않을까?

 

추가 공부 스프링 부트에서 멀티스레드 사용하는 법

1. @Async 애너테이션 추가

@Service
public class MyService {

    @Async
    public void asyncMethod() {
        // 비동기적으로 실행되는 작업
    }
}

2. ThreadPoolTaskExecutor 설정

 

@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {
 
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 스레드 풀의 기본 크기 설정
        executor.setMaxPoolSize(10); // 스레드 풀의 최대 크기 설정
        executor.setQueueCapacity(25); // 작업 큐의 크기 설정
        executor.initialize();
        return executor;
    }
}
  1. @Async 애너테이션을 사용하여 MyService 클래스 내에서 asyncMethod() 메소드를 비동기적으로 실행할 있음
  2. 또한, AppConfig 클래스에서 AsyncConfigurer 인터페이스를 구현하여 getAsyncExecutor() 메소드를 오버라이딩하고, ThreadPoolTaskExecutor 반환하는 방식으로 스레드 풀을 설정할 있음

 

비동기적으로 사용한다는 것은 어떤 의미일까?

  1. 작업을 수행하는 동안 해당 작업이 완료될 때까지 다른 작업을 수행할 있는 것을 말한다. , 비동기적으로 실행된 작업은 다른 작업과 병행하여 실행될 있습니다.
    1. 예시 : 어떤 애플리케이션이 있다고 가정했을때 이 애플리케이션에서 사용자가 로그인을 하면, 데이터베이스에서 해당 사용자 정보를 가져와서 로그인 여부를 판단한다.
    2. 동기적으로 실행했을 때 :로그인 여부가 판단될 때까지 다른 작업은 수행할 없. 이는 애플리케이션의 처리 속도를 늦추고, 사용자 경험을 저하시키는 문제를 야기할 있다.
    3. 비동기적으로 실행했을 때 : 사용자 정보를 가져오는 작업이 완료될 때까지 다른 작업을 수행할 있음. 이는 애플리케이션의 처리 속도를 향상시키고, 사용자 경험을 개선하는 도움이 됨
  2. 따라서, 비동기적으로 실행하는 것은 애플리케이션의 성능을 향상시키고, 사용자 경험을 개선하는 매우 중요하다. Spring Boot에서 @Async 애너테이션을 사용하여 메소드를 비동기적으로 실행할 있으며, 이를 통해 애플리케이션의 성능을 개선할  있다.

결국 비동기적 실행은 사용자 경험을 개선하는데 매우 중요한 역할을 한다. 

사용자가 쾌적함을 느끼고 프로그램을 계속 사용하게 할 수 있다. 

 

 

 

 

 

임계값이란 무엇인가?

  • 자연어 검색 임계값(Natural Language Search Threshold) : 전문 검색 시스템에서 사용되는 설정값이다. 이 임계값은 검색 결과의 관련성(relevance)에 영향을 준다. 자연어 검색 시스템은 이 임계값을 사용하여 쿼리와 일치하는 문서의 최소 관련성 수준을 결정한다고 한다.
  • 임계값보다 낮은 관련성 점수를 가진 문서는 검색 결과에서 제외된다. 이렇게 함으로써 검색 결과의 품질을 향상시키고, 사용자에게 관련성이 높은 결과를 제공할 있다.  임계값을 설정하거나 조정함으로써 검색 결과의 정확성과 관련성을 최적화할 있다.
  • 기본 임계값은 최소4, 최대 84

임계값에 대한 고찰

  • 임계값(Threshold)이란 쉽게 말해서 어떤 기준을 말한다.
  • 특정 조건이 충족되거나 불충족되면 다른 처리가 이루어진다.
    • 예를들어 어떤 프로세스에서 데이터를 처리하는데 걸리는 시간이 일정 시간을 넘어갈 경우 경고 메세지를 출력하는 것과 같은 상황에 사용된다. 
  • MY SQL에서 임계값은 검색 결과의 관련성에 영향을 미친다. 임계값이 너무 높으면 검색 결과가 적게 나올 수 있고, 너누 낮으면 덜 관련성있는 결과가 많이 나올 수 있다. 임계값을 조절함으로써 검색 결과의 정확성과 관련성을 개선할 수 있다.
  • 데이터 검색 민감도라고 생각하면 편할것 같다. 
  • 토큰길이가 곧 임계값이라고 생각할 수 있다. 이 값에 따라 검색 인덱스와 검색 결과의 질이 달라진다. 
    • 토큰 길이가 길면 검색 정확도가 높아지지만 색인 크기와 검색 속도에 부정적인 영향을 미칠 수 있다.
    • 반면 토큰 길이가 짧으면 색인 크기와 검색 속도는 개선되지만 검색 정확도가 떨어질 수 있다.
    • 따라서 검색 결과의 질을 유지하면서 검색 속도를 높일 수 있는 임계값 설정이 중요하다

 

확인하는 방법 : 콘솔에서 아래와 같이 적는다. 

#자연어 임계값 확인
SHOW VARIABLES LIKE 'ft_min_word_len';
SHOW VARIABLES LIKE 'ft_max_word_len';

임계값 변경는 방법 : my.cnf 파일에 아래처럼 추가한다. 

ft_min_word_len = 4

변경후 SQL재실행 -> 인덱싱 재작업

 

  • parser에도 임계값이 있다. 
# 임계값 보기
SHOW VARIABLES LIKE 'innodb_ft_ngram_size';

# 임계값 변경
SET GLOBAL innodb_ft_ngram_size = 3;
SET SESSION innodb_ft_ngram_size = 3;

 

  • 전역(global) 또는 세션(session) 범위의 변수 값을 변경한다.
  • 값이 변경된 후에는 새로 인덱싱해야 설정된 값이 변경된다.
  • 기존 인덱스에는 변경되지 않으므로 다시 인덱싱 해서 인덱스를 만들어 줘야한다. 

 

검색 결과의 질을 높이려면 임계값 설정을 잘 해야한다고 생각한다. 

 

 

 

특징

특징 1. InnoDB와 MyISAM 스토리지 엔진만 Full-Text 인덱스를 지원함

특징 2. char,varchar,text 컬럼에서만 사용할 수 있음

int,Long 컬럼에서는 B-tree방식을 사용해야 함. 

#B트리 인텍스 (숫자정보는 FULLTEXT로 인덱스화할 수 없음)
CREATE INDEX idx_ISBN_THIRTEEN_NO ON test.books(ISBN_THIRTEEN_NO);

직접 테이블, 필드를 만들어 인덱스 추가하는 경우

#직접 테이블과 필드를 만들어에 FULLTEXT 인덱스를 추가
CREATE TABLE author (
                       id INT AUTO_INCREMENT PRIMARY KEY,
                       title VARCHAR(255) NOT NULL,
                       author VARCHAR(255) NOT NULL,
                       FULLTEXT (author)
) ENGINE=InnoDB;

기존에 존재하는 테이블 정보를 인덱스화 하는 경우 

alter table books add FULLTEXT index
    AUTHR_NM_IDX(AUTHR_NM) WITH PARSER ngram;

ngram parser를 원하지 않으면 with parser ngram을 빼고 실행시키면 된다.

이미 존재하는 books 테이블에 대한 인덱싱을 하게된다. 

 

특징3.  항상 전체 컬럼을 대상으로 하며, 접두사 인덱싱은 지원하지 않는다. 

 

 

 

 Full-Text Search

  • 인덱스 유형의 데이터
  • InnoDB Storage Engine 이나 MyISAM 스토리지 엔진에서만 사용 가능
  • ngram(엔그램) passer 플러그인을 제공 (2단어로 쪼개서 인덱스화) token size 2 (기본)
  • 엔그램 설정안하고 그냥 인덱싱하는 경우 token size가 보통은 3이 기본임
  • 테이블 생성시 인덱싱하여 인덱스를 저장할 수 있고 나중에 추가할 수 있음
  • CREATE TABLEALTER TABLECREATE INDEX

 

# ngram passer로 인덱싱하여 테이블에 저장
# [AUTHR_NM_IDX]이 만들어질 테이블 이름
# (AUTHR_NM) 인덱싱할 컬럼 
alter table books add FULLTEXT index
    TITLE_NM_test1(TITLE_NM) WITH PARSER ngram;
#B트리 인텍스 (숫자정보는 FULLTEXT로 인덱스화할 수 없음)
CREATE INDEX idx_ISBN_THIRTEEN_NO ON test.books(ISBN_THIRTEEN_NO);
  • 숫자로 된 자료는 B트리 인덱스로 만들어야함. ISBN이라던가 ID라던가 시퀀스 넘버는 숫자로 이루어져 있으므로 B트리 형식으로 인덱스로 만들어 검색에 활용할 수 있음
  • 대규모 데이터가 있는 기존의 테이블에 인덱스 데이터를 만드는 것보다 새로운 테이블에 인덱스를 만들어 넣고 찾아오는 것이 훨씬 빠르다. 
  •  
SELECT * FROM test.books WHERE MATCH(TITLE_NM) AGAINST ('자바의 정석' IN NATURAL LANGUAGE MODE);
  • 텍스트 검색은 : MATCH() AGAINST()구문을 사용하여 수행

 

 Full-Text Search에서 사용되는 기능 

  1. Tokenization(토큰화): 텍스트를 개별 단어, 구, 문장 등 작은 단위로 나눕니다. 이 작은 단위를 토큰이라고 합니다.
  2. Stopword Removal(불용어 제거): 검색에 도움이 되지 않는 일반적인 단어(예: "the", "and", "is" 등)를 제거합니다.
  3. Stemming(어간 추출): 단어의 원형을 찾아 검색의 정확성을 높입니다. 예를 들어, "running", "ran", "runner"와 같은 단어는 모두 "run"이라는 원형으로 처리됩니다.
  4. Ranking(랭킹): 검색 결과를 중요도 또는 관련성에 따라 정렬합니다. 이를 위해 TF-IDF(Term Frequency-Inverse Document Frequency), BM25, Cosine Similarity 등의 알고리즘을 사용할 수 있습니다.

 

검색종류 세가지

1. 자연어 검색 (Natural Language Search): 자연어 검색은 사용자가 입력한 검색어를 자연스러운 언어 형식으로 처리하여 관련 문서를 찾는 검색 방식입니다. 검색 방식은 문장 구조, 동사 형태, 단어의 의미 등을 고려하여 문서와 검색어 간의 관련성을 평가합니다. 자연어 검색은 전문 검색 기능을 사용하여 구현할 있으며, 이를 통해 키워드의 중요도, 문맥적 의미 등을 분석할 있습니다.

#자연어 검색
select *from books
where match(TITLE_NM) against ('자바' IN NATURAL LANGUAGE MODE );
  • 장점:
    • 사용자 친화적: 사용자가 일상 언어로 검색어를 입력할 수 있으며, 별도의 검색어 형식을 배울 필요가 없습니다.
    • 검색 결과의 관련성: 검색 엔진이 입력된 검색어와 관련된 문서를 찾아서 결과로 제공합니다. 이로 인해 검색 결과의 관련성이 높아집니다.
  • 단점:
    • 덜 정확한 결과: 자연어 검색은 복잡한 쿼리나 정확한 검색어 일치를 지원하지 않기 때문에, 때로는 덜 정확한 결과를 반환할 수 있습니다.
    • 성능 문제: 일반적으로 자연어 검색은 부울 검색이나 다른 검색 방법에 비해 성능이 떨어질 수 있습니다. 이는 검색어의 의미를 해석하고 관련성을 판단하는 과정이 복잡하기 때문입니다.

 

 

2. 부울 검색 (Boolean Search): 부울 검색은 논리 연산자(AND, OR, NOT) 사용하여 검색어를 조합하고, 해당 조건에 부합하는 문서를 검색하는 방식입니다. 부울 검색은 검색어 간의 관계를 명확하게 정의할 있어, 특정 조건에 따라 문서를 필터링할 있습니다. 예를 들어, "apple AND iphone" 같은 검색어를 사용하면, "apple" "iphone" 모두 포함된 문서를 검색할 있습니다.

# 자바 and 프로그래밍
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('+자바 +프로그래밍' IN BOOLEAN MODE);
# 자바 or 프로그래밍
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('자바 | 파이썬' IN BOOLEAN MODE);
# 자바 not JavaScript
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('+자바 -JavaScript' IN BOOLEAN MODE);
  • 장점:
    • 정확한 결과: 사용자가 AND, OR, NOT 등의 논리 연산자를 사용하여 검색어를 조합할 수 있어, 정확한 검색 결과를 얻을 수 있습니다.
    • 성능: 부울 검색은 상대적으로 빠른 검색 속도를 제공합니다. 이는 논리 연산자를 사용해 검색 대상을 명확하게 지정하기 때문입니다.
  • 단점:
    • 사용자 친화성이 낮음: 부울 검색을 사용하려면 논리 연산자의 사용 방법을 알아야 하며, 일반 사용자에게는 다소 어려울 수 있습니다.
    • 검색 결과의 관련성이 낮을 수 있음: 사용자가 입력한 검색어에 대한 부울 연산만 고려하기 때문에, 결과의 관련성이 자연어 검색에 비해 낮을 수 있습니다.

 

3. 쿼리 확장 검색 (Query Expansion Search): 쿼리 확장 검색은 사용자가 입력한 검색어를 기반으로 관련 단어나 구문을 자동으로 추가하여 검색 범위를 확장하는 방식입니다. 검색 방식은 동의어, 유의어, 철자 오류 등을 고려하여 검색 결과의 정확성과 완전성을 높이려고 합니다. 쿼리 확장 검색은 자동으로 관련 단어를 추출하거나, 사전에 정의된 동의어 목록을 사용하여 구현할 있습니다.

#쿼리 확장 검색
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('자바' WITH QUERY EXPANSION);

이렇게 검색하는 경우 [HY000][188] FTS query exceeds result cache limit 라는 문제가 생길 수 있다. MySQL에서 Full-Text Search 수행할 결과 캐시의 제한을 초과했다는 것을 알려줍니다. , 검색 결과가 너무 많아서 결과 캐시에 저장할 없을 정도로 메모리가 부족한 상황. 이런경우 해결방법 아래 링크 참고 

https://lookupandfly.tistory.com/4

 

#and 연산자 사용
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('+자바 +프로그래밍' IN BOOLEAN MODE);

#or 연산자 사용
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('자바 | 파이썬' IN BOOLEAN MODE);

# not 연산자 사용
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('+자바 -JavaScript' IN BOOLEAN MODE);

#Limit 절로 반환하는 데이터 제한하기
SELECT * FROM books
WHERE MATCH(TITLE_NM) AGAINST ('자바' WITH QUERY EXPANSION)
LIMIT 10;

 

  • 장점:
    • 검색 결과의 포괄성: 사용자가 입력한 검색어와 관련된 추가 검색어를 자동으로 포함하여 검색 범위를 확장합니다. 이를 통해 더 많은 관련된 문서를 검색 결과에 포함시킬 수 있습니다.
    • 검색 결과의 관련성 향상: 입력된 검색어와 관련된 추가 검색어를 자동으로 포함하여 검색 결과의 관련성을 높입니다.
  • 단점:
    • 메모리 부하: 쿼리 확장 검색의 검색 범위가 확장되면서 검색 대상이 되는 데이터의 양이 증가하기 때문에, 메모리 부하가 높을 수 있습니다. 이는 성능 저하를 초래할 수 있으므로, 쿼리 확장 검색을 사용할때는 메모리 사용량에 대한 고려가 필요합니다.
    • 덜 정확한 결과 : 쿼리 확장 검색은 사용자가 원하는 결과와 관련이 없는 추가 검색어를 포함할 수도 있습니다. 이로 인해 때로는 덜 정확한 결과를 반환 할 수 있습니다. 

 

 

참고문헌

SQL 공식문서 참고

https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html

챗GPT 자료 참고

도서 검색 시스템에서 회원가입 기능을 추가하고, 사용자의 검색 기록을 저장한 후 해당 기록을 기반으로 책을 추천하려면 다음과 같은 구성 요소와 작업이 필요하다. 

  1. 회원가입을 위한 사용자 테이블
    • 사용자 정보를 저장할 데이터베이스 테이블과 연결되는 사용자 데이터 모델생 생성
    • User 클래스
    • 필드 id, username, password, email
  2. 사용자 인증 및 회원가입 구현
    • 사용자가 회원가입하고 로그인할 수 있는 기능을 구현
    • Spring Security
  3. 검색 기록 저장을 위한 데이터 테이블 
    • SearchHistory 클래스를 생성
    • 필드 : id, userId, bookId, searchDate
  4. 검색 시 검색 기록 저장할 수 있는 테이블 
    • 사용자가 책을 검색할 때마다 검색 기록을 저장
    • 인증된 사용자가 검색을 수행하면 검색 기록을 SearchHistory 테이블에 저장하도록 설정
    <consider>
    1. 로그인한 사람과 로그인하지 않은 사람을 어떻게 구분할 것인가? (데이터 저장 유무를 결정)
    2. 최근 검색 기록은 몇개나 저장할 것인가?
    일정 기간 저장하기 : 최근 6개월 데이터를 기준으로 분석<필요한 기술 >
    1. 두 가지 모두 최근 데이터로 업데이트 되면 가장 오래된 데이터는 자동으로 삭제하는 기능 필요
    2. 검색어가 정확하지 않은 경우 수 많은 데이터들 중에서 어떤 책을 저장할 것인가?
      1. 방안1. 대여한 책을 기준으로 바꾼다면 더 정확한 정보를 제공할 수 있음. → 대여 기능을 구현해야 함
      2. 방안2. 검색한 데이터 중에서 세부 페이지에 들어가서 확인한 책만 저장하게 하기
      3. 일정 권수 저장하기 : 최근 검색한 100권을 기준으로 분석
  5. 책 추천 알고리즘 개발
    • 사용자가 검색한 책들과 관련된 주제, 장르 또는 저자를 분석
    • 알고리즘 구현 : 도서분류 번호, 최근 발행 순 으로 도서 추천 알고리즘 구현하는 것이 목표
    <consider>
    1. 이 책을 본 사람들의 선택 : 이 책을 빌린 사람들이 공통적으로 빌린 책 보여주기
    2. 핵심 키워드를 추출해서 일정 기간동안 (최근 6개월 등) 가장 많이 검색된 도서 목록 보여주기
  6. 책 추천 API 구현
    • 사용자에게 언제, 어떻게 추천 도서를 보여줄 것인가?
    • 도서추천 API 구현

 

2023.04.11 추가

+ Recent posts