λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
[개발] Practice/Node.js MongoDB

[Node.js / MongoDB] 검색 κΈ°λŠ₯ λ§Œλ“€κΈ°

by Connecting-the-dots 2022. 3. 14.
728x90
λ°˜μ‘ν˜•

πŸ’‘ μ‹€μŠ΅ 포인트!

  • MongoDB 의 indexing 을 μ΄μš©ν•œ 검색 κΈ°λŠ₯을 λ§Œλ“€μ–΄λ³΄μ•˜λ‹€.
  • λŒ€λΆ€λΆ„μ˜ DB 듀은 주둜 μ˜μ–΄μ— μ΅œμ ν™”λœ 검색 κΈ°λŠ₯을 μ œκ³΅ν•˜κ³  μžˆμ–΄, ν•œκΈ€μ— μ΅œμ ν™”λœ κΈ°λŠ₯을 λ§Œλ“œλŠ” 게 쉽지 μ•ŠμŒμ„ μ•Œκ²Œ λ˜μ—ˆλ‹€.
  • λΈ”λ‘œκ·Έλ₯Ό μ“°λŠ” ν˜„μž¬μ—λ„ 검색 κΈ°λŠ₯은 λ‚΄κ°€ μƒκ°ν•œ μˆ˜μ€€μ—λŠ” λ„λ‹¬ν•˜μ§€ λͺ»ν–ˆλ‹€. (μ΄λŠ” μΆ”ν›„ 쑰금 더 고민해봐야 ν•  뢀뢄이닀.)
    • ν•œκΈ€μ— μ΄ˆμ μ„ λ§žμΆ”μ—ˆλ”λ‹ˆ μ˜μ–΄λŠ” 일뢀 λ‚΄μš©λ§Œ κ²€μƒ‰ν•˜λŠ” 경우 검색이 λ˜μ§€ μ•ŠλŠ”λ‹€.
    • ν•œκΈ€μ„ ν•œ κΈ€μžλ§Œ 가지고 κ²€μƒ‰ν•˜λŠ” 경우 검색이 λ˜μ§€ μ•ŠλŠ”λ‹€.

πŸ’œ URL Query string

🀍 κ²€μƒ‰μ°½ UI λ§Œλ“€κΈ°

<div class="container input-group mb-1">
  <input class="form-control" id="search-input">
  <button class="input-group-append btn btn-danger" id="search">검색</button>
</div>

 

  • 기쑴의 할일 리슀트 νŽ˜μ΄μ§€ HTML νŒŒμΌμ— Bootstrap 을 μ΄μš©ν•˜μ—¬, 검색창 λ ˆμ΄μ•„μ›ƒμ„ μΆ”κ°€ν•΄μ£Όμ—ˆλ‹€.
  • 후에 κ²€μƒ‰ν–ˆμ„ λ•Œ λ³΄μ΄λŠ” 화면도 λ™μΌν•œ λ ˆμ΄μ•„μ›ƒμ΄ ν•„μš”ν•˜λ―€λ‘œ λ‚΄μš©μ„ κ·ΈλŒ€λ‘œ λ³΅μ‚¬ν•΄μ„œ search λΌλŠ” 파일λͺ…을 가진 EJS νŒŒμΌμ„ λ§Œλ“€μ–΄μ£Όμ—ˆλ‹€.
  • JavaScript μ½”λ“œ μž‘μ„±μ‹œ μš©μ΄ν•˜κ²Œ ν•˜κΈ° μœ„ν•˜μ—¬ input νƒœκ·Έμ™€ button νƒœκ·Έμ— id λ₯Ό 각각 λΆ€μ—¬ν•΄μ£Όμ—ˆλ‹€.

🀍 GET μš”μ²­μœΌλ‘œ μ„œλ²„μ— 데이터 보내기

  • μ„œλ²„μ— 데이터λ₯Ό 보낼 λ•ŒλŠ” POST μš”μ²­μ„ μ‚¬μš©ν•˜λ©΄ 되고, 이 경우 req.body 와 같은 ν˜•νƒœλ‘œ κΊΌλ‚΄μ–΄ μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.
  • 이외에 GET μš”μ²­μœΌλ‘œλ„ URL 뒀에 데이터λ₯Ό μ‹¬λŠ” λ°©μ‹μœΌλ‘œ κ°„λ‹¨ν•œ 데이터 전달이 κ°€λŠ₯ν•˜λ‹€.
    • URL 뒀에 데이터λ₯Ό μ‹¬λŠ” λ°©μ‹μ΄λž€. URL μ—μ„œ λ¬ΌμŒν‘œ 뒀에 정보λ₯Ό μž…λ ₯ν•˜λŠ” 것을 λ§ν•˜λ©° μ΄λŠ” ν˜•μ‹μ΄ μ •ν•΄μ ΈμžˆλŠ”λ° 이λ₯Ό query string λ˜λŠ” query parameter 라고 ν•œλ‹€.

🀍 Query string μž‘μ„±ν•˜μ—¬ GET μš”μ²­ν•˜κΈ°

$('#search').click(function(){
    let searchVal = $('#search-input').val();
    // 검색 λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ /search?value=μž…λ ₯λ‚΄μš© 경둜둜 이동, GET μš”μ²­
    window.location.replace(`/search?value=${searchVal}`); 
})
  • input νƒœκ·Έμ— μž…λ ₯된 값을 searchVal μ΄λΌλŠ” λ³€μˆ˜μ— ν• λ‹Ήν•˜μ—¬, 검색 λ²„νŠΌμ„ ν΄λ¦­ν•˜λ©΄ url 의 /search?value= 뒀에 λ“€μ–΄κ°ˆ 수 μžˆλ„λ‘ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ£Όμ—ˆλ‹€.

🀍 μ„œλ²„μ—μ„œ  Query string ν™•μΈν•˜κΈ°

app.get('/search', function(req, res){
    db.collection('post').find({할일: req.query.value}).toArray(function(err, result){
        res.render('search.ejs', {post : result});
    })
})
  • console.log(req.query) λ₯Ό 해보면 query string 을 object ν˜•νƒœλ‘œ κ°€μ Έμ™€μ„œ λ³Ό 수 μžˆλ‹€.
  • κ²€μƒ‰ν•œ λ‚΄μš©μ„ 가진 데이터λ₯Ό μ „λΆ€ κ°€μ Έμ™€μ„œ 보여주어야 ν•˜λ―€λ‘œ .findOne() ν•¨μˆ˜κ°€ μ•„λ‹Œ .find().toArray() ν•¨μˆ˜λ₯Ό μ‚¬μš©ν–ˆλ‹€.
  • db.collection 쀑 post μ—μ„œ κ²€μƒ‰ν•œ λ‚΄μš©μ„ 찾을 λ•Œμ—λŠ” req.query.value 의 ν˜•νƒœλ‘œ μž‘μ„±ν•΄μ£Όμ–΄μ•Ό κ²€μƒ‰ν•œ λ¬Έμžμ—΄ κ·ΈλŒ€λ‘œμ˜ ν˜•νƒœλ‘œ DB μ—μ„œ 검색이 κ°€λŠ₯ν•˜λ‹€.

πŸ’œ κ²Œμ‹œλ¬Όμ— index λ§Œλ“€κΈ°

🀍 DataBase κ°€ κ²Œμ‹œλ¬Όμ„ μ°ΎλŠ” 방법 μ•Œμ•„λ³΄κΈ°

  • κ·Έλƒ₯ κ²Œμ‹œλ¬Όμ„ ν™•μΈν•˜μ—¬ μ›ν•˜λŠ” 데이터λ₯Ό μ°ΎλŠ” 방법은 μ›ν•˜λŠ” 데이터λ₯Ό μ°ΎλŠ” 데에 μ‹œκ°„μ΄ 였래 걸릴 κ°€λŠ₯성이 λ†’λ‹€.
  • λ”°λΌμ„œ, DB λŠ” Binary Search λΌλŠ” μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό μ°ΎλŠ”λ° 이 방법을 μ‚¬μš©ν•˜λ €λ©΄ 미리 μˆœμ„œλŒ€λ‘œ 정렬이 λ˜μ–΄μžˆμ–΄μ•Ό ν•œλ‹€.
  • μ΄λŸ¬ν•œ μˆœμ„œ 정렬은 숫자뿐만 μ•„λ‹ˆλΌ λ¬Έμžμ—΄λ„ κ°€λŠ₯ν•œλ°, 이λ₯Ό indexing 이라고 ν•˜λ©°, ν•΄λ‹Ή collection 의 μ •λ ¬λœ 사본을 ν•˜λ‚˜ 더 λ§Œλ“€μ–΄μ£ΌλŠ” 것이닀.

 

πŸ“Œ MongoDB μ—μ„œ index λ§Œλ“€κΈ°

  • MongoDB μ—μ„œ indexing 을 ν•˜λ €λ©΄ μ•„λž˜μ˜ λ°©λ²•μœΌλ‘œ μ§„ν–‰ν•˜λ©΄ λœλ‹€.

  • μ›ν•˜λŠ” collection 을 μ„ νƒν•œ ν›„, indexes 탭에 λ“€μ–΄κ°€λ©΄ CREATE INDEX λ²„νŠΌ 클릭!

 

  • λ‚˜λŠ” 할일 λ‚΄μš©μ— 따라 정렬을 ν•  μ˜ˆμ •μ΄λ―€λ‘œ ν•„λ“œλͺ…은 할일, νƒ€μž…μ€ text 둜 μ„€μ •ν•΄μ£Όμ—ˆλ‹€.

 

  • 그러고 λ‚˜λ©΄ μœ„μ™€ 같은 index κ°€ λ§Œλ“€μ–΄μ§„λ‹€.

πŸ’œ ν•œκΈ€ 검색에 μ ν•©ν•œ 검색 κΈ°λŠ₯ λ§Œλ“€κΈ°

🀍 인덱슀λ₯Ό ν™œμš©ν•œ 검색 κΈ°λŠ₯ λ§Œλ“€κΈ°

app.get('/search', function(req, res){
    db.collection('post').find({ $text : { $search: req.query.value }}).toArray(function(err, result){
        res.render('search.ejs', {searchPost : result});
    })
})
  • .find() ν•¨μˆ˜ μ•ˆμ— $text : { $search : req. query.value } μ™€ 같이 μž‘μ„±ν•˜λ©΄ λ§Œλ“€μ–΄λ‘” text μΈλ±μŠ€μ—μ„œ κ²€μƒ‰μ—”μ§„μ²˜λŸΌ 검색이 κ°€λŠ₯ν•˜λ‹€.
    • 곡뢀 κ°•μ˜ 라고 κ²€μƒ‰ν•˜λ©΄ 곡뢀 λ˜λŠ” κ°•μ˜κ°€ ν¬ν•¨λœ 데이터듀을 μ°Ύμ•„μ€€λ‹€.
    • 곡뢀 -κ°•μ˜ 라고 κ²€μƒ‰ν•˜λ©΄ 곡뢀가 ν¬ν•¨λœ 데이터듀 쀑 κ°•μ˜κ°€ ν¬ν•¨λœ 데이터듀 μ œμ™Έν•œ λ‚˜λ¨Έμ§€ 데이터듀을 μ°Ύμ•„μ€€λ‹€.
    • "곡뢀 κ°•μ˜" 라고 κ²€μƒ‰ν•˜λ©΄ μ •ν™•ν•˜κ²Œ λ”°μ˜΄ν‘œ μ•ˆμ˜ 문ꡬ가 ν¬ν•¨λœ 데이터듀을 μ°Ύμ•„μ€€λ‹€.
  • ν•˜μ§€λ§Œ 이 방법은 μ˜μ–΄μ— μ΅œμ ν™”λœ λ°©μ‹μœΌλ‘œ μ˜μ–΄κ°€ μ•„λ‹Œ μ–Έμ–΄(특히, 쑰사가 많이 μ“°μ΄λŠ” μ–Έμ–΄)λŠ” 검색이 μ œλŒ€λ‘œ λ˜μ§€ μ•ŠλŠ”λ‹€λŠ” 단점이 μžˆλ‹€.
    • 첫번째 ν•΄κ²°μ±… : 검색할 λ¬Έμ„œμ˜ 양을 μ œν•œν•œλ‹€.
    • λ‘λ²ˆμ§Έ ν•΄κ²°μ±… : text search κΈ°λŠ₯을 μ¨μ•Όν•œλ‹€λ©΄ indexing λŒ€μ‹  nGram κ³Ό 같은 μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•œλ‹€.
    • μ„Έλ²ˆμ§Έ ν•΄κ²°μ±… : Search index λ₯Ό μ‚¬μš©ν•œλ‹€.

 

πŸ“Œ MongoDB Atlas μ—μ„œλ§Œ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯인 Search index μ‚¬μš©ν•˜κΈ°

  • Cluster 의 Search 탭을 눌러 μ ‘μ†ν•œ ν›„, Create Search Index λ²„νŠΌμ„ ν΄λ¦­ν•œλ‹€.

 

  • Visual Editor λ₯Ό 선택 ν›„ Next λ²„νŠΌμ„ ν΄λ¦­ν•œλ‹€.

 

  • Index Name 을 λ§Œλ“  ν›„, μ–΄λ–€ collection 에 μžˆλŠ” ν•­λͺ©μ„ indexing ν•  것인지 μ„ νƒν•œλ‹€.

 

  • Refine Your Index λ₯Ό ν΄λ¦­ν•œλ‹€.

 

  • 기쑴의 lucene.standard λ₯Ό lucene language-lucene.korean 으둜 λ°”κΎΈμ–΄μ€€λ‹€. (ν•œκΈ€ 검색 μ΅œμ ν™”κ°€ λͺ©μ μ΄κΈ° λ•Œλ¬Έ!)
  • Add Field 뢀뢄은 좔가해도 되고 μ•ˆν•΄λ„ 상관은 μ—†λ‹€.

 

  • lucene.korean 으둜 잘 λ³€κ²½λ˜μ—ˆλŠ”μ§€ 확인 ν›„ Save Changes - Create Search Index λ₯Ό ν΄λ¦­ν•œλ‹€.

 

  • μ •μƒμ μœΌλ‘œ λͺ¨λ“  과정이 μ™„λ£Œλ˜μ—ˆλ‹€λ©΄ μœ„μ™€ 같은 화면을 확인할 수 μžˆλ‹€.

🀍 Search index λ₯Ό μ΄μš©ν•΄μ„œ 검색 μš”μ²­ν•˜κΈ°

app.get('/search', function(req, res){
    let condition = [
        {
            $search : {
                index : 'todoSearch',
                text : {
                    query : req.query.value,
                    path: '할일' // 할일과 마감일 λ‘˜ λ‹€ μ°Ύκ³  μ‹ΆμœΌλ©΄ ['할일', '마감일']
                }
            }
        },
        {
            $sort : { _id : 1 } // 1 은 μ˜€λ¦„μ°¨μˆœ, -1 은 λ‚΄λ¦Όμ°¨μˆœ
        },
        {
            $limit : 10 // κ°€μ Έμ˜¬ 데이터 개수 μ œν•œ
        }
    ];
    db.collection('post').aggregate(condition).toArray(function(err, result){
        res.render('search.ejs', {searchPost : result});
    })
})
  • .aggregate() ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ 검색쑰건을 μ—¬λŸ¬κ°œ 뢙일 수 μžˆλ‹€.
  • 단, κ°€μ‹œμ„±μ„ μœ„ν•΄ condition μ΄λΌλŠ” λ³€μˆ˜λ₯Ό λ§Œλ“€μ–΄μ„œ λ°°μ—΄μ˜ ν˜•νƒœλ‘œ μž‘μ„±ν•΄μ£Όμ—ˆλ‹€.
    • $search λΌλŠ” operator λ₯Ό λ„£μœΌλ©΄ search index μ—μ„œ index λ₯Ό κ³¨λΌμ„œ 검색할 수 μžˆλ‹€.
    • $sort λΌλŠ” operator λ₯Ό λ„£μœΌλ©΄ νŠΉμ • 속성을 κΈ°μ€€μœΌλ‘œ μ˜€λ¦„μ°¨μˆœ, λ‚΄λ¦Όμ°¨μˆœμœΌλ‘œ μ •λ ¬ν•  수 μžˆλ‹€.
    • $limit μ΄λΌλŠ” operator λ₯Ό λ„£μœΌλ©΄ κ²€μƒ‰λœ 전체 데이터가 μ•„λ‹ˆλΌ ν•΄λ‹Ήν•˜λŠ” 개수만큼 μƒμœ„μ— μ‘΄μž¬ν•˜λŠ” 데이터λ₯Ό κ°€μ Έμ˜¬ 수 μžˆλ‹€.
728x90
λ°˜μ‘ν˜•