Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

태깅

태깅은 정보를 관리하기 위한 좋은 방법이다. 완전히 공개된 서비스의 경우 태그관리가 까다롭다는 문제가 있지만, 추천시스템에 대한 노하우가 쌓이면서 많은 부분 해결이 된 것 같다.

Joinc는 개임위키이니 만큼 관리하기가 훨씬 쉽다. 태그라고 해봐야 100개가 넘지 않을 것 같은데, 이 정도라면 자동완성이나 추천에 신경쓰지 않고 단순하게 구성 할 수 있을 것이다. 자동완성쪽을 해보고 싶기는 한데, 이건 나중에 해볼 생각이다.

구성

 Tagging 시스템 구성

태그 목록을 관리하는 테이블과 태깅 적용 정보를 가지는 두 개의 테이블로 구성된다.
  1. 관리자 계정으로 로그인하면, 태그를 관리 할 수 있다.
  2. 페이지 상단에 "현재 페이지 태깅" 메뉴가 추가된다.
  3. 태깅 관리 페이지로 넘어간다.
  4. 태깅 관리 페이지에는 사용 할 수 있는 태그 목록이 있다. 여기에서 태그를 선택한다.
아래와 같이 테이블을 만들었다.
CREATE TABLE joinctag (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(64) NOT NULL,
    PRIMARY KEY(id)
);

CREATE TABLE wikitagging (
    id INT NOT NULL AUTO_INCREMENT,
    tagid INT NOT NULL,
    pageid INT NOT NULL,
    FOREIGN KEY (`tagid`) REFERENCES `joinctag`(`id`),
    PRIMARY KEY(id)
)

화면 UI 구성

인터넷을 뒤져서 적당히 보기 좋은 녀석을 찾았다. Vue.js로 테스트를 했다. 그럭저럭 괜찮은 것 같다.

태그리스트에서 원하는 태그를 클릭하면, 페이지에 태그로 붙는다. 자동완성으로 하는게 더 편할 수 있겠는데 일단은 쉬운 방법으로 했다.
테스트에 사용한 코드다.
<div class="row">
	<div class="large-12 column">
	<div id="app">
		<pagetag-management></pagetag-management>
	</div>
	</div>
</div>
<script>
Vue.component('pagetag-management', {
	template: `
<div class="row">
	<div class="large-12 columns">
	<div class="tag-cloud">
		<ul class="stats-list">
			<li class="vlist" v-for="(item, key) in tags">
				<a class="tag-cloud-individual-tag" href="javascript: void(0)" v-on:click="removeTagging(key)">{{ item }}
   		 		<i class="fa fi-minus" aria-hidden="true"></i>
				</a>
			</li>
		</ul>
	</div>
	</div>
	<div class="large-12 columns">
	<hr>
	</div>
	<div class="large-12 columns">
	<div class="tag-cloud">
		<ul class="stats-list">
			<li class="vlist" v-for="(item, key) in items">
				<a class="tag-cloud-individual-tag" href="javascript: void(0)" v-on:click="addTagging(key)">{{ item }} 
   		 		<i class="fa fi-plus" aria-hidden="true"></i>
				</a>
			</li>
		</ul>
	</div>
	</div>
</div>`,
  data: function() {
	return {
        name: [],
		items: ["api-gateway","Aurora","aws","beanstalk","bigdata","blog","cache","cli","cloudwatch","configuration","database","devops","distribute","docker","ec2","ecs","ELB","encrypt","geohash","golang","hadoop","hash","html5","iam","javascript","joinc","json","Kafka","lambda","logging","messaging","msa","mysql","network","nginx","nosql","package","proxy","redis","rpc","s3","spatial","tag","test","tip","tutorial","vpc","vue.js","미완성","분산시스템"],
		tags: []
	}
  },
  methods: {
    addTagging: function(index) {
		this.tags.push(this.items[index])
		this.items.splice(index, 1)
	},
    removeTagging: function(index) {
		this.items.push(this.tags[index])
		this.tags.splice(index, 1)
	},
  }
});

new Vue({
	el: '#app'
});
</script>
실제 코드는 ajax 호출을 해야 할 것이다. addTagging과 removeTagging 메서드에서 ajax를 호출해서 데이터베이스 작업을 요청했다.
  methods: {
    addTagging: function(index) {
		axios.post('/api/addtag',
			{id: 4, name: this.items[index] },
			{headers: {
       'Content-type': 'application/json',
		}}).then(response => {
			if (response.status == 200) {
				this.tags.push(this.items[index])
				this.items.splice(index, 1)
			}
		});
	},
    removeTagging: function(index) {
		axios.post('/api/removetag',
			{id: 4, name: this.tags[index] },
			{headers: {
       'Content-type': 'application/json',
		}}).then(response => {
			if (response.status == 200) {
				this.items.push(this.tags[index])
				this.tags.splice(index, 1)
			}
		});
	},
  }

태그 클라우드 만들기

태그 클라우드를 만들고 싶었으나 귀찮아서 태그명과 해당 이름으로 태깅된 문서의 갯수를 카운팅한 목록을 출력하는 코드를 만들었다.

Tags

테스트에 사용한 코드다. data 부분은 생략 했다.
<div class="row">
    <div class="large-12 column">   
    <div id="app_tagcount">     
        <tagcount-list></tagcount-list>
    </div>
    </div>
</div>

<script>
Vue.component('tagcount-list', {
    delimiters: ["<%","%>"],
    template:`
<div class="tag-list">
    <ul class="tag-ul">
        <li class="vlist" v-for="(item, key) in items">
		<a class="tag-cloud-individual-tag"  v-bind:href="'https://www.joinc.co.kr/w/taglist?name='+ item.Name"><% item.Name %> &#183;&#183;&#183; <% item.Num %></a>
        </li>
    </ul>
</div>`,
    data: function() {         
        return {
			items: [{"Name":"aws","Num":18},{"Name":"javascript","Num":8}]
        }
    }
})    
      
new Vue({
    el:"#app_tagcount"
})  
</script>
items 부분은 ajax 호출이 아닌데, 귀찮아서 go template에서 items 목록을 만들어 버렸다.

관련 태그 만들기

문서는 하나 이상의 태그를 가지고 있을테니, 이 태그들을 이용해서 특정 태그와 높은 관련성을 가진 태그의 목록을 뽑을 수 있을 것이다. 태깅 테이블의 내용은 아래와 같다.
+-----------------+--------+
| name            | pageid |
+-----------------+--------+
| api-gateway     |   3296 |
| Aurora          |   3274 |
| Aurora          |   3277 |----+ Aurora
| Aurora          |   3280 |----+ Aurora
| aws             |   3271 |    |
| aws             |   3274 |    |
| aws             |   3277 |----+ aws
| aws             |   3280 |----+ aws
| aws             |   3327 |    |
| aws             |   3328 |    |
| aws             |   3329 |    |
| aws             |   3334 |    |
| aws             |   3337 |    |
| beanstalk       |   3328 |    |
| bigdata         |   3277 |----+ bigdata
| bigdata         |   3292 |
name 은 태그 이름이고 pageid는 태그된 페이지의 ID(유일한 식별번호)다.

aws 태그를 기준으로 보면, Aurora 태그가 2, bigdata 태그가 1임을 알 수 있다. 데이터의 일부이지만 여기에서 우리는 aws 태그와 연관있는 테이블을 선택하고 (얼마나 가까운지)가중치를 줄 수 있을 것이다. 단순 카운트일 경우 Aurora는 2의 가중치 bigdata는 1의 가중치를 가진다. 만드는 방법을 생각해보자.

 연관 태그 만들기

태그 이름 목록은 별도의 테이블에 가지고 있다. 이 테이블을 순환하면서 각 태그의 연관 태그를 계산하면 된다. 예를 들어 "aws"에 대한 연관태그는 아래와 같이 만들 수 있을 것이다.
  1. "aws"가 태깅된 문서의 목록 - pageid - 를 얻는다.
  2. pageid에 태깅된 태그 이름을 가져온다.
  3. 해당 태그 이름에 카운트 한다.
그 결과 aws와 관련된 s3, go, msa 태그를 얻을 수 있으며, 가중치(얼마나 많은 관련성이 있는지)도 계산 할 수 있다.

위에 있는 이미지의 데이터를 가지고 테스트코드를 만들었다.
package main

import (
    "fmt"
)

type tags struct {
    pageid int
    name   []string
}

func getRelateTag(tagName string, testItem []tags) map[string]int {
    pageIds := []int{}
    for _, v := range testItem {
        for _, name := range v.name {
            if name == tagName {
                pageIds = append(pageIds, v.pageid)
            }
        }
    }
    relateTag := make(map[string]int)
    for _, pid := range pageIds {
        for _, name := range testItem[pid].name {
            if name == tagName {
                continue
            }
            if _, ok := relateTag[name]; ok {
                relateTag[name]++
            } else {
                relateTag[name] = 1
            }
        }
    }
    return relateTag
}
func main() {
    tagList := []string{"aws", "s3", "go", "rds", "msa"}

    testItem := []tags{
        {0, []string{}},
        {1, []string{"aws", "s3", "go"}},
        {2, []string{"s3", "rds"}},
        {3, []string{"aws", "s3"}},
        {4, []string{"aws", "s3", "msa"}},
        {5, []string{"msa"}},
        {6, []string{"s3", "go"}},
    }
    for _, name := range tagList {
        fmt.Println(name, " : ", getRelateTag(name, testItem))
    }
}
		
작동만 하는 코드다. 실제 서비스에서라면 골 때릴 필요 없이 REDIS ZADD로 구현하면되겠다.

태그 그래프 만들기

이론적으로는 관련 태그를 확장하면 되고, 작동하는 태그를 만드는건 어렵지는 않을 거다. 실제 서비스 환경에서 어떻게 적은 비용으로 빠르게 작동하게 하느냐가 이슈지.

... 내용 보강 필요

인기태그

문서를 읽으면 로그를 남긴다. 이 때 문서에 태깅된 태그 목록도 로그에 포함하면, 이 로그를 분석해서 인기태그를 뽑을 수 있다. 인기태그를 결정하는 요소는 아래와 같다.
  • 태그 카운트 : 당연히 카운트가 높은 태그가 인기태그일 것이다.
  • 시간 : 시간이 지날 수록 태그 인기도는 떨어져야 한다.
적당한 튜닝이 필요하겠으나 태그 카운트 * 시간 = 인기도로 계산 할 수 있을 것이다.

유저별 태그 추천

유저가 읽는 문서에 포함된 태그 기록을 전부 남기면 된다. 인기태그를 남기기 위한 로그에 유저 정보만 추가하면 된다.

 유저별 태그추천

로그는 아래처럼 쌓을 수 있을 것이다.
{"user":"yundream", "tag":["aws","s3"], "pageid":3710, "time":"2018-06-29T09:43:58Z"}
{"user":"kong", "tag":["aws","msa"], "pageid":3122, "time":"2018-06-29T09:52:58Z"}
{"user":"yundream", "tag":["aws","rds"], "pageid":1503, "time":"2018-06-29T09:52:58Z"}
{"user":"yundream", "tag":["s3","go", "rds"], "pageid":2200, "time":"2018-06-29T10:10:58Z"}
{"user":"kong", "tag":["s3","rds"], "pageid":1119, "time":"2018-06-29T09:52:58Z"}
{"user":"yundream", "tag":["aws","msa"], "pageid":3082, "time":"2018-06-29T10:10:58Z"}
{"user":"yundream", "tag":["go"], "pageid":3317, "time":"2018-06-29T10:10:58Z"}
이렇게 로그를 쌓아두면, EMR을 돌리던지 해서 서비스 데이터를 만들 수 있을 것이다.