ISUCON6ͽ����ȥå��̲ᤷ�ޤ���
@methane �Ǥ����֤��ε������ˤ����꤬���롪�פȤ���������̾�� @kizkoh (����ե�ô��), @mecha_g3 (���ץ�ô��) �ȤȤ�� ISUCON 6 �˻��路��ͽ����ȥåץ��������̲ᤷ�ޤ����� ����Τդ꤫���êµï¿½ï¿½ï¿½ï¿½ñ¤¤Þ¤ï¿½ï¿½ï¿½
�դ꤫����
��ǰ�ʤ��饹�����ϵ�Ͽ���Ƥʤ��ΤǤ������������ˤ�ä����Ȥ�ޤȤ�ޤ��� ���ץ�Υ����ɤ� methane/isu6q-app �Ǹ������Ƥ���Τǡ���̣���������ϥ����ɤ��ǧ���Ƥ���������
strings.Replacer ��Ȥ�
���Ѹ���Ϻǽ餫�� Go �ȷ��Ƥ����ΤǤ�����Go�ν���������٤����ƥ����ॢ���ȤǺǽ餫�饹����̵���Ǥ����� top �ǥ��ץ��CPU������Ū�ʤΤϤ���Ƚ��ޤ������������ɤ��ɤ�Фʤˤ��٤��Τ����ȯ��Ƚ��ޤ���������ʤ�Ĺ���ʤ��ΤǴؿ����Τ�ĥ��ޤ���
func htmlify(w http.ResponseWriter, r *http.Request, content string) string { if content == "" { return "" } rows, err := db.Query(` SELECT * FROM entry ORDER BY CHARACTER_LENGTH(keyword) DESC `) panicIf(err) entries := make([]*Entry, 0, 500) for rows.Next() { e := Entry{} err := rows.Scan(&e.ID, &e.AuthorID, &e.Keyword, &e.Description, &e.UpdatedAt, &e.CreatedAt) panicIf(err) entries = append(entries, &e) } rows.Close() keywords := make([]string, 0, 500) for _, entry := range entries { keywords = append(keywords, regexp.QuoteMeta(entry.Keyword)) } re := regexp.MustCompile("("+strings.Join(keywords, "|")+")") kw2sha := make(map[string]string) content = re.ReplaceAllStringFunc(content, func(kw string) string { kw2sha[kw] = "isuda_" + fmt.Sprintf("%x", sha1.Sum([]byte(kw))) return kw2sha[kw] }) content = html.EscapeString(content) for kw, hash := range kw2sha { u, err := r.URL.Parse(baseUrl.String()+"/keyword/" + pathURIEscape(kw)) panicIf(err) link := fmt.Sprintf("<a href=\"%s\">%s</a>", u, html.EscapeString(kw)) content = strings.Replace(content, hash, link, -1) } return strings.Replace(content, "\n", "<br />\n", -1) }
���������ϤϤƤʥ�����ɡܤϤƤʥ������Τ褦�ʥ����ӥ��ǡ����δؿ��ϥ�����ɤξҲðµ»ï¿½ï¿½ï¿½ï¿½Ð¤ï¿½ï¿½ï¿½Â¾ï¿½Î¥ï¿½ï¿½ï¿½ï¿½ï¡¼ï¿½É¤Ø¤Î¥ï¿½ó¥¯¤ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Ä¤ï¿½HTML���������פ����ΤǤ���������ɤ�ʸ������ǥ����Ȥ��Ƥ���Τǡ���Ĺ���פǥ�󥯤ˤʤ�ޤ���
������ɤ����󥯤ؤ��Ѵ�����ȯ�Ǥ���Ƥ��ʤ��Τϡ�����Ѵ������HTML���������פ� <a>
�����ޤǥ��������פ���Ƥ��ޤ������դ����HTML���������פ򤹤�ȥ�����ɤ򸫤Ĥ����ʤ��ʤ뤫��Ǥ����ʰ��٤���˵��Ť����쵤���Ѵ�����褦�ˤ��ƥϥޤ�ޤ�����
����ɽ���Υӥ�ɤ��ִ��Τɤ��餬�ɤ줯�餤�γ��ǽŤ��Τ��ޤǤϤޤ��ץ��ե������Ϥ�Ƥʤ��ä��ΤǤ狼��ޤ��󤬡���꤫��Go�餷����Ŭ����Ϥ�Ƥߤޤ���
�ޤ�Go������ɽ�����٤��Τ� strings.Replacer ��Ȥäƥ�����ɤ����󥯤ؤ��Ѵ��򤷤ޤ��� Replacer ���ۤ���Τˤ���֤�������Τǡ� htmlify()
�Ȥ����ؿ�������ۤ���ΤǤϤʤ�����ư���ȡ�������ɤ��ɲú�����˺ƹ��ۤò¤¹¤ï¿½è¤¦ï¿½Ë¤ï¿½ï¿½Þ¤ï¿½ï¿½ï¿½ï¿½Ê¼ÂºÝ¤Ë¤Ï¤ï¿½ï¿½Ç¤ï¿½Â¸ï¿½ß¤ï¿½ï¿½ë¥ï¿½ï¿½ï¿½ï¡¼ï¿½É¤Îµï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Ð¤ï¿½ï¿½ë½¤ï¿½ï¿½ï¿½Î¥ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Æ¨ï¿½ï¿½ï¿½Æ¤ï¿½ï¿½ï¿½Ìµï¿½Ì¤ËºÆ¹ï¿½ï¿½Û¤ï¿½ï¿½Æ¤ï¿½ï¿½Þ¤ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Ëµï¿½ï¿½Å¤ï¿½ï¿½Æ¤ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Ã¤È¥ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½å¤¬ï¿½Ã¤ï¿½ï¿½Ï¤ï¿½ï¿½Ä¡ï¿½
var ( mKwControl sync.Mutex kwdList KeywordArray mKwReplacer sync.RWMutex kwReplacer1st, kwReplacer2nd *strings.Replacer ) func updateReplacer() { reps1 := make([]string, 0, len(kwdList)*2) reps2 := make([]string, 0, len(kwdList)*2) for i, k := range kwdList { if k.holder == "" { k.holder = fmt.Sprintf("isuda_%x", sha1.Sum([]byte(k.Key))) kwdList[i].holder = k.holder } reps1 = append(reps1, k.Key) reps1 = append(reps1, k.holder) reps2 = append(reps2, k.holder) reps2 = append(reps2, k.Link) } r1 := strings.NewReplacer(reps1...) r2 := strings.NewReplacer(reps2...) mKwReplacer.Lock() kwReplacer1st = r1 kwReplacer2nd = r2 mKwReplacer.Unlock() } func AddKeyword(key, link string) { k := Keyword{Key: key, Link: link} mKwControl.Lock() kwdList = append(kwdList, k) sort.Sort(kwdList) updateReplacer() mKwControl.Unlock() } func ReplaceKeyword(c string) string { mKwReplacer.RLock() r1 := kwReplacer1st r2 := kwReplacer2nd mKwReplacer.RUnlock() x := r1.Replace(c) x = html.EscapeString(x) return r2.Replace(x) }
AddKeyword
�ϥ�����ɤ�ݥ��Ȥ����Ȥ��ˡ� ReplaceKeyword
�� htmlify
����ƤФ�ޤ�������� NewReplacer
��ƤӽФ�����������˺︺���뤳�Ȥ��Ǥ��ޤ�����
�����ѹ����������륿���ߥ󥰤ǡ� MySQL ����³����Ȥ��� '127.0.0.1' �������³�����Ĥ���Ƥ��ʤ��Ȥ������顼���Ǥ�褦�ˤʤäơ����⤷�Ƥʤ��Τ˲��줿�ɵ�: �����Υ����skip-name-resolve
�����ä��Τ����������͡� Unix Domain Socket ��Ȥ��褦�ˤ��ޤ�����
@kizkoh �ˤ�� nginx �� MySQL ������ (��Ū�ե������ nginx ���֤���) �䡢��������ˤ��ä��Х����٤����ꤷ�ơ�14:00������12������Ф��ޤ�����
isutar �� isuda �˥ޡ���
����� @mecha_g3 ��Ǥ������ʬ�Ǥ��� isutar �� isuda �����ߤ���JSON API��ȤäƤ����򤷤Ƥ�����ʬ�����äơ�2�ĤΥ��ץ��MySQL��1��Υޥ���˾�äƤ���ʾ崰����̵�̤ʤΤ�����1�ĤˤޤȤ�ޤ�������������Ͽ���Ƥʤ��ΤǤ�����������ǽ���åפ����Ϥ��Ǥ���
������������ƥ٥���ò¤«¤ï¿½ï¿½ï¿½È¤ï¿½ï¿½Ë¥Ï¥Þ¤Ã¤ï¿½ï¿½Î¤ï¿½ï¿½ï¿½DB���ͥ������Υǥåɥ��å��Ǥ�����Ȥ��2������1��Υޥ���ʤΤǡ�DB�ؤ���³��4�ܤ��פ�ʤ��������Ȼפ��Ĥġ�ǰ�Τ����8�ܤˤ��Ƥ��ޤ������Ȥ����������Τ褦�ʥ����ɤ�������8�ܤ��äƤ�Â��ʤ��ʤäƤ��ޤ��ޤ�����
rows, err := db.Query(fmt.Sprintf( "SELECT * FROM entry ORDER BY updated_at DESC LIMIT %d OFFSET %d", perPage, perPage*(page-1), )) if err != nil && err != sql.ErrNoRows { panicIf(err) } entries := make([]*Entry, 0, 10) for rows.Next() { e := Entry{} err := rows.Scan(&e.ID, &e.AuthorID, &e.Keyword, &e.Description, &e.UpdatedAt, &e.CreatedAt) panicIf(err) e.Html = htmlify(w, r, e.Description) e.Stars = loadStars(e.Keyword) entries = append(entries, &e) } rows.Close()
���Υ����ɤϥȥåץڡ����Υϥ�ɥ顼�ΰ����Ǥ��� for rows.Next()
�롼�פ���λ���� rows.Close()
���ƤФ��ޤ�DB����³�򰮤�ΤǤ��������Υ롼����� loadStars(e.Keyword)
����ʬ����Ǥ����SQL��ƤӽФ��Ƥ��ޤ���
���ξ��֤ǡ� top �ڡ�����8������ǥ�����������ƥ롼�פγ�¦�Υ������¹Ԥ���ȡ�8�ܤ���³��Ȥ��ڤä����֤ˤʤꡢ�ɤ� goroutine ����¦�� loadStars()
��DB��³��̵�¤��ԤäƤ��ޤ����Ȥˤʤ�ޤ���
����夤�ƹͤ���Ф��������������Τ��ȤʤΤǤ������ǽ�� rows.Close()
��Ƥ�Ǥʤ���꤬�ɤ����ˤ���󤸤�ʤ�����õ����ä��ꡢ���η��Υͥ��Ȥ��ޤ������Ȥ˵��Ť����Ȥ���֤Ǥ�1goroutine�������DB��³����2�ܤˤʤ���������顢�������٤�;͵�ϸ������8�ܤˤ���������ɤʤ����פȹͤ��Ƥ��ޤä��ꤷ�Ƥ��ޤ��ޤ�����
��̡�����������к�������ȥͥ��Ȥ��٤��ΤǤϤʤ���ñ�˥��ͥ������ס���ο����ܤ�16�����䤷�������Ǥ����٥���ޡ��������ɤ�ɤ������پ夲�Ƥ��륿���פ��ä��餳��Ǥ���Ǥޤ�����
�����Ȥ��ơ� rows.Next()
�롼����ǥͥ��Ȥ��ƥ������¹Ԥ���Τ�ñ��ɬ�פ���³�����ܤˤʤ�ʾ�ζ���������äƤ���Τǡ� rows.Close()
�ޤǤν�����ñ��˥������̤Υե��å������ˤ��ޤ��礦��
Replacer �ι��ۤ� zero time cache ��
�ץ��ե�������äƤߤ��Ȥ����ޤ� Replacer �ι��ۤ��Ť��Τǡ��Ŀ�Ū�� zero time cache �ȸƤ�Ǥ��륤�ǥ������Ȥäư����˥���å���򤷤Ƥߤޤ������Υ����ɤ򸫤Ƥ���������
var ( mKwControl sync.Mutex kwdList KeywordArray mUpdateReplacer sync.Mutex repLastUpdated time.Time ) func AddKeyword(key, link string) { k := Keyword{Key: key, Link: link} mKwControl.Lock() kwdList = append(kwdList, k) mKwControl.Unlock() updateReplacer() } func updateReplacer() { now := time.Now() mUpdateReplacer.Lock() defer mUpdateReplacer.Unlock() if repLastUpdated.After(now) { return } repLastUpdated = time.Now() reps1 := make([]string, 0, len(kwdList)*2) reps2 := make([]string, 0, len(kwdList)*2) mKwControl.Lock() kws := kwdList[:] mKwControl.Unlock() sort.Sort(kws) ����// ... �ʹ� Replacer �ι��۽��� }
mUpdateReplacer
�� repLastUpdated
���ɲä����ѿ��ǡ� updateReplacer()
�� repLastUpdated = time.Now()
�ޤǤ����ǥ�����ˤʤäƤ��ޤ���
�㤨�С��������A, B, C ���ۤ�Ʊ�����ɲä��줿�Ȥ��ޤ���
- �ޤ�A���ǽ�˥��å���������ơ�
repLastUpdated = time.Now()
��¹Ԥ��� Replacer �ι��ۤ�Ϥ�ޤ��� - ³���ƥ������B��C���Ȥ��
mUpdateReplacer.Lock()
����ߤ��ޤ������å�ľ���˼�������now
�� (1) �ǹ�������repLastUpdated
����̤��λ��֤ˤʤ�ޤ��� - �������A�� Replacer ��������λ����
mUpdateReplacer
����������ޤ����������B��������Ƥ��� goroutine �����Υ��å���������ޤ��� - ���å��������˼�������
now
��repLastUpdated
���⿷�����Τǡ�if repLastUpdated.After(now)
�����ˤʤ�ޤ��󡣤ʤΤ� Replacer ��ƹ��ۤ��ޤ����������A�򹹿������Ȥ���kwdList
�˥������B�����äƤ��ʤ��ä��ʸ�̩�ˤϥ����ߥ󥰰�¸�����äƤ�����ǽ���⤢��ˤΤǡ������ɬ�פʽ����Ǥ��� - ����˥������B�� Replacer ����������ꡢ�������C��
mUpdateReplacer
�Υ��å���������ޤ������٤�if repLastUpdated.After(now)
�����ˤʤ�Τǡ� Replacer �κƹ��ۤϥ����åפ���ޤ��� (4) �� Replacer ��ƹ��ۤ����Ȥ��ˤϳμ¤˥������C��kwdList
�����äƤ����Τǡ������åפ��Ƥ�����Ǥ���
��������������ɤ�������Ǥ�����Ǥ��ʤ����⤷��ޤ��󤬡����Ҥ��ä���ȥ����ɤȸ���٤����򤷤ƤߤƤ��������� ���Υ��ǥ������ISUCON�����ǤϤʤ����ºݤ�Go�ǹ���ǽ�ʥ����С���񤯤Ȥ������������Ǥ���
�ޤ����㤨�� MySQL ��ʣ���Υȥ�󥶥���������٤� fsync �ǥ��ߥåȤ��륰�롼�ץ��ߥåȤʤɡ���Ʊ���˼¹Ԥ����Ť�������Хå�������פȤ����Τ���������Υǥ�����ѥ�����ȸƤ�Ǥ��ɤ����餤����Ū���Ȼפ��ΤǤ������ɤʤ������Υѥ������̾����¸�ΤǤ����� @methane ���� Tweet �ʤɤǶ����Ƥ���������
�ž夲
�ޤ� top �ڡ����Υϥ�ɥ顼���Ť��ä��Τǡ��Ƥ� zero time cache �ѥ������ȤäƤ���˥ȥåץڡ��������Ƥμ������Ψ�����ޤ���
���������ΤȤ��ϸ�Ψ�������μ��ᤵ��ͥ�褷�ơ� zero time cache �ѥ������ȥåץڡ����Υϥ�ɥ����ľ�ܼ������Ƥ��ޤ��ޤ�������¤ˤ���ǽ���夷�ޤ�����������â¥ï¿½ï¿½ï¿½ï¡¼ï¿½É¤Î¹ï¿½ï¿½ï¿½Â¦ï¿½Ë½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Ã¤Æ¤ï¿½ï¿½ï¿½ï¿½Ð¤ï¿½Ã¤È·ï¿½Åªï¿½ï¿½ï¿½ï¿½Ç½ï¿½ï¿½ï¿½å¤¬ï¿½ï¿½ï¿½ï¿½ï¿½á¤¿ï¿½Ï¤ï¿½ï¿½Ç¤ï¿½ï¿½ï¿½
�Ǹ�ˡ����ץ�δĶ��ѿ��� GOGC=400 �����ꤷ���ץ��ե�������˥���󥰤�nginx�Υ��������������ڤꡢ�٥��ȥ�������Ф��ޤ� enqueue �����¹Ԥ��ƽ�λ���ޤ�����
�ͻ�
POST /keyword
�Υ���������������ɤ��ɲä����ǤϤʤ������ˤ�Ȥ��Ƥ��뤳�Ȥ�ƨ���Ƥ��ơ����ֽŤ� Replacer �κƹ��ۤ�ɬ�װʾ�˼¹Ԥ��Ƥ��ޤäƤ����Τ�����ޤ�ޤ���
�ޤ�����˽񤤤��褦�˥ȥåץڡ��������Ƥι����⡢ GET /
�ǤϤʤ� POST /keyword
�ǹԤ��Ф�äȷ�Ū�ʥ��������åפ��Ǥ����Ǥ��礦��
����ˡ����������桼�����Υȥåץڡ�������Ū�ե�����˽ñ¤½Ð¤ï¿½ï¿½Æ¤ï¿½ï¿½Þ¤Ã¤ï¿½ nginx ��ľ���֤�����Τ⡢ISUCON���ѽФι�άˡ�ǻ�����Ƚ�äƤ����Ϥ��ǡ�����������֤⤢�ä��Ϥ��ʤΤˤ�륿���ߥ󥰤�路�Ƥ��ޤ��ޤ�����
�����������Ǥ��Ƥ���С��٥���ޡ������μ�������Ǥ������ܤ�50�����β�ǽ���⤢�ä����⤷��ޤ���
����
���ޤǤ���ǯͽ���ϰ�ͤ���äƾ���ȴ���Ƥ����ΤǤ�������ǯ��ͽ���Υܥ�塼�ब�����Ʊ�����餤���äƥ��Ĥ��ä��Τǡ���ǯ��ͽ�������������臘���Ȥˤ��ޤ�����
��ľ�����Ϥ��ΥХ������ä��Υ֥����Ǥ�ľ�äƤʤ��ä��ס֥ץ��ե����뤬�㤦���ΥХ��ʥ꡿�������򻲾Ȥ��Ƥ����⤷��ʤ��Ƥ��ƥ��Ƥˤʤ�ʤ��׷ϤΥȥ�֥�Ϥ��ä���ΤΡ���ʬ�����ץ��񤯤Ȥ��˿��ۻ���˺��ƽ���Ǥ����ꡢ�դ˼�����Ǥ���Ƥ�֤˼�ʬ�ϵٷƤ���������夤�Ƹ�ľ���򤷤���Ǥ��ơ�����Ū����٤������˸��ꡢ;͵����ä�ͥ�����廊���Ȼפ��ޤ���
�����ǡ�Azure�˴���Ƥ��ʤ��ä����Ȥ䡢14���᤮�˼�ä����Ƥ�0���ʳ��Υ����������λ������ǥȥĥȥåפǡ����������ۤܤ��ä�2�̤Υ��֥륹�����ò¥¡ï¿½ï¿½×¤ï¿½ï¿½Æ¤ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½È¤â¤¢ï¿½Ã¤Æ¡ï¿½ï¿½Ý¼ï¿½Åªï¿½Ë¤Ê¤ê¤¹ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½Ï¤ï¿½Ð¤ï¿½ï¿½Ú¤ì¤¿ï¿½È¤Ï¸ï¿½ï¿½ï¿½ï¿½ñ¤¤¤Ç¤ï¿½ï¿½ï¿½
����Ǥ����Ϥ�Ф���褦�ˡ��⤦������������������Ķ����ۤ�������ŤͤƹԤ��ޤ���
����Ǥϡ��辡�ʽмԤΤߤʤ��󡢷辡�Ǥ��񤤤��ޤ��礦�� ���ġ�����ԤΤߤʤ���Ϥ�����ͤǤ������辡�����³�����ꤤ�������ޤ���
�ȥ�å��Хå�URL
���ε����ؤΥ�����
�����餷�������򤢤꤬�Ȥ��������ޤ�����
�������󲽤Υƥ��˥å�����ʬ�Ǥ�time.Now��Ȥ�����len(kwdList)��Ȥ�����ʬ����䤹���󤸤�ʤ����Ȼפ��ޤ���
Go��time.Now�ϼ����Ȥ��Ƥ�monotonic���Ȥϻפ��ΤǤ�����̤��ǧ�ˡ����ʤ��Ȥ��ɥ�����Ⱦ���ݾڤ���Ƥ��ʤ��褦�˻פ��ޤ���
�����reps1, reps2�ν��������len(kwdList)����Ѥ��Ƥ��ޤ����������lock�Ǽ�ä������ɤ��ΤǤ�? append��len�˶����̵���ΤǤ��礦����
�ºݤˤϥ�����ɤ�������ؿ��⤢��Τ��ɲäȺ����1�Ĥ��ļ¹Ԥ�����
len(kwdList) ��Ʊ���ˤʤäƤ��ޤ��ޤ���
�ޤ�������� kwdList �˷׻���ɬ�פʻ񸻤����äƤ��ޤ����������Υ��ǥ����������Ū��
�ǡ����١����˽Ť���������ꤲ��ʤɤǤ����Ѳ�ǽ�ǡ� time.Now() ��Ȥä��ۤ���
�����ɥ��˥ڥåȤȤ��Ƥκ����������⤤�Ǥ���
time.Now() �ϥ�Υȥ˥å��ˤϤʤäƤ��ޤ��� Google ���Ǥϡ����פ򴬤��᤹�ۤ���
�����Ȥ������Ȥˤʤ�ΤǤ��礦�͡�
�ʤΤǼ�ư�ǻ��׹�碌�򤹤������������դ�ɬ�פǤ���
�ʻ�Ϥ����դ�����˾ܤ����ʤ��ΤǼ��䤵��Ƥ��������ޤ��󤬡�
�ºݤˤϥ�����ɤ�������ؿ��⤢��Τ��ɲäȺ����1�Ĥ��ļ¹Ԥ�����
len(kwdList) ��Ʊ���ˤʤäƤ��ޤ��ޤ���
�ʤ�ۤɡ�ABA����ˤʤäƤ��ޤ���Ǥ��͡�
> �ޤ�������� kwdList �˷׻���ɬ�פʻ񸻤����äƤ��ޤ����������Υ��ǥ����������Ū��
�ǡ����١����˽Ť���������ꤲ��ʤɤǤ����Ѳ�ǽ�ǡ� time.Now() ��Ȥä��ۤ���
�����ɥ��˥ڥåȤȤ��Ƥκ����������⤤�Ǥ���
�Τ��ˤ����̤�Ǥ��͡�
> time.Now() �ϥ�Υȥ˥å��ˤϤʤäƤ��ޤ��� Google ���Ǥϡ����פ򴬤��᤹�ۤ���
�����Ȥ������Ȥˤʤ�ΤǤ��礦�͡�
�ʤΤǼ�ư�ǻ��׹�碌�򤹤������������դ�ɬ�פǤ���
�ʻ�Ϥ����դ�����˾ܤ����ʤ��ΤǼ��䤵��Ƥ��������ޤ��󤬡�
���������ʤ�Ǥ��͡��μ¤˥��ߥåȤ��������ϼ�����syscall�䥫���󥿡��ʤɤ��Ѱդ���ɬ�פ�Í��Ȥ������Ǥ����͡��ͤ�褯ʬ���äƤ��ޤ��󤬡�
���������꤬�Ȥ��������ޤ�����