Algolia API 브라우즈

Algolia API 브라우즈

인덱스에 담긴 전체 레코드를 한 번에 빠짐없이 가져와야 할 때가 있어요. 백업을 만들거나, 마이그레이션을 하거나, 분석 도구로 데이터를 옮길 때처럼요. 그럴 때 검색(search) API로는 답이 안 돼요. 검색은 쿼리에 맞는 히트(hits) 만 돌려주니까요. Algolia는 이런 '전부 내보내기' 용도로 브라우즈(browse) 라는 메서드를 따로 마련해 뒀어요. 이름 그대로 인덱스 전체를 '훑어 읽는' 작업이죠.

브라우즈도 겉보기엔 검색과 비슷해 보이지만, 목적이 확실히 달라요. 검색은 순위(relevance)와 하이라이팅이 붙은 결과를 보여주는 데 쓰고, 브라우즈는 순수하게 레코드만 돌려받는 데 써요. 오늘은 이 브라우즈를 REST API로 호출하는 방법, 페이지를 넘기는 cursor 방식, 그리고 검색과 무엇이 다른지 차례대로 살펴볼게요.

출처: 문서

본문

전체 인덱스 탐색·내보내기 — POST /1/indexes/{indexName}/browse

브라우즈는 POST /1/indexes/{indexName}/browse 엔드포인트로 호출해요. 인덱스 이름을 경로에 넣고, 요청 본문에 검색 파라미터를 담으면 돼요. 응답으로는 인덱스의 레코드가 요청당 최대 1,000개 돌아와요. 이 메서드를 쓰려면 API 키에 browse ACL 권한이 필요해요.

실제 호출은 curl로 이렇게 생겼어요. ALGOLIA_APPLICATION_IDALGOLIA_API_KEY 자리에 자기 값이 들어가야 한다는 것만 기억하면 돼요.

curl --request POST \
  --url https://algolia_application_id.algolia.net/1/indexes/ALGOLIA_INDEX_NAME/browse \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
  --header 'x-algolia-api-key: *** \
  --header 'x-algolia-application-id: ALGOLIA_APPLICATION_ID' \
  --data '
{
  "query": "",
  "similarQuery": "comedy drama crime Macy Buscemi",
  "filters": "(category:Book OR category:Ebook) AND _tags:published",
  "facetFilters": [
    [
      "category:Book",
      "category:-Movie"
    ],
    "author:John Doe"
  ],
  "optionalFilters": [
    "category:Book",
    "author:John Doe"
  ],
  "numericFilters": [
    [
      "inStock = 1",
      "deliveryDate < 1441755506"
    ],
    "price < 1000"
  ],
  "tagFilters": [
    [
      "Book",
      "Movie"
    ],
    "SciFi"
  ],
  "sumOrFiltersScores": false,
  "restrictSearchableAttributes": [
    "title",
    "author"
  ],
  "facets": [
    "*"
  ],
  "facetingAfterDistinct": false,
  "page": 0,
  "offset": 42,
  "length": 0,
  "aroundLatLng": "40.71,-74.01",
  "aroundLatLngViaIP": false,
  "aroundRadius": 1,
  "aroundPrecision": 10,
  "minimumAroundRadius": 1,
  "insideBoundingBox": "lorem",
  "insidePolygon": [
    [
      47.3165,
      4.9665,
      47.3424,
      5.0201,
      47.32,
      4.9
    ],
    [
      40.9234,
      2.1185,
      38.643,
      1.9916,
      39.2587,
      2.0104
    ]
  ],
  "naturalLanguages": [],
  "ruleContexts": [
    "mobile"
  ],
  "personalizationImpact": 100,
  "userToken": "test-user-123",
  "getRankingInfo": false,
  "synonyms": true,
  "clickAnalytics": false,
  "analytics": true,
  "analyticsTags": [],
  "percentileComputation": true,
  "enableABTest": true,
  "attributesToRetrieve": [
    "author",
    "title",
    "content"
  ],
  "ranking": [
    "typo",
    "geo",
    "words",
    "filters",
    "proximity",
    "attribute",
    "exact",
    "custom"
  ],
  "relevancyStrictness": 90,
  "attributesToHighlight": [
    "author",
    "title",
    "conten",
    "content"
  ],
  "attributesToSnippet": [
    "content:80",
    "description"
  ],
  "highlightPreTag": "<em>",
  "highlightPostTag": "</em>",
  "snippetEllipsisText": "…",
  "restrictHighlightAndSnippetArrays": false,
  "hitsPerPage": 20,
  "minWordSizefor1Typo": 4,
  "minWordSizefor2Typos": 8,
  "typoTolerance": true,
  "allowTyposOnNumericTokens": true,
  "disableTypoToleranceOnAttributes": [
    "sku"
  ],
  "ignorePlurals": [
    "ca",
    "es"
  ],
  "removeStopWords": [
    "ca",
    "es"
  ],
  "queryLanguages": [
    "es"
  ],
  "decompoundQuery": true,
  "enableRules": true,
  "enablePersonalization": false,
  "queryType": "prefixLast",
  "removeWordsIfNoResults": "firstWords",
  "mode": "keywordSearch",
  "semanticSearch": {
    "eventSources": [
      "lorem"
    ]
  },
  "advancedSyntax": false,
  "optionalWords": "lorem",
  "disableExactOnAttributes": [
    "description"
  ],
  "exactOnSingleWordQuery": "attribute",
  "alternativesAsExact": [
    "ignorePlurals",
    "singleWordSynonym"
  ],
  "advancedSyntaxFeatures": [
    "exactPhrase",
    "excludeWords"
  ],
  "distinct": 1,
  "replaceSynonymsInHighlight": false,
  "minProximity": 1,
  "responseFields": [
    "*"
  ],
  "maxValuesPerFacet": 100,
  "sortFacetValuesBy": "count",
  "attributeCriteriaComputedByMinProximity": false,
  "renderingContent": {
    "facetOrdering": {
      "facets": {
        "order": [
          "lorem"
        ]
      },
      "values": {
        "property1": {
          "order": [
            "lorem"
          ],
          "sortRemainingBy": "count",
          "hide": [
            "lorem"
          ]
        },
        "property2": {
          "order": [
            "lorem"
          ],
          "sortRemainingBy": "count",
          "hide": [
            "lorem"
          ]
        }
      }
    },
    "redirect": {
      "url": "lorem"
    },
    "widgets": {
      "banners": [
        {
          "image": {
            "urls": [
              {
                "url": "lorem"
              }
            ],
            "title": "lorem"
          },
          "link": {
            "url": "lorem"
          }
        }
      ]
    }
  },
  "enableReRanking": true,
  "reRankingApplyFilter": [
    []
  ],
  "cursor": "jMDY3M2MwM2QwMWUxMmQwYWI0ZTN"
}
'

cursor 페이지네이션

브라우즈는 한 번에 1,000개까지만 돌려주니까, 더 많은 레코드를 가져오려면 cursor 방식으로 페이지를 넘겨야 해요. 응답이 끝나지 않았다면 응답에 cursor 값이 포함돼요. 이 값을 그대로 다음 요청의 cursor 파라미터에 실어 보내면, 정확히 이어지는 다음 페이지를 받을 수 있어요.

{
  "cursor": "jMDY3M2MwM2QwMWUxMmQwYWI0ZTN"
}

핵심 규칙 두 가지만 기억하면 돼요. 첫째, cursor 파라미터 값은 반드시 이전 요청의 응답에서 받은 그 값과 일치해야 해요. 둘째, 마지막 페이지에는 cursor가 오지 않아요. 그러니까 응답에 cursor가 더 이상 없다는 건, 이제 모든 레코드를 다 훑었다는 신호예요. 클라이언트 쪽에서 그때까지 반복을 멈추면 돼요.

검색(search)과의 차이

브라우즈는 검색처럼 보이지만 목적이 달라요. 공식 문서가 명확하게 짚어 주는 차이부터 볼게요.

  • 검색은 히트(hits), 즉 하이라이팅과 랭킹 정보가 붙은 레코드를 돌려줘요. 브라우즈는 일치하는 레코드만 돌려줘요.
  • 브라우즈는 인덱스 내보내기(export) 용도로 설계됐어요.
  • 브라우즈를 쓰면 Analytics API가 데이터를 수집하지 않아요. 조회가 분석 데이터를 오염시키지 않는다는 뜻이죠.
  • 레코드는 속성(attributes)과 커스텀 랭킹으로 정렬돼요.
  • 오타 허용(typo tolerance), 매치된 단어 수, 근접성(proximity), 지리적 거리에 따른 랭킹은 하지 않아요.

그리고 브라우즈 요청에는 아래 설정이 자동으로 적용되고, 이 파라미터들을 요청에 실어 보내면 무시돼요. 내보내기라는 목적과 어울리지 않는 하이라이팅·규칙·개인화 같은 기능들이 전부 꺼지는 거예요.

advancedSyntax: false
attributesToHighlight: []
attributesToSnippet: []
distinct: false
enablePersonalization: false
enableRules: false
facets: []
getRankingInfo: false
ignorePlurals: false
optionalFilters: []
typoTolerance: true or false (min, strict는 true로 처리)

응답

브라우즈 응답은 browseResponse 스키마로 정의돼요. 검색 응답의 기반 구조(baseSearchResponse) 위에 페이지네이션 정보, 히트 목록, cursor가 붙는 형태예요. 구조를 간단히 나열하면 이렇죠.

  • hits — 요청당 돌아온 레코드 목록(최대 1,000개).
  • cursor — 다음 페이지를 가져올 커서 값. 마지막 페이지에서는 생략돼요.
  • nbHits — 일치한 레코드(히트) 수.
  • processingTimeMS — 서버가 요청을 처리하는 데 걸린 시간(밀리초).

응답의 구체적인 필드 설명은 아래 더 알아보기 링크의 전체 API 레퍼런스에서 확인할 수 있어요. 특히 입력 파라미터와 응답 필드가 궁금할 때 그쪽이 정답지예요.

더 알아보기 (Learn more)