[algorithm] 태그 시스템 구현 방법

SO에서 사용되는 것과 같은 태그 시스템을 구현하는 가장 좋은 방법이 무엇인지 궁금합니다. 나는 이것을 생각하고 있었지만 좋은 확장 가능한 솔루션을 찾을 수 없습니다.

tags테이블, articles테이블 및 테이블을 갖는 기본적인 3 개의 테이블 솔루션을 생각하고있었습니다 tag_to_articles.

이것이이 문제에 대한 최선의 해결책입니까, 아니면 대안이 있습니까? 이 방법을 사용하면 테이블이 시간이 지남에 따라 매우 커지며 검색하는 것이 너무 효율적이지 않다고 생각합니다. 반면에 쿼리가 빠르게 실행되는 것은 그다지 중요하지 않습니다.



답변

이 블로그 게시물이 흥미로울 것이라고 생각합니다. 태그 : 데이터베이스 스키마

문제 : 원하는만큼의 태그로 책갈피 (또는 블로그 게시물 등)에 태그를 지정할 수있는 데이터베이스 스키마를 원합니다. 나중에 쿼리를 실행하여 책갈피를 태그의 결합 또는 교차로 제한하려고합니다. 검색 결과에서 일부 태그를 제외 (예 : 빼기) 할 수도 있습니다.

“MySQLicious”솔루션

이 솔루션에서 스키마에는 테이블이 하나만 있으며 비정규 화됩니다. MySQLicious는 del.icio.us 데이터를이 구조의 테이블로 가져 오기 때문에이 유형을 “MySQLicious 솔루션”이라고합니다.

여기에 이미지 설명 입력여기에 이미지 설명 입력

“search + webservice + semweb”에 대한 교차 (AND) 쿼리 :

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

“search | webservice | semweb”에 대한 통합 (OR) 쿼리 :

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

“search + webservice-semweb”에 대한 마이너스 쿼리

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

“Scuttle”솔루션

Scuttle 은 데이터를 두 개의 테이블로 구성합니다. “scCategories”테이블은 “태그”테이블이며 “책갈피”테이블에 대한 외래 키를 가지고 있습니다.

여기에 이미지 설명 입력

“bookmark + webservice + semweb”에 대한 교차 (AND) 쿼리 :

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

먼저 모든 북마크-태그 조합이 검색됩니다. 여기서 태그는 “bookmark”, “webservice”또는 “semweb”(c.category IN ( ‘bookmark’, ‘webservice’, ‘semweb’))입니다. 검색된 세 개의 태그가 모두 고려됩니다 (HAVING COUNT (b.bId) = 3).

“bookmark | webservice | semweb”에 대한 Union (OR) 쿼리 :
HAVING 절을 생략하고 union이 있습니다.

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

마이너스 (제외) “bookmark + webservice-semweb”쿼리, 즉 semweb이 아니라 북마크 및 웹 서비스입니다.

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

HAVING COUNT를 생략하면“bookmark | webservice-semweb”에 대한 쿼리가 표시됩니다.


“Toxi”솔루션

Toxi 는 3 개의 테이블 구조를 생각해 냈습니다. 테이블 “태그 맵”을 통해 북마크와 태그는 n-to-m 관련됩니다. 각 태그는 서로 다른 북마크와 함께 사용할 수 있으며 그 반대의 경우도 마찬가지입니다. 이 DB 스키마는 워드 프레스에서도 사용됩니다. 쿼리는 “scuttle”솔루션에서와 매우 동일합니다.

여기에 이미지 설명 입력

“bookmark + webservice + semweb”에 대한 교차 (AND) 쿼리

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

“bookmark | webservice | semweb”에 대한 통합 (OR) 쿼리

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

마이너스 (제외) “bookmark + webservice-semweb”쿼리, 즉 semweb이 아니라 북마크 및 웹 서비스입니다.

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

HAVING COUNT를 생략하면“bookmark | webservice-semweb”에 대한 쿼리가 표시됩니다.


답변

3 테이블 솔루션에 문제가 없습니다.

또 다른 옵션은 기사에 적용 할 수있는 태그 수를 제한하고 (SO의 5와 같이) 기사 테이블에 직접 추가하는 것입니다.

DB 정규화에는 장점과 단점이 있습니다. 마치 하나의 테이블에 하드 와이어 링하는 것에는 장점과 단점이 있습니다.

둘 다 할 수 없다고 말하는 것은 없습니다. 정보를 반복하는 것은 관계형 DB 패러다임에 위배되지만 성능이 목표라면 패러다임을 깨뜨려야 할 수도 있습니다.


답변

제안 된 세 가지 테이블 구현이 태그 지정에 적합합니다.

그러나 스택 오버플로는 다른 구현을 사용합니다. 태그를 posts 테이블의 varchar 열에 일반 텍스트로 저장하고 전체 텍스트 인덱싱을 사용하여 태그와 일치하는 게시물을 가져옵니다. 예를 들면 posts.tags = "algorithm system tagging best-practices". 나는 Jeff가 이것을 어딘가에서 언급했다고 확신하지만 나는 어디에 있는지 잊는다.


답변

제안 된 솔루션은 태그와 기사 사이의 다 대다 관계를 해결하기 위해 내가 생각할 수있는 유일한 방법은 아니지만 가장 좋은 방법입니다. 그래서 내 투표는 ‘예, 여전히 최고입니다.’ 그래도 대안에 관심이 있습니다.


답변

데이터베이스가 인덱싱 가능한 배열 (예 : PostgreSQL)을 지원하는 경우 완전히 비정규 화 된 솔루션을 권장합니다. 태그를 동일한 테이블에 문자열 배열로 저장합니다. 그렇지 않은 경우 개체를 태그에 매핑하는 보조 테이블이 최상의 솔루션입니다. 태그에 대한 추가 정보를 저장해야하는 경우 별도의 태그 테이블을 사용할 수 있지만 모든 태그 조회에 대해 두 번째 조인을 도입 할 필요가 없습니다.


답변

더 나은 성능을 위해 최적화 된 MySQLicious를 제안하고 싶습니다. 그 전에 Toxi (3 테이블) 솔루션의 단점은

수백만 개의 질문이 있고 각각에 5 개의 태그가있는 경우 태그 맵 테이블에는 5 백만 개의 항목이 있습니다. 따라서 먼저 태그 검색을 기반으로 1 만 개의 태그 맵 항목을 필터링 한 다음 해당 1 만 개의 일치하는 질문을 다시 필터링해야합니다. 따라서 아티 컬 ID가 단순한 숫자이면 필터링하는 동안 괜찮지 만 UUID (32 varchar)의 경우 필터링하면 인덱싱되지만 더 큰 비교가 필요합니다.

내 솔루션 :

새 태그가 생성 될 때마다 counter ++ (base 10)을 사용하고 해당 카운터를 base64로 변환합니다. 이제 각 태그 이름에는 base64 ID가 있습니다. 이 ID를 이름과 함께 UI에 전달합니다. 이렇게하면 시스템에 4095 개의 태그가 생성 될 때까지 최대 2 개의 문자 ID를 갖게됩니다. 이제 이러한 여러 태그를 각 질문 테이블 태그 열에 연결합니다. 구분 기호도 추가하고 정렬하십시오.

그래서 테이블은 다음과 같습니다

여기에 이미지 설명 입력

쿼리하는 동안 실제 태그 이름 대신 id를 쿼리합니다. 이 때문에 SORTED , and태그의 상태가 더 효율적입니다 ( LIKE '%|a|%|c|%|f|%).

참고 하나의 공간 구분이 충분하지 않다고 우리는 같은 차별화 태그 더블 구분이 필요 sql하고 mysql있기 때문에를 LIKE "%sql%"반환합니다 mysql아니라 결과를. 해야한다LIKE "%|sql|%"

검색이 인덱싱되지 않음을 알고 있지만 여전히 author / dateTime과 같은 기사와 관련된 다른 열에 인덱싱되었을 수 있습니다. 그렇지 않으면 전체 테이블 스캔으로 이어질 것입니다.

마지막으로이 솔루션을 사용하면 조인 조건에서 백만 개의 레코드와 5 백만 개의 레코드를 비교해야하는 내부 조인이 필요하지 않습니다.


답변

CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

메모:

  • 최적화를 어렵게 만드는 추가 다 대다 테이블을 거치지 않는다는 점에서 TOXI보다 낫습니다.
  • 물론 중복 태그로 인해 내 접근 방식이 TOXI보다 약간 더 클 수 있지만 전체 데이터베이스 의 작은 비율이며 성능 향상이 상당 할 수 있습니다.
  • 확장 성이 뛰어납니다.
  • 대리 AUTO_INCREMENTPK 가 없습니다 (필요하지 않기 때문에) . 따라서 Scuttle보다 낫습니다.
  • 이 인덱스를 사용할 수 없기 때문에 MySQLicious 짜증 ( LIKE최고의 와일드 카드를 거짓 안타를 문자열에)
  • MySQL의 경우 ‘클러스터링’효과를 얻으려면 ENGINE = InnoDB를 사용해야합니다.

관련 토론 (MySQL 용) :
다 : 다 매핑 테이블 최적화
순서 목록