Algolia 도입으로 firestore database 문자열 포함 검색 가능하게 만들기

개바리바리·2025년 5월 8일
post-thumbnail

배경

나는 firestore database에서 문자열 포함 단어 검색이 안된다는 것을 알게된 후
컬렉션에 있는 모든 문서를 프로젝트에 불러온 후, foreach로 불러온 문서를 순환해,
조건에 맞는 문서를 담기 위해 빈 변수를 만들고, 그 변수로 주차 정보데이터를 업데이트 했다

그러다 Algolia 를 함께 사용해보면 어떻겠냐는 감사한 댓글을 받았다 직접해보자 😍

Algolia

Algolia 는 어떻게 문자열 포함 단어 검색을 가능하게 하는걸까

Firestore는 데이터 읽기 보다 쓰기에 중점을 둔 NoSQL 문서 기반이지만,
Algolia 는 검색용으로 최적화 된 인덱스 기반이라 포함 검색과 빠른 검색이 가능하다고 한다

Algolia는 내 Firestore과 데이터 싱크를 맞추고
나는 그 Algolia 의 검색기능을 사용해 포함 검색을 진행하는 것이다

Algolia 설치

Firebase 확장 프로그램으로 Algolia를 설치하자
그전에 해야 할일로는

  1. firebase 플랜을 Blaze로 업그레이드 하기
  2. Algolia 홈페이지 회원가입

그 후에 잘 작성된 블로그를 따라오면 된다
글이 잘 작성되어 있으니 나는 따라가다 생긴 이슈들만 정리해본다

이슈 ❶

Firestore 데이터베이스 'projects/parking-management-2cba1/databases/parking-management' 가 없습니다

이건 위에 블로그가 예전 게시물이다 보니
그 당시엥 설치할 때 Database ID 항목이 없었어서 그런데

나는 프로젝트 페이지에서 직접 url을 따와 비교해본 뒤 (default) 를 입력하니 오류가 해결됐다

이슈 ❷

또 다른 항목으로 Application IDAlgolia API KEY가 연달아 나오는데

이때 홈페이지에 나란히 적힌 Application IDSearch API KEY를 보고 그대로 적기 쉽다
그럼 안된다 Algolia API KEYAdmin API Key 를 적어야한다
(Admin API Key 는 홈페이지 Settings > API Keys 항목에서 찾을 수 있다)

설치 완료

그렇게 Step 5까지 따라오면 데이터 동기화가 완료된것이다

그래서 나는 아래 명령어를 통해 테스트 앱을 진행해봤다

테스트 앱을 실행해보니 처음에 전체가 나온 후 실시간 필터링되는 식으로 구현이 됐다
이렇게 되면 검색하기도 전에 다른 주차되어있는 차량 정보들 까지 나온다는건데
이 방법은 위험하다 여기는 실시간 검색만 있는건가 챗 지피티에게 물어봤다

다행이다 쿼리 요청을 해서 받아오는 방법이 있었다
공식문서를 찾은 후 프로젝트에 적용해보기. 나는 가장 최신 버전을 이용했다

이슈 ❶

근데 자꾸 틀린 번호 '2223'를 입력해도 비슷한 답 '2222'이 나오는 이슈 발견

이건 Algolia의 기본적으로 세팅되어 있는 오타 허용 기능 때문이라고 한다
문서를 확인한 후 오타 허용 기능을 꺼줬다

const { results } = await client.search({
	requests: [
    	{
        	indexName: "parking_records",
            query: inputValue,
            typoTolerance: false, // 해당 코드 추가 
            hitsPerPage: 1,
        },
	],
});

이슈 ❷

그렇게 받아온 results 한번 구조를 살펴보자
내가 원하는 정보는 car_number, imgUrl, timestamp 이며 그 정보는 results[0].hits[0] 에 담겨있다

results = [
  {
    exhaustive: { nbHits: true },
    exhaustiveNbHits: true,
    hits: [
      {
        path: "parking_records/stleQ7ub75uN7LABXcrj",
        car_number: "22바2222",
        imgUrl: "https://firebasestorage.googleapis.com/v0/b/parkin…",
        timestamp: "2025-02-13 19:35:13",
        // ..추가 필드
      }
    ],
    hitsPerPage: 1,
    index: "parking_records",
    nbHits: 1,
    // ..추가 필드
  }
]

그래서 results[0].hits[0]을 불러오는데 자꾸 hits 부분에 빨간 밑줄이 생긴다

그리고 생긴 오류메시지 이건 무슨뜻일까
Property 'hits' does not exist on type 'SearchResult'.
Property 'hits' does not exist on type 'SearchForFacetValuesResponse'.

이건 타입스크립트에서 모든 타입을 알지 못해서 생긴 오류라고 한다
이럴수도 있구나 난 당연하게 타입스크립트는 모든 타입을 알고 있을거라 생각했다
암튼 오류메시지를 검색하고 관련 글을 찾아 참고해 코드를 적어봤다

results 의 타입을 지정해주고 구조분해로 hits를 받아와 return 하기

async function getParkingInfo(inputValue: string) {
	const { results } = await client.search({
		requests: [
			{
  				indexName: "parking_records",
  				query: inputValue,
  				typoTolerance: false,
  			},
  		],
  	});
  	const { hits } = results[0] as SearchResponse;
  	return hits;
}

이렇게 하면 원하는 정보에 접근 가능하게 된다

이슈 ❸

주차 정보를 업데이트 하는데도 여기서도 타입 문제가 생겼다
Type 'unknown' is not assignable to type 'string'.
이건 간단히 as string으로 해결했다

const results = await getParkingInfo(inputValue);

if (results.length == 0) {
	setInputErrorMessage("등록되지않은 차량입니다");
  	return;
} else {
  	setParkingInfo({
  		car_number: results[0]["car_number"] as string,
  		imgUrl: results[0]["imgUrl"] as string,
  		timestamp: results[0]["timestamp"] as string,
  	});
}

드디어 Algolia 도입으로 문자열 포함 단어 검색을 구현해냈다

Algolia 도입 후 비교

먼저 코드 줄 수 비교를 해보면 25줄이 지워지고 28줄을 추가했다
하지만 내 체감상 Algolia 도입 후 코드가 더 간단한 느낌

// 전 (Firestore Database)
async function getParkingInfo(inputValue: string) {
	const q = query(collection(db, "parking_records"));
    const querySnapshot = await getDocs(q);

	let findInputValue: ParkingData | undefined;

	querySnapshot.forEach((doc) => {
    const data = doc.data() as Data;
    	if (data.car_number.includes(inputValue)) {
        	findInputValue = { id: doc.id, data };
        }
    });
	return findInputValue;
}


// 후 (Algolia)
async function getParkingInfo(inputValue: string) {
	const { results } = await client.search({
    	requests: [
        	{
            	indexName: "parking_records",
                query: inputValue,
                typoTolerance: false,
            },
        ],
	});
    const { hits } = results[0] as SearchResponse;
    return hits;
}

전 코드를 보면 네트워크 요청하는데 왠 반복문이 들어가나 싶은데
후 코드를 보면 쿼리문만 적혀있어서 뭘 하고자 하는게 바로 보여서 그렇게 느끼나보다

마무리

확실히 알았다 내가 원하는 데이터를 딱 가져오려면 쿼리문을 건드려야 한다
앞으로 어떤 DB를 사용해도 데이터 접근에는 바로 쿼리문으로 접근할것같다
이렇게 한단계 또 배웠다

profile
삽질한만큼 내 땅

1개의 댓글