<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>kakao 기술 블로그</title>
    <description>Connect Everything 새로운 연결, 새로운 세상.
연결의 혁신으로 세상은 더욱 가깝고 새로워진다고 카카오는 믿습니다.
</description>
    <link>http://tech.kakao.com/</link>
    <atom:link href="http://tech.kakao.com/rss" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 16 Nov 2016 03:40:34 +0900</pubDate>
    <lastBuildDate>Wed, 16 Nov 2016 03:40:34 +0900</lastBuildDate>
    <generator>Jekyll v3.3.0</generator>
    
      <item>
        <title>테스트 블로깅</title>
        <description>&lt;p&gt;KEMI(&lt;strong&gt;K&lt;/strong&gt;akao &lt;strong&gt;E&lt;/strong&gt;vent &lt;strong&gt;M&lt;/strong&gt;etering &amp;amp; mon&lt;strong&gt;I&lt;/strong&gt;toring)는 카카오의 전사 리소스 모니터링 시스템 입니다.
서버, 컨테이너와 같은 리소스의 메트릭 데이터를 수집해서 보여주고 설정한 임계치에 따라 알림을 보내주는 KEMI-STATS과 ETL을 통해 수집한 log를 대시보드 형태로 보여주거나 실시간 알림을 할 수 있는 KEMI-LOG로 구성되어 있습니다.&lt;/p&gt;

&lt;h3 id=&quot;kemi-stats&quot;&gt;KEMI-STATS&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/files/kemi-stats.jpg&quot; alt=&quot;KEMI-STATS&quot; /&gt;
KEMI-STATS는 수만대에 이르는 카카오의 전체 서버와 컨테이너 서비스를 모니터링하는데 이용되고 있으며 polling방식과 push방식 두가지를 사용합니다. 
리소스 중 서버(physical machine, virtual machine, amazon ec2)의 경우 polling방식으로 SNMP를 이용하여 시스템 메트릭을 수집합니다. 
데이터를 수집하는데 여러가지 방식이 있을 수 있지만 SNMP를 기본으로 선택한 이유는 서버의 운영체제와(linux/windows/nw switch) 상관없이 모니터링하기 위해서 입니다.&lt;/p&gt;

&lt;p&gt;polling 방식의 수집은 젠킨스 배치 job을 이용해서 1분마다 아래와 같은 순서로 실행됩니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;job이 시작되면 KEMI의 Job Producer가 IMS(Infrastructure Management System)라는 카카오 인프라 관리 시스템에서 데이터를 가져올 대상을 받아와서 kafka의 polling job queue topic에 넣어 놓습니다.
(이렇게 매 주기마다 호스트 목록을 새로 가져오는 이유는 서버가 추가되거나 빠지는걸 바로 반영하기 위해서 입니다.)&lt;/li&gt;
  &lt;li&gt;polling job queue topic을 보고 있던 KEMI의 Poller가 각 대상들에서 시스템 메트릭을 가져와서 다시 kafka에 저장합니다.
(이때 그 호스트가 속한 서비스가 어떤 메트릭들을 수집할지를 etcd에 저장해 두고 사용합니다. 그래서 추가로 필요한 메트릭이 있을때 Poller를 재시작하지 않고도 etcd에 있는 정보만 업데이트하면 수집할 수 있습니다.)&lt;/li&gt;
  &lt;li&gt;이 데이터들은 값을 그대로 이용할 수 있는 것(CPU usage 등)과 계산이 필요한 것(DISK usage 등)의 2종류가 있게 되며 samza를 이용한 KEMI의 Metric Calculator가 계산이 필요한 것은 계산을 해서 그외 는 그대로 다시 kafka에 넣게 됩니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;push 방식의 수집은 컨테이너 리소스 모니터링과 SNMP가 지원되지 않는 서버에서 사용되고 있으며 아래와 같은 순서로 실행됩니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;시간, 리소스 아이디, 시스템 메트릭을 KEMI의 Stats Agg로 push&lt;/li&gt;
  &lt;li&gt;KEMI의 Stats Agg에서는 이 메트릭들을 kafka에 저장합니다.&lt;/li&gt;
  &lt;li&gt;일부 계산이 필요한 메트릭은 polling방식과 마찬가지로 KEMI의 Metric Calculator에 의해 계산되어 저장되고 그외 메트릭은 그대로 kafka에 저장됩니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;polling 또는 push방식으로 수집된 데이터는 아래와 같은 순서로 View를 위해 Time Series DB에 저장되고 룰에 따라 알람이 수행됩니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;준비된 최종 데이터는 KEMI의 Metric Forwarder를 통해서 OpenTSDB에 저장됩니다. 
(OpenTSDB에 들어간 데이터는 Grafana를 이용하여 사용자들이 그래프 형태로 볼수 있게 됩니다.)&lt;/li&gt;
  &lt;li&gt;KEMI Event Alert를 통해서 etcd에 정해진 룰에 따라 알람 이벤트를 생성하여 다시 kafka에 넣습니다.
(CPU 사용량이 90% 이상라던가 네트워크 트래픽이 떨어졌다던가 할때 개별 호스트 단위/여러 호스트를 묶은 서비스 단위로 알람 이벤트를 생성할 수 있습니다.)&lt;/li&gt;
  &lt;li&gt;KEMI Event Handler는 kafka에 생성된 알람 이벤트를 가지고 kakaotalk, custom api 호출 등의 알람 서비스를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이게 KEMI-STATS의 기본적인 구성입니다.&lt;/p&gt;

&lt;p&gt;그리고 위의 실시간 스트리밍 데이터를 이용한 알람 외에 수집된 데이터를 이용한 리소스 효율화 서비스도 제공하고 있고 그 순서는 아래와 같습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Cuota Collect를 이용해서 주기적으로 특정 시간 간격의 데이터를 샘플링하여(시간당 max/min 값) MySQL에 저장합니다.&lt;/li&gt;
  &lt;li&gt;MySQL의 샘플링 데이터를 Cuota Report가 확인해서 실제 서버의 사용량을 확인하고 이 중 사용량이 작은 VM(Virtual Machine)을 주기적으로 파악해서 담당자에게 알림을 주어서, 해당 VM을 삭제하거나 다른 VM들과 통합하게 함으로써 시스템을 보다 효율적으로 사용하는데 이용하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 외에도  SNMP의 oid를 확장하여 SNMP에서 제공해주는 기본 메트릭 외에 다양한 커스텀 메트릭을 수집할 수 있고, push방식으로 메트릭 데이터를 넣을 때 사용자가 만든 메트릭을 넣을 수 있습니다.
현재 KEMI-STATS의 이러한 확장성을 이용해서 시스템의 보드의 온도, haproxy, nginx, memcached, redis 등의 stats 정보, 컨테이너 등의 상태를 모니터링하기 위해 필요한 커스텀 메트릭이 수집되고 있습니다.&lt;/p&gt;

&lt;h3 id=&quot;kemi-log&quot;&gt;KEMI-LOG&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/files/kemi-log.jpg&quot; alt=&quot;KEMI-LOG&quot; /&gt;
KEMI-LOG는 각 서비스에서 발생한 로그를 모아서 저장하고 보여주는 기능과 로그 별로 설정된 룰에 따라 알람을 발생시켜 줍니다.
인프라운영에 필요한 기본적인 syslog나 네트워크 관련 로그들을 받고 있으며, 필요에 따라 각 서비스들에서 KEMI-LOG쪽으로 로그 데이터를 보내서 이용하고 있으며 그 규모는 하루 수백기가 정도입니다.
KEMI-LOG의 데이터 흐름은 아래와 같습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;서비스 별 로그의 경우 각 서버에 설치된 fluentd를 이용하고 syslog나 네트워크 관련 로그는 syslog의 target 설정을 통해 consul domain 으로 엮여진 KEMI Aggregator로 전달됩니다.&lt;/li&gt;
  &lt;li&gt;이렇게 보내진 데이터는 KEMI-LOG에 aggregator 역할을 해주는 fluentd 서버그룹들이 받은 다음에 그 로그들을 각각 hadoop, kafka에 넣어줍니다.&lt;/li&gt;
  &lt;li&gt;hadoop에 저장된 데이터는 hive batch job을 통해 주기적으로 (5~15분) elasticsearch cluster로 indexing되며 kibana를 통해 사용자가 조회할 수 있게 됩니다.&lt;/li&gt;
  &lt;li&gt;kafka에 저장된 데이터는 etcd의 알람 룰과 STORM, redis를 활용해 개발된 KEMI Dike를 통해 실시간으로 알림을 발생시킵니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;위 데이터 흐름에서 선택하거나 개발된 몇가지 기술들과 방법은 아래와 같은 장점을 가집니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;fluentd는 다양한 플러그인들이 존재해서 간단한 설정만으로도 손쉽게 원하는 형태로 로그 데이터를 변환해서 주고 받을 수 있습니다.&lt;/li&gt;
  &lt;li&gt;전체 aggregator 호스트를 service discovery 도구인 consul로 관리되는 도메인을 바라보게 되어 있어서, 각 서버에 있는 fluentd의 설정을 변경하지 않은채로 KEMI Aggregator에 서버를 추가하거나 빼는 작업을 할 수 있어서 손쉬운 scale in/out이 가능합니다.&lt;/li&gt;
  &lt;li&gt;hadoop의 안정적인 데이터 저장 제공으로 elasticsearch가 문제가 생겼더라도 재처리와 bulk insert로 인해 보다 많은 양의 로그를 indexing 처리할 수 있습니다.&lt;/li&gt;
  &lt;li&gt;로그 알림에 사용하는 rule은 표준 SQL 형식으로 사용자가 지정할 수 있고, rule을 etcd에 저장해두기 때문에 storm topology의 재시작없이 변경사항이 동적으로 적용됩니다.&lt;/li&gt;
  &lt;li&gt;발생한 알림은 커스텀 메트릭의 형태로 KEMI-STATS쪽에 저장해서 대시보드를 구성해서 본다거나 KEMI-STATS과 KEMI-LOG의 통합 알림에도 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;KEMI 개발은 issac.lim, hardy.jung, jenny.ssong, joanne.hwang, andrew.kong 이 
함께하고 있으며 카카오 인프라&amp;amp;데이플랫폼팀의 많은 지원을 받아 운영되고 있습니다.
끝으로…
위 내용이 모니터링 서비스를 개발하고 계신 분들께 조금이라도 도움이 되면 좋을 것 같습니다.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Thu, 25 Aug 2016 14:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/08/25/kemi/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/08/25/kemi/</guid>
        
        <category>go</category>
        
        <category>kafka</category>
        
        <category>storm</category>
        
        
      </item>
    
      <item>
        <title>CODING BATTLE 가위바위보! - 못다한 이야기</title>
        <description>&lt;h2 id=&quot;section&quot;&gt;어쩌다보니 키스톤!&lt;/h2&gt;

&lt;p&gt;지난 8월 13일(토)부터 이틀간 코엑스 그랜드에서 열렸던 &lt;a href=&quot;https://www.pycon.kr/2016apac/&quot;&gt;PyCon 2016 APAC&lt;/a&gt;에서 
카카오 부스를 지켰던 &lt;a href=&quot;https://github.com/iolo&quot;&gt;iolo.fitzowen&lt;/a&gt; 입니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;키스톤 스폰서&lt;/strong&gt; 자격으로 행사장에서 가장 큰 부스를 운영하게되었는데,
거대한 부스를 어떻게 활용할 것인가를 오랫동안 고민했습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/gawibawibo-booth.png&quot; alt=&quot;카카오 부스 이모저모&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;section-1&quot;&gt;어쩌다보니 가위바위보!&lt;/h2&gt;

&lt;p&gt;부스 이벤트로 코딩 퀴즈를 하기로 하고
사내 그룹웨어인 아지트를 통해 문제를 추천 받았는데,
&lt;strong&gt;&lt;a href=&quot;&quot;&gt;bryan.j&lt;/a&gt;가 제안한 가위바위보 AI 대전&lt;/strong&gt; 아이디어을 다듬어
&lt;strong&gt;CODING BATTLE 가위바위보!&lt;/strong&gt;라는 이름의 행사를 진행했습니다.
(이 자리를 빌어, 멋진 아이디어를 주신 bryan.j, 그리고 채택되지 않았지만 다양한 의견을 주신 여러분께 감사드립니다.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/gawibawibo-poster.png&quot; alt=&quot;CODING BATTLE 가위바위보! 포스터&quot; /&gt;&lt;/p&gt;

&lt;p&gt;자세한 내용은 이벤트 페이지 &lt;a href=&quot;http://tech.kakao.com/pycon2016apac&quot;&gt;CODING BATTLE 가위바위보! in 파이콘 2016 APAC&lt;/a&gt;를 참고하시고,
이 글에서는 파이썬 초보(!)가 코딩 이벤트를 진행하면서 겪었던 에피소드를 인상적인 소스 코드와 함께 전해드리겠습니다.&lt;/p&gt;

&lt;h2 id=&quot;making-film&quot;&gt;1부: MAKING FILM&lt;/h2&gt;

&lt;h3 id=&quot;ver1-----&quot;&gt;Ver1. 클라우드 서비스 + 네트웍 대전&lt;/h3&gt;

&lt;p&gt;최초의 아이디어는 &lt;strong&gt;서버-to-서버 HTTP 통신을 이용하는 방식&lt;/strong&gt;이었습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;(제가) 게임 진행 서버(host-server)를 개발하고,&lt;/li&gt;
  &lt;li&gt;(참가자들은) 플레이어 서버(player-server)를 개발해서 (무료) 클라우드 서비스에 올리고,&lt;/li&gt;
  &lt;li&gt;(제가 만든) 진행 서버가 (참가자들이 만든) 두 개의 플레어어 서버가 HTTP 통신을 하면서 게임을 진행하고,&lt;/li&gt;
  &lt;li&gt;진행 서버에 브라우저로 접속해서 생방송(websocket)으로 볼 수 있는…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;방식이었습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/235f17cb451b46cf1769668f50ae59981667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;보신 분들은 없겠지만, 이 방식으로 구현된 버전이 행사 이틀 전에 &lt;a href=&quot;https://github.com/kakao&quot;&gt;카카오의 깃헙&lt;/a&gt;에 잠깐 올라가 있었습니다^^;&lt;/p&gt;

&lt;h3 id=&quot;ver2--------back-to-the-199x&quot;&gt;Ver2. 이메일 접수 + 로컬 대전 - Back to the 199x&lt;/h3&gt;

&lt;p&gt;행사 전날 오후, 회사 카페에서
&lt;a href=&quot;https://charsyam.wordpress.com&quot;&gt;clark.kang&lt;/a&gt;과
&lt;a href=&quot;http://www.slideshare.net/yongho&quot;&gt;henry.ha&lt;/a&gt;를 우연히 만나
(그렇습니다! 카카오에 입사하시면… 이런 연예인(?)들은 매일 우연히 마주칠 수 있습니다!)
파이콘 이벤트에 대해 얘기를 나누다가…
&lt;strong&gt;코엑스의 열악한 네트웍 환경에서 클라우드 서버에 올리는 삽질의 무의미함&lt;/strong&gt;에 대한 날카로운 지적(!)을 받고,
&lt;strong&gt;로컬 환경에서 개발하고 테스트하고 대결할 수 있는 방식&lt;/strong&gt;으로 변경하기로 전격 결정!!!&lt;/p&gt;

&lt;p&gt;그날 밤을 꼴딱 새서 새로운 &lt;a href=&quot;https://github.com/kakao/pycon2016apac-gawibawibo&quot;&gt;게임 진행 서버&lt;/a&gt;를 만들었습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/477c52636630bc15b2890bde099cba0a1667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;(참가자들은) &lt;code class=&quot;highlighter-rouge&quot;&gt;show_me_the_hand&lt;/code&gt;라는 괴상한 이름의 파이썬 함수를 작성하고,&lt;/li&gt;
  &lt;li&gt;(참가자들은) 작성한 코드를 &lt;strong&gt;이메일&lt;/strong&gt;을 통해 &lt;code class=&quot;highlighter-rouge&quot;&gt;player.py&lt;/code&gt;라는 첨부파일로 제출하면,&lt;/li&gt;
  &lt;li&gt;(옆에 앉아있던 &lt;a href=&quot;&quot;&gt;violet.blue&lt;/a&gt;와 &lt;a href=&quot;&quot;&gt;yally.next&lt;/a&gt;가) &lt;strong&gt;수작업으로&lt;/strong&gt; 메일박스를 확인해서 &lt;code class=&quot;highlighter-rouge&quot;&gt;player.py&lt;/code&gt; 파일을 모아서 정리하고,&lt;/li&gt;
  &lt;li&gt;(제가) 지난 밤에 발로 만든 &lt;a href=&quot;https://github.com/kakao/pycon2016apac-gawibawibo&quot;&gt;게임 진행 서버&lt;/a&gt;를 사용해 게임을 진행하는…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;흠흠… 그러니까 여러분들이 보신… (웹브라우저와 터미널을 번갈아 보여주면서 생목으로 고함을 지르는) 바로 그 방식입니다^^;;&lt;/p&gt;

&lt;h3 id=&quot;day-1-of-the-random-by-the-random-for-the-random&quot;&gt;Day 1. of the random, by the random, for the random&lt;/h3&gt;

&lt;p&gt;방식을 급히 바꾸면서 사건사고도 많았습니다.&lt;/p&gt;

&lt;p&gt;카카오 부스를 방문한 &lt;a href=&quot;https://www.jetbrains.com&quot;&gt;JetBrains&lt;/a&gt; 직원이 콧방귀를 뀔 정도로 전근대적인 방법이지만,
그만큼 확실하다고 믿었던 메일이 백본네트웍 장애라는 전대미문의 장애로 첨부파일이 손상되는 초유의 사태가 발생했습니다.
오후 4시가 되서야 참가자 9명의 첨부 파일이 모두 복구됐고… 9명을 추가해서 예선전을 다시 치렀죠. 덕분에 결과 발표도 5시로 연기.&lt;/p&gt;

&lt;p&gt;(옆에 앉아었던 &lt;a href=&quot;&quot;&gt;violet.blue&lt;/a&gt;가) 웹을 뒤져서 받은 음성 파일과 손바닥 이미지 파일을, 
(바로 옆에서 &lt;a href=&quot;http://www.smartstudy.co.kr&quot;&gt;스마트스터디&lt;/a&gt; 부스를 지키고 있던) &lt;a href=&quot;https://blog.outsider.ne.kr&quot;&gt;outsider&lt;/a&gt;님에게 넘겨주고 30분 남짓 개발한 것을,
(제가) 대충 갖다 붙여서 (수동으로 버튼을 누르면 나타나고/사라지는) &lt;strong&gt;안내면술래 가위바위보! 보! 보! 보!&lt;/strong&gt; 애니메이션도 준비했습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/gawibawibobobobo.png&quot; alt=&quot;안내면술래 가위바위보! 보! 보! 보!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;첫 날은 참가 신청자가 100명을 넘지 않아,
메일만 제 때 도착했다면 &lt;a href=&quot;https://store.kakaofriends.com/store/kr/product/product.lime?r_ctseq=23&amp;amp;r_prcode=FRPBRYMBG0181&quot;&gt;라이언 캐릭터 목베개&lt;/a&gt;를
받을 수 있었습니다. 무더운 날씨에도 불구하고 목배게를 목에 걸고 다니시던 모습을 보며… 참 뿌듯(?) 했었죠.&lt;/p&gt;

&lt;p&gt;시험적으로 몇판 돌려보니, 라이브로 해도 되겠다고 판단하고, 예정보다 한시간 늦은 오후 5시, 랩탑을 TV에 연결하고 &lt;strong&gt;라이브&lt;/strong&gt;로 대본없이 진행했습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/gawibawibo-day1-live.jpg&quot; alt=&quot;Day 1. 게임 결과 발표 라이브&quot; /&gt;&lt;/p&gt;

&lt;p&gt;결과는 아시다시피(예상대로) &lt;strong&gt;random&lt;/strong&gt;의 완승이었습니다.&lt;/p&gt;

&lt;p&gt;진지하게(?) 접근했던 참가자들은 그 결과에 분노(?)했고, 저도 마찬가지였습니다.
이벤트를 준비하면서 수억번의 가위바위보를 했지만, 랜덤을 이길 방법은 쉽게 보이지 않았습니다. 정말 없을까요?&lt;/p&gt;

&lt;p&gt;그래서!&lt;/p&gt;

&lt;p&gt;2일차는 규칙을 바꿔보기로 했습니다. 파이썬 기본으로 제공하는 &lt;code class=&quot;highlighter-rouge&quot;&gt;random&lt;/code&gt;과 외부 모듈을 사용하지 않고, 코드로 정면 승부!!&lt;/p&gt;

&lt;h3 id=&quot;day-2-algorithm-strikes-back&quot;&gt;Day 2. algorithm strikes back&lt;/h3&gt;

&lt;p&gt;다음 날 아침, 행사장에 도착하니, 이미 참가 접수 메일이 들어오고 있었습니다.(순진한 개발자들, 이제야 선착순의 의미를 깨달았어…)
점심때 쯤엔 참가 신청 메일이 100통을 넘었습니다.(어랏 장난이 아닌데…)
접수를 중단해야 할지를 고민하다가, 경품(라이언 목베개)은 줄 수 없지만, 게임에는 참가할 수 있는 것으로 결정했습니다.&lt;/p&gt;

&lt;p&gt;오후 2시, 참가 접수를 마감하고 시험삼아 몇 게임씩 돌리던 중,
몇몇 플레이어들의 특이한 결과를 확인하고,
코드들 뜯어보니 &lt;strong&gt;파이썬 마법의 열쇠&lt;/strong&gt; 더블언더스코어(&lt;code class=&quot;highlighter-rouge&quot;&gt;__&lt;/code&gt;)가 여기저기 흩어져 있더군요.
파이썬 초보인 저로써는 의도 조차 파악하기 힘들어, 파이썬 고수들을 찾아갔습니다.
자원봉사자로 참여하고 있던 &lt;a href=&quot;https://github.com/ganadist&quot;&gt;ganadist&lt;/a&gt;님, 정지오님, 서승효님을 비롯한 몇몇 분들의 도움을 받아
적들의(?) 의도와 막을 수 있는 방법을 급히 확보했습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/09632d0f859f20907e11d3d4ec51e95b1667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;게임 결과 발표 시간이 임박해서 일부 참가자들의 코드는 확인하지 못한채 발표를 시작했는데….
아니나 다를까… 확인하지 못한 코드들이 말썽을 부렸습니다.
 175명이 풀리그를 치르려면 15,225 게임(1,522,500 번의 가위바위보)를 해야 하는데… 매번 &lt;code class=&quot;highlighter-rouge&quot;&gt;sleep(.1)&lt;/code&gt;을 하면… ㅠㅠ
가위바위보 한 번 결정하는데 1초 이상이 걸리는 플레이어도 있었습니다. 이 플레이어도 공평하게 174 게임(17,400 번의 가위바위보)를 해야 했죠.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/gawibawibo-day2-live.jpg&quot; alt=&quot;Day 2. 게임 결과 발표 라이브&quot; /&gt;&lt;/p&gt;

&lt;p&gt;그래도 기다려보자!라고 버텼지만, 저의 오기를 비웃 듯 행사장의 전원이 꺼졌습니다.
CPU 사용량 100%의 상태에서 랩탑의 배터리는 2시간도 못버틸테고, 그때까지 결과가 나올 가능성은 없었습니다.
결국, 2일차 결과 발표를 연기할 수 밖에 없었습니다.
부피를 최소화하기 위해 래핑된 라이언의 표정이 슬프…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/gawibawibo-day2-ryan.jpg&quot; alt=&quot;Day 2. 게임 결과 발표 연기 때문에 귀를 잃어버린 라이언&quot; /&gt;&lt;/p&gt;

&lt;p&gt;참가자들의 의욕을 과소평가한… 저의 불찰입니다.
이 자리를 빌어서 기다려주신 많은 참가자 분들께 사과드리고, 또 감사드립니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/46d01f5ff600e23a970e9ce86c02eff81667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;마무리를 짓지 못하고 찝찝한 마음과 지친 몸을 끌고 섬으로 돌아오면서,
제시한 룰을 깨트리지 않고 어뷰징을 막을 수 있는 방법을 고민했는데,
&lt;a href=&quot;https://github.com/ganadist&quot;&gt;ganadist&lt;/a&gt;님께서 &lt;a href=&quot;https://gist.github.com/ganadist/de618f92897144273586648bc07cfd7b&quot;&gt;선물&lt;/a&gt;을 보내왔습니다.&lt;/p&gt;

&lt;p&gt;우여곡절 끝에, 오늘에야 2일차 게임 결과를 발표합니다(두둥!):&lt;/p&gt;

&lt;h4 id=&quot;day2------&quot;&gt;Day2. 게임 결과 발표 - 예선(풀리그)&lt;/h4&gt;

&lt;p&gt;2일차 오후 2시까지 접수된 188명 중에서, 예제를 복사해서 낸 5명, &lt;code class=&quot;highlighter-rouge&quot;&gt;random&lt;/code&gt;을 사용한 8명을 뺀
175명(1일차 참가자 32명 포함)의 코드가 접수되었고,
&lt;code class=&quot;highlighter-rouge&quot;&gt;input&lt;/code&gt; 함수를 사용하거나(키보드 입력 대기) &lt;code class=&quot;highlighter-rouge&quot;&gt;show_me_the_hand&lt;/code&gt; 함수가 없는 11명을 제외한
164명이 예선에 참가했습니다.&lt;/p&gt;

&lt;p&gt;예선은 풀리그 방식으로 164명의 플레이어가 다른 163명과 게임을 진행합니다.
총 13,366(164*163/2) 게임, 1,336,60 번의 가위바위보를 하는 거죠.
이 글을 쓰고 있는 시점에 6시간째 돌아가고 있군요. 12,452 번째 게임이니까 끝이 보입니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/9e7a61ac86c673b1e6a5bbe2cde7ff791667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;예선전 시작 동영상(&lt;strong&gt;용량이 큽니다. 데이터 요금에 주의하세요&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;혼자 회의실에 앉아 대사를 읊으려니 여간 쑥스러운게 아닙니다.&lt;/p&gt;

&lt;video controls=&quot;true&quot; crossorigin=&quot;anonymous&quot; style=&quot;width:100%;&quot;&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part1-fullleague.webm&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part1-fullleague.ogv&quot; type=&quot;video/ogg&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part1-fullleague.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;h4 id=&quot;day2-------1&quot;&gt;Day2. 게임 결과 발표 - 본선(토너먼트)&lt;/h4&gt;

&lt;p&gt;본선 32강전 동영상(&lt;strong&gt;용량이 큽니다. 데이터 요금에 주의하세요&lt;/strong&gt;)&lt;/p&gt;

&lt;video controls=&quot;true&quot; crossorigin=&quot;anonymous&quot; style=&quot;width:100%;&quot;&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part2-round32.webm&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part2-round32.ogv&quot; type=&quot;video/ogg&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part2-round32.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;본선 16강전 동영상(&lt;strong&gt;용량이 큽니다. 데이터 요금에 주의하세요&lt;/strong&gt;)&lt;/p&gt;

&lt;video controls=&quot;true&quot; crossorigin=&quot;anonymous&quot; style=&quot;width:100%;&quot;&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part3-round16.webm&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part3-round16.ogv&quot; type=&quot;video/ogg&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part3-round16.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;본선 8강전 &amp;amp; 준결승 동영상(&lt;strong&gt;용량이 큽니다. 데이터 요금에 주의하세요&lt;/strong&gt;)&lt;/p&gt;

&lt;video controls=&quot;true&quot; crossorigin=&quot;anonymous&quot; style=&quot;width:100%;&quot;&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part4-round8to4.webm&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part4-round8to4.ogv&quot; type=&quot;video/ogg&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part4-round8to4.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;결승전 동영상(&lt;strong&gt;용량이 큽니다. 데이터 요금에 주의하세요&lt;/strong&gt;)&lt;/p&gt;

&lt;video controls=&quot;true&quot; crossorigin=&quot;anonymous&quot; style=&quot;width:100%;&quot;&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part5-final.webm&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part5-final.ogv&quot; type=&quot;video/ogg&quot; /&gt;
    &lt;source src=&quot;https://mk.kakaocdn.net/dn/osa/pycon2016apac/day2-part5-final.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;h2 id=&quot;section-2&quot;&gt;코드 리뷰 - 그것이 알고싶다&lt;/h2&gt;

&lt;p&gt;이제부터 본론입니다.&lt;/p&gt;

&lt;h3 id=&quot;day1---4&quot;&gt;Day1. 우승, 준우승, 4강&lt;/h3&gt;

&lt;p&gt;참가하신(구경하신) 분들은 아시다시피 &lt;strong&gt;random의 완승&lt;/strong&gt;이었습니다. 그게 가위바위보 게임의 본질이니까 이상할 건 없죠. 그래도…&lt;/p&gt;

&lt;h4 id=&quot;by-k239507&quot;&gt;우승자의 코드 by k239507&lt;/h4&gt;

&lt;p&gt;차암 쉽죠? &lt;code class=&quot;highlighter-rouge&quot;&gt;가위&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;바위&lt;/code&gt;만 내는 전략입니다.
그런데 이 전략이 (수학적인 설명은 못하지만) 대회 전에 제가 알고 있던 (상대방의 대전 기록을 분석하지 않고) &lt;strong&gt;랜덤을 이기는 유일한 전략&lt;/strong&gt;이었습니다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by-hulk&quot;&gt;준우승자의 코드 by hulk&lt;/h4&gt;

&lt;p&gt;크게 다르지 않습니다. &lt;code class=&quot;highlighter-rouge&quot;&gt;가위&lt;/code&gt;를 낼 확률이 좀 더 높아졌네요.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by-hahanbyul&quot;&gt;3위의 코드 by hahanbyul&lt;/h4&gt;

&lt;p&gt;역시 크게 다르지 않습니다. &lt;code class=&quot;highlighter-rouge&quot;&gt;가위&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;바위&lt;/code&gt;를 낼 확률이 좀 더 높아졌네요.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by-eclipse1725&quot;&gt;또 다른 3위의 코드 by eclipse1725&lt;/h4&gt;

&lt;p&gt;아! 이번엔 뭔가 조금 다르죠?
상품을 전달할때도 참가자의 아쉬움과 분노(랜덤에 지다니!)가 느껴져서 저도 같이 분노(랜덤에 지다니!)했었죠.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/d97c14883a6a6d849c7b15d68ed8c7271667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;check1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;check2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;gbb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;check1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;check2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;51&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;52&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;53&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;54&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;56&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gbb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
				&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gbb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
				&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gbb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;대부분 예제 코드와 동일하거나 약간 수정한 전략이었습니다만, 몇몇 참가자들은 참신한 시도를 했습니다.
그러나, 이런 참신한 시도들은 순수한 랜덤에 허무하게 무너졌는데요,
상대의 전략을 파악했다(패턴을 읽었다)고 생각하고 대응 전략을 결정한 순간… 진거죠. 있지도 않은 패턴을 읽은… 오해영!&lt;/p&gt;

&lt;h3 id=&quot;day2---4&quot;&gt;Day2. 우승, 준우승, 4강&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/872b56c06d9ac5bc264419e187b346041667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;1일차 참가자들의 코드는 대충봐도 대부분 의도를 파악할 수 있었는데,
2일차 참가자들의 코드는 저같은 파이썬 초보에겐 어림도 없었습니다.
단 하루만에 완전히 다른 게임이 되었습니다.
개발자들이 독한 마음을 먹으면 이렇게 무섭습니다.&lt;/p&gt;

&lt;h4 id=&quot;by-qkqnrpa&quot;&gt;우승자의 코드 by qkqnrpa&lt;/h4&gt;

&lt;p&gt;상대방이 직전에 냈던 손에 맞춰서 내는 전략인데요,
단순해 보이지만, 결과는 우승!!&lt;/p&gt;

&lt;p&gt;CODING BATTLE 가위바위보는 - 이 분의 아이디처럼 -  &lt;strong&gt;바부겜&lt;/strong&gt;이었을까요?&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gababo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gababo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gababo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by-johnjanghochoi&quot;&gt;준우승자의 코드 by johnjanghochoi&lt;/h4&gt;

&lt;p&gt;상대방이 직전에 냈던 손과 같은 손을 내면 이기는 손을 내는 전략입니다.
역시나, 이유를 설명할 순 없지만, 결과는 결과는 매우 성공적!&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by-bak723&quot;&gt;3위의 코드 by bak723&lt;/h4&gt;

&lt;p&gt;상대방이 낸 손에 따라 승리에 4, 무승부에 2, 패배에 1의 가중치를 계산하고,
가중치가 가장 높은 - 가장 승률이 높은 손을 내는 전략입니다.(맞나요?)
안정적인 전략이지만, 이런 식의 전략이 아무생각없는 단순 랜덤에게 허무하게 패하는 경우가 많았습니다.
그래도 결과가 좋습니다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drawn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drawn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loser&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;w_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;win&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;lose&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;win&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;lose&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;hands&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
		&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
		&lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;maxValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;maxValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hands&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by-bage79&quot;&gt;또 다른 3위의 코드 by bage79&lt;/h4&gt;

&lt;p&gt;대전 중에서 가장 많은 시간을 사용한 코드입니다. 동영상 녹화를 힘들게 만든 분이죠^^;
제 수준으로 의도 파악이 불가능하네요. 뭔가 어렵구나… 정도?
개인적으로는 우승하지 않을까… 기대를 했었는데…
의외로 단순한 전략에 허무하게 무너졌습니다.ㅠㅠ&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/ee57651cae091b135d3abf1f3d482a1d1667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;min_max_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# print('ratio[%s]=%.1f' % (k, ratio[k]))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;min_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_enemy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;max_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_enemy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;min_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_enemy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_enemy&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;check_pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;li1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;my_hand_from_enemy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# elif len(records) == 2:&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;#     return 'gawi'&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;enemy_by_pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enemy_by_pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# 상대방의 패턴을 읽어서, 대응해보자.&lt;/span&gt;
                &lt;span class=&quot;c&quot;&gt;# print('by pattern -&amp;gt; ', my_hand_from_enemy[enemy_by_pattern])&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_hand_from_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;enemy_by_pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# 패턴이 없다면..&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;min_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_enemy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_max_ratio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_enemy&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_ratio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# 많이 내는 종류가 있다면, 다시 낼 확률이 높다.&lt;/span&gt;
                    &lt;span class=&quot;c&quot;&gt;# print('by enemy max -&amp;gt; ', my_hand_from_enemy[max_enemy])&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_hand_from_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# 많이 내는 종류가 없다면, 안 낸 것을 낼 확률이 높다.&lt;/span&gt;
                    &lt;span class=&quot;c&quot;&gt;# print('by enemy min -&amp;gt; ', my_hand_from_enemy[min_enemy])&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_hand_from_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min_enemy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;본선 토너먼트에 오른 64명 중에서 승률 TOP 5을 보면:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;chang12(71%)&lt;/li&gt;
  &lt;li&gt;blacksangi(66%)&lt;/li&gt;
  &lt;li&gt;etaehyun4(66%)&lt;/li&gt;
  &lt;li&gt;novelview9(62%)&lt;/li&gt;
  &lt;li&gt;bage79(62%)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;가위바위보처럼 운에 의존하는 게임에서 60% 이상의 승률을 보였다는 것이 놀랍네요.
이 분들은 대진운이 안좋았거나 카운터 전략을 만났을 뿐… 너무 서운해 하지 않으셨으면…&lt;/p&gt;

&lt;p&gt;첫째날처럼 단순한 랜덤보다는 약간의 전략이 가미된 랜덤들이 많았고, 성적도 좋았습니다.
또한 상대방의 플레이를 교묘하게 따라 하는 전략들도 많았고, 성적도 좋았습니다.
복잡한 계산을 통해 상대방의 전략을 분석하고, 그에 따라 전략을 바꾸는 플레이어도 있었는데,
32강, 16강, 8강을 거치면서 의외로 단순한 전략을 오해해서(랜덤에서 패턴을 읽었다면… 오해영)
맥없이 무너지는 결과가 종종 있었습니다.
아직은 파악된 전략의 종류가 많지 않아서 효과적이지 않지만,
이런 대회를 계속 개최하면 어떻게 될까요?&lt;/p&gt;

&lt;p&gt;이걸로 끝내기엔 아쉬우니, 재미있는 코드를 몇가지 살펴보겠습니다:&lt;/p&gt;

&lt;h4 id=&quot;by-wesky93&quot;&gt;한글로 작성한 코드 by wesky93&lt;/h4&gt;

&lt;p&gt;아… &lt;code class=&quot;highlighter-rouge&quot;&gt;gawi&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;bawi&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;bo&lt;/code&gt;를 사용한 제 자신이 부끄러워지네요. &lt;code class=&quot;highlighter-rouge&quot;&gt;rock&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;scissors&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;paper&lt;/code&gt;도 아니고 말이죠 ㅠㅠ&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemgetter&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 하루동안 갑자기 베이지 활률을 공부해서.. 어떻게 될지는 모르겠네요..ㅋㅋ&amp;amp;땜방코드가 몇개 있습니다...&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# pycon2016 홧팅&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# p.s. 재미삼아 일부러 한글코딩 해봤습니다 ㅋ&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
작동구조
-------
1. 게임 플레이 30회 까지는 임의로 정한 가위,바위,보 순서에 맞게 차례대로 진행됩니다.
2. 30회 이후부터는 가위,바위,보에 대한 조건확률을 구하고 가장 높은 확률을 정답을 내보냅니다.
3. 간혹 운이 나뻐 패배를 하는데 그 격차가 상당하기에 격차를 줄이기 위하여 500회 ~ 1000회의 시점에서는 매회 승률을 계산하여 적의 승률이 40&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;이상일경우 알고리즘을 변경합니다.
4. 변경될 알고리즘은 상대방의 기준으로 유리한 행동을 고르고 해당 행동과 반대되는 행동을 내보냅니다.
&quot;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 내가 이길확률로 값을 결정&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적추적(전적):
    내전적 = []
    &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;기록&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;기록&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;거울&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;거울&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 내가 이길확률로 값을 결정&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부계수추가(승부계수,값):
    &quot;&quot;&quot; 승부여부 숫자값을 바탕으승부계수 딕셔너리에 값을 추가&quot;&quot;&quot;
    &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;값&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;err&quot;&gt;승부계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'승'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;값&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;err&quot;&gt;승부계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'무'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;err&quot;&gt;승부계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'패'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부계수&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;전적분석(전적):
    &quot;&quot;&quot;상대 전적을 분석줌하여 각 패의 활과승부계수을 알려줌&quot;&quot;&quot;
    # 행동계수 분석
    진행횟수 = &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;초기화&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;행동계수&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;초기화&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# 전적에서 각 행동이 발생한 횟수&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;초기화&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# 전적에서 각 행동이 발생한 학률&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;승부행동계수&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;초기화&lt;/span&gt;    &lt;span class=&quot;c&quot;&gt;# 전적에서 승리 했을때의 각 행동 수&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;초기화&lt;/span&gt;    &lt;span class=&quot;c&quot;&gt;# 전적에서 승리 했을때의 각 행동 확률&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;총승부계수&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'승'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'패'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'무'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;기록&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;기록&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# 승부 통계를 위한 데이터&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;승부계수추가&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;총승부계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;승부&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;행동계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# 내가 이겼을 경우 (적이 졌을경우) -1 // 적이 이겼을 경우는 1&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;승부행동계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# 분석&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;총승부계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'승'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;진행횟수&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;진행횟수&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;총승부계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'승'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 총승부 계수가 0일 경우 계산을 위해 임의로 매우 낮은 활률을 입력&lt;/span&gt;
                &lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부행동계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0000001&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부행동계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;총승부계수&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'승'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;거울(손모양):
    &quot;&quot;&quot;입력값의 반대되는 값을(승리값)반환해&quot;&quot;&quot;
    &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;거울2(손모양):
    &quot;&quot;&quot;입력값의 반대되는 값을(패배값)반환해&quot;&quot;&quot;
    &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;손모양&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# 30회 까지는 임의로 가위바위적&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적추적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;전적분석&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;전적분석&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;c&quot;&gt;# 베이지안 확률로 각 행동별 승률을 구한다&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3333333&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;c&quot;&gt;# 적에게 승률이 제일 높은 값의 반대값으로 리턴함&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;상대방예상값&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itemgetter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;거울&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;상대방예상값&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;전적분석&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;내전적&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;c&quot;&gt;# 베이지안 확률로 각 행동별 승률을 구한다&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3333333&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;승률&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;승부행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동확률&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;c&quot;&gt;# 적에게 승률이 제일 높은 값의 반대값으로 리턴함&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;상대방예상값&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;행동별승률&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itemgetter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;상대방예상값&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by-&quot;&gt;연산자 오버로딩을 이용한 어뷰징 by 아무개&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;==&lt;/code&gt; 연산자를 오버로딩해서 상대의 손을 확인하고,
&lt;code class=&quot;highlighter-rouge&quot;&gt;__str__&lt;/code&gt;(문자열 형변환) 연산자를 오버로딩해서 항상 이기는 손을 내는 &lt;strong&gt;객체를 리턴&lt;/strong&gt;하는 어뷰징(?)입니다.
이벤트 소개 페이지의 pseudo code에서는 효과가 있었겠지만,
실제 게임 진행 서버에서는 (상대방이 정상이라면) 항상 부전패입니다^^;
이 방식의 또 다른 문제점은 내가 항상 &lt;code class=&quot;highlighter-rouge&quot;&gt;==&lt;/code&gt; 연산자의 앞에 온다는(left-hand operand) 보장이 없다는 거죠.&lt;/p&gt;

&lt;p&gt;심사 초반에 이 코드를 보지 않았다면, 어뷰징에 대해서 생각도 못했을텐데…
덕분에 다른 어뷰징들도 찾아보는 계기가 된 코드입니다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Gawibawibo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;switch&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__eq__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;your&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;your&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;your&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;your&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;

            &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;your&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__repr__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__str__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Gawibawibo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by--1&quot;&gt;모듈/함수 바꿔치기 by 아무개&lt;/h4&gt;

&lt;p&gt;파이썬 모듈 로더를 활용해서,
로딩된 모듈 중에 &lt;code class=&quot;highlighter-rouge&quot;&gt;show_me_the_hand&lt;/code&gt; 함수가 있으면 항상 &lt;code class=&quot;highlighter-rouge&quot;&gt;bo&lt;/code&gt;만 내는 함수로 바꿔치기 합니다.
물론 플레이어 자신은 항상 &lt;code class=&quot;highlighter-rouge&quot;&gt;gawi&lt;/code&gt;만 내죠^^;&lt;/p&gt;

&lt;p&gt;처음 계획한 서버-to-서버 HTTP 통신 방식이었으면 효과가 없었겠지만,
바뀐 로컬에서 실행하는 방식에서는 쉽게 막을 방법이 없었습니다.
그렇다고 약속한 게임 규칙을 임의로 바꿀 수도 없어서 고민했는데,
&lt;a href=&quot;https://github.com/ganadist&quot;&gt;ganadist&lt;/a&gt;님이 급히 만들어주신 &lt;strong&gt;플레이어 자신의 모듈만 로딩된 격리된 프로세스에서 함수를 실행하고 파이프로 결과를 주고 받는 방식&lt;/strong&gt;으로
게임 규칙 변경없이 무력화할 수 있었습니다.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sys&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;string&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;suckzoo_fantastic_symbolic_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sticky_note_exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;#read_modules()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;scan_modules_and_hijack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;remove_sticky_note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;scan_modules_and_hijack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'show_me_the_hand'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; \
                &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'suckzoo_fantastic_symbolic_func'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show_me_the_hand&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;remove_sticky_note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sticky_note&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sticky_note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sticky_note_exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sticky_note&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dir_ls&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getcwd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sticky_note&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dir_ls&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;leave_sticky_note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sticky_note&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sticky_note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'a'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'before injecting a lambda&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#if not read_modules():&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scan_modules_and_hijack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sticky_note&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'suckzoo_fantastic_rsp_sticky_note'&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;leave_sticky_note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;by--2&quot;&gt;모듈 사전 로딩 &amp;amp; 학습 by 아무개&lt;/h4&gt;

&lt;p&gt;현재 디렉토리의 모든 모듈을 &lt;code class=&quot;highlighter-rouge&quot;&gt;.py&lt;/code&gt; 파일을 미리 로딩해서 각각 1000번의 결과를 학습하고,
학습된 결과에 기반해 자신의 손을 결정하는 방식입니다.&lt;/p&gt;

&lt;p&gt;기발한 아이디어 였지만, 허무하게도… &lt;code class=&quot;highlighter-rouge&quot;&gt;random&lt;/code&gt; 사용 금지 규칙에 따라… 서류 전형에서 탈락!! 참가상도 못받아가셨네요 ㅠㅠ
그냥 &lt;code class=&quot;highlighter-rouge&quot;&gt;time() % 3&lt;/code&gt;이라도 하시지…&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sys&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;types&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;glob&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os.path&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isfile&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;collections&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Counter&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pyfiles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;glob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;glob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'/*.py'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;globals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;__import__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;globals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;locals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pyfiles&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;opponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;thismodule&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;modulenames&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;globals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;allmodules&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modulenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allmodules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;allmodules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ModuleType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;\
            &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'show_me_the_hand'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;\
            &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thismodule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;occurance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;occurance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_me_the_hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;COUNTER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'gawi'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# if opponent module is accessable?&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;opponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;most_common&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;COUNTER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;loser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;most_common&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;COUNTER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COUNTER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;이 외에도, 수알못 파알못 - 수학을 전혀 모르는 파이썬 초보인 제 수준으로는
의도 파악조차 힘든 멋진 코드들이 많았지만,
깃헙에 올려둔 &lt;a href=&quot;https://github.com/kakao/pycon2016apac-gawibawibo&quot;&gt;게임 진행 서버&lt;/a&gt;의 소스 코드로 대신하며, 코드 리뷰(구경?)를 마칩니다.&lt;/p&gt;

&lt;h2 id=&quot;coding-battle--&quot;&gt;CODING BATTLE 가위바위보!를 돌아보며…&lt;/h2&gt;

&lt;p&gt;예능으로 시작했다가 다큐로 끝난다더니, 이번 가위바위보 이벤트가 바로 그랬습니다.&lt;/p&gt;

&lt;p&gt;부족한 이벤트에 큰 호응을 보여주신 많은 참가자 분들과 이벤트 진행과 준비에 고생한 운영자 분들께 감사드립니다.
통큰 스폰서로 개발자들에게 꿈과 희망, 그리고 좌절(?)을 선물해주신 회사 측에도 감사드립니다.
특히, 급하게 요청드렸음에도 기꺼이 코드를 만들어주신 &lt;a href=&quot;https://blog.outsider.ne.kr&quot;&gt;outsider&lt;/a&gt;님과 &lt;a href=&quot;https://github.com/ganadist&quot;&gt;ganadist&lt;/a&gt;님께 감사드립니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/01a3b97731f42b4efc929ebd7fa376431667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;긴 글, 끝까지 읽어주셔서 감사합니다. 이 한마디를 전하며 긴 글을 마칩니다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;may the &lt;strong&gt;PYTHON&lt;/strong&gt; be with you…&lt;/p&gt;
&lt;/blockquote&gt;

</description>
        <pubDate>Fri, 19 Aug 2016 18:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/08/19/gawibawibo/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/08/19/gawibawibo/</guid>
        
        <category>coding-battle</category>
        
        <category>gawibawibo</category>
        
        <category>pycon2016apac</category>
        
        <category>python</category>
        
        
      </item>
    
      <item>
        <title>개발자를 위한 SSD (Coding for SSD) - Part 6 : A Summary – What every programmer should know about solid-state drives</title>
        <description>&lt;p&gt;이번 챕터에서는 지금까지 언급했던 내용들을 간략히 요약해서 정리해 보았다.
조금 더 상세한 내용을 쉽게 찾아갈 수 있도록, 각 내용들은 다른 파티의 한두개 이상의 섹션을 나열해 두었다.&lt;/p&gt;

&lt;h2 id=&quot;ssd-&quot;&gt;SSD 기본&lt;/h2&gt;

&lt;h3 id=&quot;section&quot;&gt;1. 메모리 셀 타입&lt;/h3&gt;

&lt;p&gt;SSD (Solid state drive)는 플래시 메모리를 기반으로 하는 저장 장치이이다.
각 비트들은 셀에 저장되는데, SSD의 셀은 1 비트(SLC), 2 비트(MLC) 그리고 3 비트(TLC) 셀 타입이 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/13/coding-for-ssd-part-1/&quot;&gt;섹션 1.1&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-1&quot;&gt;2. 수명 제한&lt;/h3&gt;

&lt;p&gt;각 셀은 최대 가능한 P/E(Program/Erase) cycle을 가지며, 최대 가능한 P/E cycle을 초과하면 결함 셀(Defective cell)로 간주된다.
이는 NAND 플래시 메모리는 언젠가는 Wear-off되고 수명이 제한적이라는 것을 의미한다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/13/coding-for-ssd-part-1/&quot;&gt;섹션 1.1&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-2&quot;&gt;3. 벤치마킹의 어려움&lt;/h3&gt;

&lt;p&gt;테스트도 결국 사람이 수행하는 것이기 때문에, 모든 벤치마크는 오류나 실수를 담고 있다.
그래서 제조사나 제 삼자에 의한 벤치 마킹 결과를 참조할 때는 주의해야 한다.
SSD를 이용한 벤치마킹을 진행할 경우에는, 실제 사용할 SSD 드라이브와 최대한 응용 프로그램의 워크로드와 비슷한 인하우스 벤치마킹 도구를 이용할 것을 권장한다.
마지막으로 응용 프로그램의 요건(SLA)에 맞는 메트릭에 집중해서 벤치마킹을 할 것을 권장한다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/14/coding-for-ssd-part-2/&quot;&gt;섹션 2.2, 2.3&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;section-3&quot;&gt;페이지와 블록&lt;/h2&gt;

&lt;h3 id=&quot;nand---&quot;&gt;4. NAND 플래시 블록과 페이지&lt;/h3&gt;

&lt;p&gt;SSD의 셀(Cell)은 블록(Block)으로 그룹핑되어 있으며, 블록(Block)은 다시 플레인(Plane)으로 그룹핑된다.
SSD에서 읽고 쓰기의 가장 작은 단위는 페이지(Page)이며, 페이지는 단독으로 삭제(Erase)되지 못하고 블록(Block) 단위로만 삭제될 수 있다.
NAND 플래시 페이지 사이즈는 제품이나 제조사별로 다양한데, 대 부분의 SSD는 2KB와 4KB 그리고 8KB와 16KB를 페이지 사이즈로 사용하고 있다.
또한 대부분의 SSD에서는 하나의 블록은 128개 또는 256개의 페이지를 가지므로, 블록의 사이즈는 256KB에서 4MB까지 다양한 사이즈를 가지게 되는 것이다.
예를 들어서 삼성 SSD 840 EVO는 2048KB의 블록 크기를 가지며, 각 블록은 256개의 8KB 크기 페이지를 가지고 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;align&quot;&gt;5. 읽기는 페이지 단위로 실행(Align)&lt;/h3&gt;

&lt;p&gt;SSD 드라아브에서는 페이지 하나의 크기보다 작은 사이즈의 데이터를 읽을 수는 없다.
물론 응용 프로그램이나 운영 체제에서는 단 1개의 바이트도 읽을 수는 있지만,
실제 SSD 내부적으로는 하나의 페이지 전체를 읽어서 불필요한 데이터는 모두 버리고 사용자가 원하는 하나의 바이트만 리턴해주는 것일 뿐이다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;align-1&quot;&gt;6. 쓰기는 페이지 단위로 실행(Align)&lt;/h3&gt;

&lt;p&gt;SSD에 쓰기를 할 때에는 SSD의 페이지 크기의 배수로 처리된다. 그래서 단지 1 바이트만 SSD에 기록하는 경우에는 최소 하나의 페이지 크기의 데이터가 기록되어야 한다. 필요 이상으로 데이터를 기록해야 하는 것을 “Write amplication”이라고 하며, SSD 드라이브에 쓰기를 하는 것을 “program”이라고도 한다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-4&quot;&gt;7. 페이지는 덮어쓰기 안됨&lt;/h3&gt;

&lt;p&gt;NAND 플래시 페이지는 “free” 상태일때에만 데이터를 저장(program)할 수 있다.
데이터가 변경되면 이전 버전의 페이지는 SSD의 내부 레지스터로 복사된 후 데이터가 변경되고
그리고 새로운 버전의 데이터는 새로운 “free” 상태의 페이지에 기록되는데 이를 “read-modify-update”라고 한다.
또한 SSD에서 변경된 데이터는 기존 위치에 업데이트되지 못하고, 항상 새로운 “free” 상태의 페이지에만 저장될 수 있다.
일단 데이터가 SSD 드라이브에 영구히 저장되면,
이전 버전의 데이터를 가지고 있는 페이지는 “stale” 상태로 마킹되고 Garbage-collection에 의해서 삭제(Erase)될때까지 그 상태로 남아있게 된다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;erase---aligned&quot;&gt;8. 삭제(Erase)는 블록 단위로 실행(Aligned)&lt;/h3&gt;

&lt;p&gt;SSD의 페이지는 덮어쓰기할 수 없다. 일단 페이지가 “stale” 상태로 바뀌면, 그 페이지를 다시 사용하기 위해서는 반드시 삭제(Erase) 과정을 거쳐야 한다.
그러나 페이지는 단독으로 삭제될 수 없으며, 그 페이지를 포함한 블록이 통째로 삭제(Erase)될때에만 “free” 상태로 바뀔 수 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;ssd--&quot;&gt;SSD 컨트롤러와 인터널&lt;/h2&gt;

&lt;h3 id=&quot;ftl-flash-translation-layer&quot;&gt;9. FTL (Flash Translation Layer)&lt;/h3&gt;

&lt;p&gt;FTL(Flash translation layer)는 호스트의 논리 블록 주소를 SSD의 물리 블록 주소로 맵핑해주는 SSD 컨트롤러의 컴포넌트이다.
최근의 SSD 드라이브는 대 부분 Log structured 파일 시스템과 비슷한 작동 방식을 가진 “hybrid log-block mapping”나 그로부터 파생된 블록 맵핑 알고리즘을 사용하고 있다.
“hybrid log-block mapping” 맵핑 알고리즘은 랜덤 쓰기를 시퀀셜하게 변환해주는 장점을 가지고 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/16/coding-for-ssd-part-4/&quot;&gt;섹션 4.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-5&quot;&gt;10. 내부 병렬 처리&lt;/h3&gt;

&lt;p&gt;SSD 드라이브는 내부적인 병렬 처리 기능을 가지고 있는데, 이 병렬 처리 기능은 NAND 플래시 칩 단위로 여러 블록들에 동시에 쓰기할 수 있도록 해준다.
이렇게 한번에 병렬로 액세스할 수 있는 블록들의 그룹을 “Clustered block”이라고 한다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/18/coding-for-ssd-part-6/&quot;&gt;섹션 6&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;wear-leveling&quot;&gt;11. Wear leveling&lt;/h3&gt;

&lt;p&gt;NAND 플래시 셀은 데이터 쓰기를 할 수 있는 회수 제한이 있어서, 이 제한을 넘어서면 해당 셀은 더 이상 사용할 수 없게 된다.
이런 현상을 “Wear-off”라고 하는데, FTL은 최대한 SSD의 셀들이 최대한 골고루 사용될 수 있도록 쓰기 작업을 분산해준다.
이상적으로는 SSD의 모든 셀들이 동시에 그들의 P/E cycle 한계에 도달하도록 하는 것이 FTL의 중요한 목표인 것이다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.4&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;garbage-collection&quot;&gt;12. Garbage collection&lt;/h3&gt;

&lt;p&gt;SSD 컨트롤러의 Garbage-collection은 “stale” 상태의 페이지를 삭제하여 다시 사용 가능한 “free” 상태로 만들어준다.
“stale” 상태의 페이지는 “free” 상태로 전환되지 않고서는 재사용될 수 없다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/16/coding-for-ssd-part-4/&quot;&gt;섹션 4.4&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-6&quot;&gt;13. 백그라운드 오퍼레이션은 유저 오퍼레이션에 영향을 미침&lt;/h3&gt;

&lt;p&gt;Garbage-collection과 같은 백그라운드 오퍼레이션은 호스트로부터 유입되는 사용자 요청(포그라운드 오퍼레이션)에 좋지 않은 영향을 미치게 된다.
대표적으로 지속적인 작은 랜덤 쓰기가 발생하는 상황에서는 더욱 더 성능상 나쁜 영향을 미치게 된다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/16/coding-for-ssd-part-4/&quot;&gt;섹션 4.4&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;section-7&quot;&gt;액세스 패턴&lt;/h2&gt;

&lt;h3 id=&quot;ssd-------&quot;&gt;14. SSD는 페이지 크기보다 작은 데이터만 쓰기할 수 없음&lt;/h3&gt;

&lt;p&gt;“Read-modify-write” 오퍼레이션으로 인한 “Write amplication”을 최소화하기 위해서,
가능하다면 NAND 플래시의 페이지 크기로 SSD 드라이브에 쓰기를 하는 것이 좋다.
현재까지 출시된 SSD 제품들중에서 가장 큰 페이지 사이즈는 16KB이므로, 가능하면 16KB 정도를 기본 쓰기 데이터 사이즈로 선택하는 것이 좋다.
SSD의 페이지 사이즈는 SSD 제조사나 모델별로 상이하며, 또한 SSD 기술 발전으로 더 크게 증가할 수도 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2, 3.3&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;align-writes&quot;&gt;15. 쓰기 맞춤 (Align writes)&lt;/h3&gt;

&lt;p&gt;SSD 드라이브에 쓰기를 할 때에는 페이지 사이즈에 맞춰서(Align) 호출하는 것이 좋으며, 페이지 사이즈의 배수로 데이터를 기록하는 것은 더더욱 권장한다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2, 3.3&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-8&quot;&gt;16. 작은 데이터 쓰기는 버퍼링후 실행&lt;/h3&gt;

&lt;p&gt;최대의 스루풋을 위해서, 가능한 작은 사이즈의 데이터 쓰기는 메모리에 모아서 버퍼링했다가 버퍼가 가득 차면 SSD 드라이브로 기록하는 것이 좋다.
작은 데이터 쓰기들을 모아서 배치로 한번의 쓰기 요청으로 처리하는 것이 좋다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;섹션 3.2, 3.3&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-9&quot;&gt;17. 읽기 성능 향상을 위해서, 관련된 데이터는 한번에 같이 저장&lt;/h3&gt;

&lt;p&gt;읽기 성능은 쓰기 패턴의 결과라고 볼 수 있다.
만약 대용량의 데이터가 한번에 SSD 드라이브에 쓰여진다면, 그 데이터들은 NAND 플래시 칩의 여러 위치로 분산 저장된다.
그래서 연관된 데이터는 동일 페이지와 블록 그리고 Clustered block에 기록하는 것이 좋다.
그래야지만 나중에 (SSD의 내부 병렬 처리의 도움을 받아서) 한번의 I/O 요청으로 데이터를 빠르게 읽을 수 있기 때문이다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.3&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-10&quot;&gt;18. 읽기와 쓰기 요청은 분리 실행&lt;/h3&gt;

&lt;p&gt;읽기와 쓰기가 동시에 실행되는 워크로드에서는 SSD 내부적인 캐싱과 Readahead 메커니즘의 효과를 얻기 어려우며, 이로 인해서 스루풋이 떨어질 수도 있다.
이렇게 읽고 쓰기가 혼합된 형태의 워크로드는 가능하다면 읽기와 쓰기를 대용량(가능하다면 Clustered block 크기에 맞춰서)으로
순차적으로 실행할 수 있도록 유도하는 것이 좋다.
예를 들어서 1000개의 파일을 읽고 변경해야 한다면, 하나씩 파일을 읽어서 변경하는 형태보다
한번에 1000개의 파일을 읽어서 처리한 후 1000개의 파일을 변경하는 형태가 더 SSD의 처리 효울성에는 도움이 된다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.4&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;invalidate-obsolete-data-in-batch&quot;&gt;19. 불필요한 데이터는 배치로 삭제 (Invalidate obsolete data in batch)&lt;/h3&gt;

&lt;p&gt;SSD 드라이버의 데이터가 더이상 필요치 않아서 삭제하고자 할 때에는, 삭제 요청을 일정량 모아서 한번의 오퍼레이션으로 삭제하는 것이 좋다.
이는 SSD 컨트롤러의 Garbage-collection이 한번에 더 큰 영역을 처리할 수 있도록 해주며, 그로 인해서 SSD의 내부적인 프레그멘테이션을 최소화할 수 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/16/coding-for-ssd-part-4/&quot;&gt;섹션 4.4&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;random-writes-are-not-always-slower-than-sequential-writes&quot;&gt;20. 랜덤 쓰기가 Random writes are not always slower than sequential writes&lt;/h3&gt;

&lt;p&gt;SSD 드라이브에 기록하고자 하는 데이터가 작다면(SSD의 Clustered block 보다 작은 경우), 랜덤 쓰기는 시퀀셜 쓰기보다 느리게 처리될 것이다.
만약 쓰기가 Clustered block 크기와 일치하거나 Clustered block의 크기의 N배수(정확하게)인 경우에는,
랜덤 쓰기라 하더라도 SSD의 내부 병렬 처리 능력을 활용할 수 있게 된다.
랜덤 쓰기라 하더라도 SSD의 병렬 처리 능력을 활용할 수 있다면 시퀀셜 쓰기만큼의 스루풋을 얻을 수 있다.
대 부분의 SSD 드라이브에서 Clustered block의 사이즈는 16MB에서 32MB 정도이므로,
32MB 정도의 데이터를 한번에 쓰기할 수 있으면 최적의 쓰기 성능을 얻을 수 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-11&quot;&gt;21. 단일 쓰레드로 대량 읽기가 멀티 쓰레드의 소량 데이터 읽기보다 나은 스루풋 보장&lt;/h3&gt;

&lt;p&gt;동시에 여러 쓰레드로 실행되는 읽기 요청은 SSD 드라이브의 Readahead 메커니즘을 100% 활용하기 어렵다.
게다가 한번에 여러 논리 블록 주소를 액세스하는 것은 결국 하나의 NAND 플래시 칩으로 집중될 수 있고,
이런 경우에는 SSD의 내부 병렬 처리 능력을 활용하지 못하게 된다.
연속된 블록을 대용량으로 읽는 오퍼레이션은 SSD 드라이브의 Readahead 버퍼를 활용(SSD 드라이브가 Readahead cache를 가지고 있는 경우)할 수 있으며,
또한 내부적인 병렬 처리 기능까지 활용할 수 있다.
결과적으로 가능하다면 요청을 버퍼링했다가 한번에 큰 데이터를 읽는 형태가 스루풋 향상에 도움이 된다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.3&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-12&quot;&gt;22. 단일 쓰레드로 대량 쓰기가 멀티 쓰레드의 소량 데이터 쓰기보다 나은 스루풋 보장&lt;/h3&gt;
&lt;p&gt;단일 쓰레드로 대용량의 데이터를 쓰는 것은 많은 쓰레드로 동시에 작은 데이터를 쓰는 것 만큼의 스루풋을 얻을 수 있다.
하지만 단일 쓰레드로 대용량의 데이터를 기록하는 것이 레이턴시(응답 속도) 측면에서는 여러 쓰레드로 동시에 쓰기를 하는 것보다 빠르게 처리된다.
그래서 가능하다면, 단일 쓰레드로 대량의 데이터를 한꺼번에 기록하는 것이 좋다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-13&quot;&gt;23. 소량 데이터 쓰기를 버퍼링하거나 그룹핑할 수 없을 때는, 멀티 쓰레드로 실행&lt;/h3&gt;

&lt;p&gt;멀티 쓰레드로 작은 데이터를 아주 빈번하게 쓰기 요청을 실행하는 것은 단일 쓰레드로 작은 쓰기를 실행하는 것보다는 더 나은 스루풋을 낼 것이다.
그래서 작은 데이터 쓰기가 배치 형태로 묶어서 처리될 수 없다면, 멀티 쓰레드로 쓰기를 실행하는 것이 좋다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-14&quot;&gt;24. 콜드 데이터와 핫 데이터 분리&lt;/h3&gt;

&lt;p&gt;핫(Hot) 데이터는 빈번하게 읽고 변경되는 데이터를 의미하며, 반대로 콜드(Cold) 데이터는 그렇지 않은 데이터를 말한다.
만약 핫 데이터가 콜드 데이터와 함께 동일 페이지에 저장되어 있다면,
콜드 데이터는 핫 데이터가 Read-modify-write 형태로 변경될 때마다 항상 같이 복사되어야하며 또한 Wear-leveling을 위해서
Garbage-collection이 해당 페이지를 이동시킬 때에도 동일하게 콜드 데이터가 항상 같이 다른 페이지로 이동되어야 한다.
콜드 데이터와 핫 데이터를 분리하는 것은 Garbage-collection이 좀 더 빠르고 효율적으로 처리될 수 있도록 도와줄 것이다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/16/coding-for-ssd-part-4/&quot;&gt;섹션 4.4&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;section-15&quot;&gt;25. 핫 데이터는 버퍼링해서 배치로 업데이트&lt;/h3&gt;

&lt;p&gt;매우 빈번하게 변경되는 핫 데이터나 메타 데이터는 최대한 시스템의 메모리에 캐시되거나 버퍼링될 수 있도록 유지하고,
SSD 드라이브에 변경되는 빈도를 최소화해주는 것이 좋다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/16/coding-for-ssd-part-4/&quot;&gt;섹션 4.4&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;section-16&quot;&gt;시스템 최적화&lt;/h2&gt;

&lt;h3 id=&quot;pci-express--sas--sata--&quot;&gt;26. PCI Express 와 SAS 인터페이스는 SATA 인터페이스보다 빠르다&lt;/h3&gt;

&lt;p&gt;최근의 SSD 드라이버는 일반적으로  SATA 3.0(550MB/s)과 PCI Express 3.0(채널당 1GB/s, 모델별 차이는 있지만 일반적으로는 다중 채널 채택)을 지원하고 있다.
SAS(Serial Attached SCSI) 인터페이스는 일부 엔터프라이즈 SSD 모델에 장착되긴 하지만, 일반적이지는 않다.
최근 버전에서는 PCI Express와 SAS 인터페이스가 SATA 보다는 훨씬 뛰어난 전송 속도를 보이지만, 조금 비싼 편이다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/14/coding-for-ssd-part-2/&quot;&gt;섹션 2.1&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;over-provisioning-wear-leveling-----&quot;&gt;27. Over-provisioning은 Wear-leveling과 성능 향상에 많은 도움이 된다.&lt;/h3&gt;

&lt;p&gt;SSD 드라이브는 최대 물리 용량보다 더 작은 용량으로 파티션을 생성함으로써 Over-provisioning 공간을 할당할 수 있다.
남은 공간은 사용자나 호스트에게는 보이지 않지만 SSD 컨트롤러는 여전히 남은 물리 공간을 활용할 수 있다.
Over-provisioning 공간은 NAND 플래시 셀의 제한된 수명을 극복하기 위한 Wear-leveling이 좀 더 원활하게 처리될 수 있도록 도와준다.
데이터 쓰기가 아주 빈번한 경우에는 성능 향상을 위해서 전체 용량의 25% 정도의 공간을 Over-provisioning 공간을 할당하는 것이 좋으며,
그렇게 쓰기 부하가 심하지 않은 경우에는 10%~15% 정도의 공간을 Over-provisioning 공간으로 할당해주는 것이 좋다.
Over-provisioning 공간은 NAND 플래시 블록의 버퍼처럼 작동하기도 하는데,
이는 일시적으로 평상시보다 높은 쓰기 부하가 발생하더라도 Garbage-collection이 충분한 “free” 상태 페이지를 만들어 낼수 있도록 완충 역할을 해준다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.2&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;trim--&quot;&gt;28. TRIM 명령의 활성화&lt;/h3&gt;

&lt;p&gt;운영 체제의 커널과 파일 시스템이 TRIM 명령을 지원하는지 확인하도록 하자.
TRIM 명령은 운영 체제나 파일 시스템 레벨에서 삭제된 블록의 정보를 SSD 컨트롤러에게 알려주는 역할을 한다.
SSD 컨트롤러는 새로운 쓰기 요청들을 위해서, 시스템이 한가한 시간에 Garbage-collection을 실행하는데,
TRIM 명령으로 삭제된 블록 정보를 SSD 컨트롤러가 받을 수 있으면 Garbage-collection의 삭제(Erase) 작업이 훨씬 효율적으로 처리될 수 있다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.1&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;align-the-partition&quot;&gt;29. 파티션 얼라인 (Align the partition)&lt;/h3&gt;

&lt;p&gt;쓰기 요청이 NAND 플래시의 물리 메모리와 맞춰지려면(Alignment),
SSD 드라이브를 파티셔닝하고 포맷할때 NAND 플래시의 페이지 크기와 일치(Align)시켜야 한다.&lt;/p&gt;

&lt;p&gt;See also: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;섹션 5.1&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;section-17&quot;&gt;결론&lt;/h2&gt;

&lt;p&gt;지금까지 “Coding for SSDs” 시리즈의 내용을 간단히 요약했다.
SSD에 대한 개인적인 연구를 통해 배운 것들을 누구나 쉽게 이해할 수 있도록 전달되었기를 희망한다.
이 시리즈를 읽고 더 상세히 SSD에 대해서 공부하기를 원한다면,
챕터 2부터 5까지의 게시물 하단에 냐열된 레퍼런스 문서들은 아마도 훌륭한 가이드가 될 수 있을 것으로 생각한다.
USENIX에서도 매년 SSD에 관련된 놀라운 연구들이 진행되고 있으므로,
USENIX FAST 컨퍼런스(파일시스템과 스토리지 기술에 관련된 USENIX 컨퍼런스) 웹 사이트도 많은 도움이 될 것을 생각된다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;This articles are translated to Korean with original author’s(&lt;a href=&quot;http://www.goossaert.com/&quot;&gt;Emmanuel Goossaert&lt;/a&gt;) permission. Really appreciate his effort and sharing.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Original articles :&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/&quot;&gt;Part 1: Introduction and Table of Contents&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-2-architecture-of-an-ssd-and-benchmarking/&quot;&gt;Part 2: Architecture of an SSD and Benchmarking&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-3-pages-blocks-and-the-flash-translation-layer/&quot;&gt;Part 3: Pages, Blocks, and the Flash Translation Layer&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-parallelism/&quot;&gt;Part 4: Advanced Functionalities and Internal Parallelism&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-5-access-patterns-and-system-optimizations/&quot;&gt;Part 5: Access Patterns and System Optimizations&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-6-a-summary-what-every-programmer-should-know-about-solid-state-drives/&quot;&gt;Part 6: A Summary – What every programmer should know about solid-state drives&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Mon, 18 Jul 2016 20:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/18/coding-for-ssd-part-6/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/18/coding-for-ssd-part-6/</guid>
        
        <category>ssd</category>
        
        <category>nand-flash</category>
        
        <category>ssd</category>
        
        <category>garbage-collection</category>
        
        <category>LBA</category>
        
        <category>PBA</category>
        
        <category>block</category>
        
        <category>page</category>
        
        <category>clustered-block</category>
        
        
      </item>
    
      <item>
        <title>개발자를 위한 SSD (Coding for SSD) - Part 5 : 접근 방법과 시스템 최적화</title>
        <description>&lt;p&gt;지금까지 SSD 드라이브의 내부적인 작동 방식에 대해서 살펴 보았다.
또한 SSD를 접근할 때 어떤 방식이 사용되어야 하며, 그리고 그 접근 방법이 다른 방법보다 왜 나은지 등의 이해를 돕는 자료들도 제공했다.
이번 챕터에서는 읽기와 쓰기는 어떻게 처리되어야 하는지, 그리고 읽고 쓰기가 동시에 발생할 때 서로 어떤 간섭 효과를 내게 되는지를 살펴보도록 하겠다.
그리고 성능 향상을 위해서 파일 시스템 레벨에서 가능한 최적화에 대해서도 조금 언급하도록 하겠다&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part5_1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;section&quot;&gt;7. 액세스 패턴&lt;/h2&gt;

&lt;h3 id=&quot;io-&quot;&gt;7.1. 시퀀셜과 랜덤 I/O의 정의&lt;/h3&gt;

&lt;p&gt;이번 섹션에서 사용되는 시퀀셜(“sequential”)과 랜덤(“random”)이라는 단어로 SSD 액세스를 의미한다.
이전 I/O 오퍼레이션의 마지막 논리 블록 주소(LBA) 직후의 논리 블록 주소(LBA)를 시작 지점으로 SSD를 접근하는 경우 시퀀셜이며,
그렇지 않은 경우를 랜덤이라고 한다.
그리고 FTL에 의해서 동적으로 블록 맵핑이 실행되기 때문에,
논리 주소가 연속적이라고 해서 실제 물리적인 데이터의 위치가 연속적인 것은 아닐 수도 있다는 것도 기억해두자.&lt;/p&gt;

&lt;h3 id=&quot;section-1&quot;&gt;7.2. 쓰기&lt;/h3&gt;

&lt;p&gt;벤치마킹과 제조사의 데이터 시트를 보면, 랜덤 쓰기는 시퀀셜 쓰기보다 느리다는 것을 알 수 있다.
물론 이건 정확하게 랜덤 쓰기의 워크로드가 어떠냐에 따라서 틀린 이야기일 수도 있다.
하지만 쓰기 데이터가 작다면(여기에서 작은 데이터의 기준은 Clustered block의 크기보다 작은 것을 의미하며,
예를 들어서 32MB 미만 정도로 생각할 수 있다), 데이터 시트나 벤치마킹의 결과에서와 같이 랜덤 쓰기는 시퀀셜 쓰기보다 느리다.
그러나 Clustered block의 크기 또는 그 배수의 데이터를 랜덤으로 쓰기한다면, 시퀀셜 쓰기와 거의 비슷한 수준의 성능을 내게 된다.
섹션 6에서 본 것과 같이 SSD의 내부 병렬 처리는 최소 하나의 Clustered block이 처리될 때 최대 처리 능력을 보여주기 때문이다.
그래서 이렇게 대량의 데이터를 기록하는 경우에는 시퀀셜이든지 랜덤이든지,
내부적으로 여러 채널과 여러 칩을 사용하는 내부 병렬 처리 방식은 동일하게 사용된다(데이터가 여러 채널과 여러 칩으로 스트라이핑되는 것이다).
또한 이 경우 SSD가 지원하는 내부적인 병렬 처리 능력들을 모두 활용하게 되는 것이다.
Clustered block의 쓰기 오퍼레이션은 섹션 7.3에서 다시 살펴보도록 하겠다.
지금은 성능적인 측면에서 (그림 8과 9) 쓰기 버퍼의 크기가 Clustered block(일반적인 SSD에서는 16MB 또는 32MB)에
가까워지거나 더 커지면서 랜덤 쓰기의 스루풋이 시퀀셜 쓰기와 비슷해지는 것을 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part5_2.jpg&quot; alt=&quot;그림 8: 4개 SSD 제품의 랜덤 쓰기와 시퀀셜 쓰기 성능 및 버퍼 크기의 영향도&quot; /&gt;
(출처: 2012년 김 재홍 외 &lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part5_3.jpg&quot; alt=&quot;그림9: 4개 SSD 제품의 랜덤 쓰기와 시퀀셜 쓰기 성능 및 버퍼 크기의 영향도&quot; /&gt;
(출처: 2012년 민 창우 외 &lt;sup id=&quot;fnref:8&quot;&gt;&lt;a href=&quot;#fn:8&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;

&lt;p&gt;하지만 쓰기가 작다면(여기에서 작다는 것은 NAND 플래시 페이지의 크기보다 작은 16KB 미만을 의미),
컨트롤러는 블록 맵핑을 위한 메타 데이터 관리를 위해서 더 많은 일을 해야 한다.
일부 SSD 드라이브는 논리 블록 주소와 물리 블록 주소의 맵핑을 위해서 트리 형태의 데이터 구조체를 사용한다&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.
그리고 작고 빈번한 랜덤 쓰기는 SSD 드라이브의 메모리(RAM)의 맵핑 테이블을 매우 빈번하게 업데이트해야 함을 의미한다.
그리고 메모리의 맵핑 테이블은 플래시 메모리로 동기화되어야 하는데&lt;sup id=&quot;fnref:1:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;,
메모리의 모든 업데이트는 플래시 메모리의 많은 쓰기를 발생시킨다.
하지만 시퀀셜 쓰기는 메타 데이터의 변경을 덜 필요로 하기 때문에 플래시 메모리에 동기화하는 회수도 줄어들게 된다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-2&quot;&gt;랜덤 쓰기가 항상 시퀀셜보다 느린 것은 아니다&lt;/h5&gt;

  &lt;p&gt;만약 쓰기가 Clustered block의 크기보다 작다면, 랜덤 쓰기는 시퀀셜 쓰기보다 느릴 것이다.
하지만 쓰기가 Clustered block과 동일하거나 배수 크기의 데이터를 기록한다면,
랜덤 쓰기도 SSD의 모든 병렬 처리 능력을 활용하게 되며 그로 인해서 시퀀셜 쓰기와 비슷한 스루풋을 낼 수 있게 된다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;랜덤 쓰기의 데이터 크기가 작으면 시퀀셜보다 스루풋이 떨어지는 또 다른 이유로는 블록의 “copy-erase-write” 오퍼레이션이 많이 발생하기 때문이다.
반면 최소 블록 크기의 데이터를 저장하는 시퀀셜 쓰기는 빠른 “switch-merge” 최적화가 사용될 수 있다.
게다가 작은 랜덤 쓰기는 데이터를 랜덤하게 무효화하기 때문에, 최악의 경우 많은 블록들이 단 하나의 무효화된 페이지를 가지게 된다.
이는 “stale” 페이지들이 일부 영역에 집중화(localizing)되는 것이 아니라,
전체 물리 공간에 골고루 퍼뜨리는 효과를 내게 되는데 이를 “내부 프레그멘테이션(internal fragmentation)”이라고 한다.
내부 프레그멘테이션은 Garbage-collection이 “free” 페이지를 만들어내기 위해서 많은 블록 삭제(Erase)를 실행하게 만드는 “Cleaning efficiency”를 유발하게 된다.&lt;/p&gt;

&lt;p&gt;마지막으로 동시성 측면에서 보면, 하나의 쓰레드에서 대량의 버퍼를 기록하는 것은 많은 쓰레드가 동시에 작은 데이터를 쓰기하는 것만큼 빠르다.
단일 쓰레드로 대량 데이터 쓰기시에는 SSD의 내부 병렬 처리 능력을 모두 활용할 수 있기 때문이다.
그래서 여러 쓰레드로 동시에 병렬로 데이터 쓰기를 실행하는 것은 SSD 드라이브의 스루풋을 향상시키지 못한다&lt;sup id=&quot;fnref:1:2&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:5:1&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.
역으로 많은 동시 쓰레드가 데이터를 쓰기하는 것은 단일 쓰레드 쓰기보다 레이턴시를 증가시키는 역 효과만 낳게 될 것이다&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:26&quot;&gt;&lt;a href=&quot;#fn:26&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:27&quot;&gt;&lt;a href=&quot;#fn:27&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-3&quot;&gt;하나의 큰 데이터 쓰기는 많은 동시 쓰기보다 낫다&lt;/h5&gt;

  &lt;p&gt;하나의 큰 쓰기 요청은 병렬로 실행되는 많은 작은 쓰기 요청만큼의 스루풋을 낼 수 있다.
하지만 레이턴시 측면에서 보면, 하나의 큰 쓰기 요청은 많은 쓰기 요청보다 더 나은 응답 속도를 보장받을 수 있다.
그래서 가능하다면 큰 데이터를 한번에 기록하는 것이 좋다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-4&quot;&gt;버퍼링되지 못하는 작은 쓰기는 멀티 쓰레드로 기록하는 것이 도움이 된다&lt;/h5&gt;

  &lt;p&gt;많은 동시 쓰레드로 작은 쓰기 요청은 하나의 작은 쓰기 요청보다 나은 스루풋을 제공한다.
만약 I/O 데이터가 작고 버퍼링 또는 배치로 묶을 수 없다면 멀티 쓰레드로 쓰기를 수행하는 것이 좋다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;section-5&quot;&gt;7.3. 읽기&lt;/h3&gt;

&lt;p&gt;읽기는 쓰기보다 빠른다. 시퀀셜 읽기와 랜덤 읽기의 성능은 워크로드에 의존적이다.
FTL은 논리 블록과 물리 블록을 동적으로 맵핑하며, 쓰기를 여러 채널로 스트라이핑해준다.
이 방법을 때로는 “Write-order-based” 맵핑이라고도 한다&lt;sup id=&quot;fnref:3:1&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.
만약 처음 데이터가 기록되던 순서와는 무관하게 완전히 랜덤하게 데이터 읽기 요청이 발생한다면,
설령 읽기 요청이 연속적이라 하더라도 여러 채널로 분산되어 처리된다는 것을 보장할 수 없다.
연속적인 랜덤 읽기가 하나의 채널에 연결된 다른 블록들을 액세스할 수도 있으며,
이런 경우에는 SSD의 내부적인 병렬 처리 능력을 활용하지 못하게 되는 것이다.
Acunu는 그의 블로그&lt;sup id=&quot;fnref:47&quot;&gt;&lt;a href=&quot;#fn:47&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;에서, 읽기 성능은 데이터의 읽기 패턴이 얼마나 쓰기 패턴과 일치하느냐에 직접적으로 연결된다고 언급하고 있다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-6&quot;&gt;읽기 성능 향상을 위해서, 연관된 데이터를 같이 쓰도록 하자&lt;/h5&gt;

  &lt;p&gt;읽기 성능은 쓰기 패턴의 결과이다. 대량의 데이터가 한번에 기록된다면, 그 데이터는 NAND 플래시 칩에 골고루 분산되어서 저장될 것이다.
그래서 연관된 데이터들은 최대한 동일 페이지와 블록 그리고 클러스터링 블록에 기록되도록 하는 것이 좋다.
그래야지만 SSD의 병렬 처리 능력을 이용해서 나중에 한번의 I/O로 빠르게 읽을 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;아래의 그림 10은 하나의 플레인을 가진 칩이 4개 그리고 2개의 채널을 가진 SSD를 보여주고 있다.
참고로 대 부분의 SSD는 항상 하나의 칩당 2개 또는 그 이상의 플레인을 가지고 있는데,
여기에서는 설명의 단순화를 위해서 칩당 하나의 플레인만 있는 그림을 그렸다. 대문자는 NAND 플래시 블록 크기의 데이터를 의미한다.
그림 10의 최 상단에 표시된 오퍼레이션은 &lt;code class=&quot;highlighter-rouge&quot;&gt;[A B C D]&lt;/code&gt; 데이터를 시퀀셜하게 쓰는 것을 의미하는데, 여기에서는 Clustered block의 사이즈이다.
쓰기 오퍼레이션은 빠른 처리를 위해서 병렬 처리와 인터리빙 모드를 이용해서 4개의 플레인으로 스트라이핑된다.
4개의 블록은 논리 블록 주소상으로는 연속되게 시퀀셜하지만, 실제 내부적으로는 각각의 플레인에 저장된다.
“Write-order-based” FTL에서는, 플레인의 모든 블록은 동등하게 유입된 쓰기를 처리하게 된다.
그래서 Clustered block은 플레인의 동일한 PBN을 가질 필요가 없다.
예를 들어서 그림 10에서는, 첫 Clustered block은 4개의 서로 다른 플레인에 속한 블록에 저장되며, 각 플레인에서의 PBN은 1, 23, 11 그리고 51을 갖게 된다.&lt;/p&gt;

&lt;p&gt;그림 10의 하단에는 2번의 읽기 오퍼레이션을 보여주고 있는데, &lt;code class=&quot;highlighter-rouge&quot;&gt;[A B E F]&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;[A B G H]&lt;/code&gt; 데이터를 읽고 있다. &lt;code class=&quot;highlighter-rouge&quot;&gt;[A B E F]&lt;/code&gt; 읽기를 위해서,
&lt;code class=&quot;highlighter-rouge&quot;&gt;A&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;E&lt;/code&gt;는 동일 플레인에 속해 있으며, &lt;code class=&quot;highlighter-rouge&quot;&gt;B&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;F&lt;/code&gt;는 다른 플레인에 속해 있다.
그래서 &lt;code class=&quot;highlighter-rouge&quot;&gt;[A B E F]&lt;/code&gt;는 하나의 채널을 통해서 2개의 플레인으로부터 읽게 된다.
&lt;code class=&quot;highlighter-rouge&quot;&gt;[A B G H]&lt;/code&gt; 읽기에서, &lt;code class=&quot;highlighter-rouge&quot;&gt;A&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;B&lt;/code&gt; 그리고 &lt;code class=&quot;highlighter-rouge&quot;&gt;G&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;H&lt;/code&gt;는 모두 각각 다른 플레인에 저장되어 있기 때문에 &lt;code class=&quot;highlighter-rouge&quot;&gt;[A B G H]&lt;/code&gt;는 2개의 채널을 이용해서 4개의 플레인으로부터 동시에 읽을 수 있다.
데이터를 읽을 때, 더 많은 채널과 플레인을 이용할 수 있다는 것은 좀 더 내부 동시성 처리 기능을 활용할 수 있음을 의미하며 더 나은 읽기 성능을 보장받을 수 있게 된다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part5_4.jpg&quot; alt=&quot;그림 10: SSD의 내부 병렬 처리 활용&quot; /&gt;&lt;/p&gt;

&lt;p&gt;내부적인 병렬 처리로 인해서, 성능 향상을 위해서 멀티 쓰레드를 이용하여 동시에 데이터를 읽지 않아도 된다.
쓰레드는 읽고자 하는 데이터의 내부적인 맵핑 정보를 알지 못하기 때문에 내부적인 병렬 처리의 효과를 활용할 수가 없고
결국 동일 채널의 데이터를 이용하게 될 가능성도 있다.
또한 멀티 쓰레드로 동시에 데이터를 읽게 되면, SSD의 Read ahead (Prefetching buffer) 효과를 저해할 수도 있다는 게시물도 있다&lt;sup id=&quot;fnref:3:2&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-7&quot;&gt;단일 쓰레드의 대량 데이터 읽기는 멀티 쓰레드의 작은 읽기보다 낫다&lt;/h5&gt;

  &lt;p&gt;동시 랜덤 읽기는 SSD의 Read ahead 메커니즘을 제대로 활용하지 못할 수도 있다.
또한 다중으로 논리 블록 주소를 액세스하는 것은 내부적인 병렬 처리 기능을 활용하지 못하고 결국 동일 칩으로 집중되어 버릴 수도 있다.
게다가 대량 데이터 읽기 오퍼레이션은 연속된 주소를 액세스함으로써 Read ahead 버퍼(Read ahead 버퍼가 있다면)를 활용할 수 있다.
결과적으로 한번에 대량의 데이터를 읽는 방법이 추천된다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SSD 제조사는 일반적으로 페이지나 블록 그리고 Clustered block의 크기에 대해서는 공개하지 않는다.
그러나 단순한 테스트&lt;sup id=&quot;fnref:2:1&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:3:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;를 통해서 SSD의 기본적인 특성을 상당한 확률로 알아낼 수 있다.
이러한 정보는 데이터를 읽고 쓸 때 버퍼의 크기를 최적화하는데 활용할 수 있으며,
해당 SSD의 특성에 맞게 포맷시에 파티션을 얼라인(Align)할 수도 있다. 이런 내용은 섹션 8.4를 참조하도록 하자.&lt;/p&gt;

&lt;h3 id=&quot;section-8&quot;&gt;7.4. 동시 읽고 쓰기&lt;/h3&gt;

&lt;p&gt;작은 데이터를 읽고 쓰기는 작업이 동시에 발생하게 되면 성능 저하를 유발할 수 있다&lt;sup id=&quot;fnref:1:3&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:3:4&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.
동일 내부 자원을 위해서 읽고 쓰기가 경합을 할 수 있기 때문이다.
또한 읽기와 쓰기를 혼합하면 Read ahead와 같은 내부 최적화 메커니즘을 활용하지 못하게 만들기도 한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-9&quot;&gt;읽기와 쓰기 요청의 분리&lt;/h5&gt;

  &lt;p&gt;작은 읽기와 쓰기가 혼합되어 동시에 실행되는 워크로드는 내부적인 캐싱이나 Read ahead 메커니즘이 제대로 활용되지 못하게 만들어서 전체적인 스루풋을 떨어뜨리게 된다.
동시에 읽고 쓰기 요청을 실행하는 것은 피하고 하나씩 큰 용량의 청크(Clsutered block 크기)를 읽고 쓰도록 하는 것이 좋다.
예를 들어서 1000개의 파일이 업데이트 되어야 한다면, 파일을 한 개씩 루프로 읽고 쓰는 형태로 처리할 수도 있다.
하지만 이런 방법은 느리게 처리될 것이다. 가능하다면 한번에 1000개의 파일을 한번에 읽고 그 다음에 1000개의 파일을 한번에 업데이트하도록 하는 것이 좋다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;section-10&quot;&gt;8. 시스템 최적화&lt;/h2&gt;

&lt;h3 id=&quot;partition-alignment&quot;&gt;8.1. 파티션 얼라인먼트 (Partition alignment)&lt;/h3&gt;

&lt;p&gt;섹션 3.1에서 설명된 바와 같이, 쓰기는 페이지의 크기에 맞춰서 실행된다.
페이지의 크기에 맞춰서 쓰기 요청된 데이터는 NAND 플래시의 물리 페이지에 바로(Read-modify-write 과정 없이) 기록될 수 있다.
기록하는 데이터가 SSD 페이지 크기라 하더라도, 물리 페이지와 맞춰지지(Alignment) 않은 쓰기는 2개의 NAND 플래시 물리 페이지에 기록되어야 한다.
이런 현상이 생기면, SSD 드라이브는 두번의 “Read-modify-write” 오퍼레이션을 수행해야 한다&lt;sup id=&quot;fnref:53&quot;&gt;&lt;a href=&quot;#fn:53&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;.
그래서 SSD 드라이브의 쓰기에서 사용되는 파티션이 NAND 플래시 페이지 사이즈와 물리적으로 맞춰졌는지(Align)는 매우 중요한 요소이다.
여러 가이드 문서와 튜토리얼에서 SSD 드라이브를 포맷할 때 어떻게 파티션 파라미터를 설정하는지 소개되고 있다&lt;sup id=&quot;fnref:54&quot;&gt;&lt;a href=&quot;#fn:54&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:55&quot;&gt;&lt;a href=&quot;#fn:55&quot; class=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;.
구글에서 검색해보면 특정 SSD 드라이브 모델의 페이지와 블록 사이즈 그리고 Clustered block의 크기를 확인할 수 있다.
만약 이런 정보들이 구글 검색으로 확인이 안될 경우에는, 리버스 엔지니어링을 통해서 이런 파라미터 값들을 확인할 수 있다&lt;sup id=&quot;fnref:2:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:3:5&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.
파티션 얼라인먼트를 통해서 성능을 상당히 끌어올릴 수 있다는&lt;sup id=&quot;fnref:43&quot;&gt;&lt;a href=&quot;#fn:43&quot; class=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt; 것은 이미 여러 문서&lt;sup id=&quot;fnref:43:1&quot;&gt;&lt;a href=&quot;#fn:43&quot; class=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;에서 확인되었다.
또한 크진 않지만, 파일 시스템을 건너뛰어서 SSD 드라이브에 직접 쓰기를 실행하는 경우에도 성능 향상을 기대할 수 있다&lt;sup id=&quot;fnref:44&quot;&gt;&lt;a href=&quot;#fn:44&quot; class=&quot;footnote&quot;&gt;13&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;align-the-partitio&quot;&gt;Align the partitio&lt;/h5&gt;

  &lt;p&gt;SSD 플래시 메모리와 논리적인 쓰기가 정확하게 맞춰지도록 하기 위해서, 반드시 파티션이 NAND 플래시 메모리의 페이지와 맞춰지도록(Align) 포맷해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;section-11&quot;&gt;8.2. 파일 시스템 파라미터&lt;/h3&gt;

&lt;p&gt;섹션 5.1에서 소개되었듯이, 모든 파일 시스템이 TRIM 명령&lt;sup id=&quot;fnref:16&quot;&gt;&lt;a href=&quot;#fn:16&quot; class=&quot;footnote&quot;&gt;14&lt;/a&gt;&lt;/sup&gt;을 지원하는 것은 아니다.
리눅스 2.6.33 또는 그 이상의 버전에서 ext4와 XFS 파일 시스템만 TRIM 명령을 지원하는데,
이때에도 &lt;code class=&quot;highlighter-rouge&quot;&gt;discard&lt;/code&gt; 마운트 옵션이 활성화되어야 한다. 또한 파티션을 마운트할 때, &lt;code class=&quot;highlighter-rouge&quot;&gt;noatimer&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;nodiratime&lt;/code&gt;
그리고 &lt;code class=&quot;highlighter-rouge&quot;&gt;relatime&lt;/code&gt; 마운트 옵션을 활성화하여 파일의 메타 정보 업데이트를 하지 않도록 해서 성능 향상을 기대할 수도 있다
(물론 메타 정보 업데이트가 불필요한 경우에만) &lt;sup id=&quot;fnref:40&quot;&gt;&lt;a href=&quot;#fn:40&quot; class=&quot;footnote&quot;&gt;15&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:55:1&quot;&gt;&lt;a href=&quot;#fn:55&quot; class=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:56&quot;&gt;&lt;a href=&quot;#fn:56&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:57&quot;&gt;&lt;a href=&quot;#fn:57&quot; class=&quot;footnote&quot;&gt;17&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;trim--&quot;&gt;TRIM 명령 활성화&lt;/h5&gt;

  &lt;p&gt;파일 시스템과 리눅스 커널이 TRIM 명령을 지원하는지 확인하도록 하자.
TRIM 명령은 호스트에서 블록이 삭제되는 경우에, 그 삭제 블록 정보를 SSD 드라이브로 전달해주는 역할을 하며,
이렇게 삭제 블록 정보를 SSD 컨트롤러가 알게 되면 Garbage-collection에서는 삭제된 블록에 대해서 불필요한 복사 작업을 피할 수 있게 된다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;io--1&quot;&gt;8.3. 운영 체제의 I/O 스케줄러&lt;/h3&gt;

&lt;p&gt;리눅스의 기본 I/O 스케줄러는 &lt;code class=&quot;highlighter-rouge&quot;&gt;CFQ&lt;/code&gt;(Completely Fair Queuing)이다.
&lt;code class=&quot;highlighter-rouge&quot;&gt;CFQ&lt;/code&gt;는 요청된 I/O를 모아서 배치로 기록하기 때문에 스피닝 HDD(Spinning hard disk)의 seek-time을 최소화 해준다.
SSD 드라이브는 기계적인 장치가 아니기 때문에, 이런 형태의 I/O 요청의 재 정렬(Re-ordering)은 필요치 않다.
많은 가이드 문서와 토론 사이트에서는 I/O 스케줄러를 &lt;code class=&quot;highlighter-rouge&quot;&gt;CFG&lt;/code&gt;에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;NOOP&lt;/code&gt;이나 &lt;code class=&quot;highlighter-rouge&quot;&gt;Deadline&lt;/code&gt;으로 변경하는 것이 SSD의 레이턴시를 줄여줄 것이라고 말하고 있다.&lt;sup id=&quot;fnref:56:1&quot;&gt;&lt;a href=&quot;#fn:56&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:58&quot;&gt;&lt;a href=&quot;#fn:58&quot; class=&quot;footnote&quot;&gt;18&lt;/a&gt;&lt;/sup&gt;.
하지만 리눅스 버전 3.1부터, &lt;code class=&quot;highlighter-rouge&quot;&gt;CFQ&lt;/code&gt;는 SSD 드라이브를 위한 어느 정도의 최적화 알고리즘을 가지고 있다&lt;sup id=&quot;fnref:59&quot;&gt;&lt;a href=&quot;#fn:59&quot; class=&quot;footnote&quot;&gt;19&lt;/a&gt;&lt;/sup&gt;.
SSD로 주어지는 워크로드에 따라서 I/O 스케줄러별 성능 차이를 보여주는 벤치마크 자료들도 있다&lt;sup id=&quot;fnref:40:1&quot;&gt;&lt;a href=&quot;#fn:40&quot; class=&quot;footnote&quot;&gt;15&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:60&quot;&gt;&lt;a href=&quot;#fn:60&quot; class=&quot;footnote&quot;&gt;20&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:61&quot;&gt;&lt;a href=&quot;#fn:61&quot; class=&quot;footnote&quot;&gt;21&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:62&quot;&gt;&lt;a href=&quot;#fn:62&quot; class=&quot;footnote&quot;&gt;22&lt;/a&gt;&lt;/sup&gt;.
개인적인 생각으로는 워크로드가 아주 특별하지 않고 실제 응용 프로그램 벤치마킹 테스트에서 다른 I/O 스케줄러가 더 나은 성능을 보여주는 것이 아니라면,
굳이 &lt;code class=&quot;highlighter-rouge&quot;&gt;CFQ&lt;/code&gt; 스케줄러에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;NOOP&lt;/code&gt;이나 &lt;code class=&quot;highlighter-rouge&quot;&gt;Deadline&lt;/code&gt;으로 바꿀 필요는 없어 보인다.&lt;/p&gt;

&lt;h3 id=&quot;swap&quot;&gt;8.4. 스왑 (Swap)&lt;/h3&gt;

&lt;p&gt;Swap이 자주 사용되는 경우에는 많은 I/O 요청이 발생하기 때문에,
SSD 드라이브에 스왑 파티션이 있는 경우 SSD의 랜덤 쓰기가 자주 발생하며 그로 인해서 수명이 훨씬 더 빨리 줄어들 수 있다.
리눅스 커널에서는 &lt;code class=&quot;highlighter-rouge&quot;&gt;vm.swappiness&lt;/code&gt; 커널 파라미터를 기준으로 얼마나 자주 페이지들을 디스크로 스왑할지를 결정한다.
&lt;code class=&quot;highlighter-rouge&quot;&gt;vm.swappiness&lt;/code&gt; 값으 &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;부터 &lt;code class=&quot;highlighter-rouge&quot;&gt;100&lt;/code&gt;까지 설정할 수 있는데,
&lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;은 커널이 최대한 스왑을 하지 않도록 하며 &lt;code class=&quot;highlighter-rouge&quot;&gt;100&lt;/code&gt;은 최대한 커널이 디스크의 스왑 영역을 사용하도록 유도한다.
예를 들어 Ubuntu에서는 &lt;code class=&quot;highlighter-rouge&quot;&gt;vm.swappiness&lt;/code&gt; 기본 값이 &lt;code class=&quot;highlighter-rouge&quot;&gt;60&lt;/code&gt;이다. SSD 드라이브를 사용하는 경우에는 &lt;code class=&quot;highlighter-rouge&quot;&gt;vm.swappiness&lt;/code&gt; 값을
최대한 낮은 값으로 설정하여 SSD로 기록되는 데이터를 최소화하여 SSD 드라이브의 수명을 늘리는 것이 좋다&lt;sup id=&quot;fnref:56:2&quot;&gt;&lt;a href=&quot;#fn:56&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:63&quot;&gt;&lt;a href=&quot;#fn:63&quot; class=&quot;footnote&quot;&gt;23&lt;/a&gt;&lt;/sup&gt;.
어떤 가이드에서는 &lt;code class=&quot;highlighter-rouge&quot;&gt;vm.swappiness&lt;/code&gt; 값으로 &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;을 추천하는데, 실질적으로 &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;은 &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;과 동일한 효과를 낸다&lt;sup id=&quot;fnref:57:1&quot;&gt;&lt;a href=&quot;#fn:57&quot; class=&quot;footnote&quot;&gt;17&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:58:1&quot;&gt;&lt;a href=&quot;#fn:58&quot; class=&quot;footnote&quot;&gt;18&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h3 id=&quot;section-12&quot;&gt;8.5. 임시 파일&lt;/h3&gt;

&lt;p&gt;굳이 디스크에 기록되지 않아도 되는 모든 임시 파일들과 로그 파일들은 SSD 드라이브의 P/E cycle을 낭비하는 것이다.
그러한 파일들은 가능하다면 RAM을 이용한 &lt;code class=&quot;highlighter-rouge&quot;&gt;tmpfs&lt;/code&gt; 파일 시스템으로 유도하도록 하자&lt;sup id=&quot;fnref:56:3&quot;&gt;&lt;a href=&quot;#fn:56&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:57:2&quot;&gt;&lt;a href=&quot;#fn:57&quot; class=&quot;footnote&quot;&gt;17&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:58:2&quot;&gt;&lt;a href=&quot;#fn:58&quot; class=&quot;footnote&quot;&gt;18&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;This articles are translated to Korean with original author’s(&lt;a href=&quot;http://www.goossaert.com/&quot;&gt;Emmanuel Goossaert&lt;/a&gt;) permission. Really appreciate his effort and sharing.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Original articles :&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/&quot;&gt;Part 1: Introduction and Table of Contents&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-2-architecture-of-an-ssd-and-benchmarking/&quot;&gt;Part 2: Architecture of an SSD and Benchmarking&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-3-pages-blocks-and-the-flash-translation-layer/&quot;&gt;Part 3: Pages, Blocks, and the Flash Translation Layer&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-parallelism/&quot;&gt;Part 4: Advanced Functionalities and Internal Parallelism&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-5-access-patterns-and-system-optimizations/&quot;&gt;Part 5: Access Patterns and System Optimizations&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-6-a-summary-what-every-programmer-should-know-about-solid-state-drives/&quot;&gt;Part 6: A Summary – What every programmer should know about solid-state drives&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://csl.skku.edu/papers/CS-TR-2010-329.pdf&quot;&gt;Parameter-Aware I/O Management for Solid State Disks (SSDs), Kim et al., 2012&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:2:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:2:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:8&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;https://www.usenix.org/legacy/event/fast12/tech/full_papers/Min.pdf&quot;&gt;SFS: Random Write Considered Harmful in Solid State Drives, Min et al., 2012&lt;/a&gt; &lt;a href=&quot;#fnref:8&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.cse.ohio-state.edu/hpcs/WWW/HTML/publications/papers/TR-09-2.pdf&quot;&gt;Understanding Intrinsic Characteristics and System Implications of Flash Memory based Solid State Drives, Chen et al., 2009&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:1:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:1:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:1:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://research.microsoft.com/pubs/63596/usenix-08-ssd.pdf&quot;&gt;Design Tradeoffs for SSD Performance, Agrawal et al., 2008&lt;/a&gt; &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:5:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://bit.csc.lsu.edu/~fchen/paper/papers/hpca11.pdf&quot;&gt;Essential roles of exploiting internal parallelism of flash memory based solid state drives in high-speed data processing, Chen et al, 2011&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:3:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:3:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:3:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:3:4&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:3:5&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;6&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:26&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.storagereview.com/samsung_ssd_840_pro_review&quot;&gt;http://www.storagereview.com/samsung_ssd_840_pro_review&lt;/a&gt; &lt;a href=&quot;#fnref:26&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:27&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.storagereview.com/micron_p420m_enterprise_pcie_ssd_review&quot;&gt;http://www.storagereview.com/micron_p420m_enterprise_pcie_ssd_review&lt;/a&gt; &lt;a href=&quot;#fnref:27&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:47&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.acunu.com/2/post/2011/08/why-theory-fails-for-ssds.html&quot;&gt;http://www.acunu.com/2/post/2011/08/why-theory-fails-for-ssds.html&lt;/a&gt; &lt;a href=&quot;#fnref:47&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:53&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://blog.nuclex-games.com/2009/12/aligning-an-ssd-on-linux/&quot;&gt;http://blog.nuclex-games.com/2009/12/aligning-an-ssd-on-linux/&lt;/a&gt; &lt;a href=&quot;#fnref:53&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:54&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.linux-mag.com/id/8397/&quot;&gt;http://www.linux-mag.com/id/8397/&lt;/a&gt; &lt;a href=&quot;#fnref:54&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:55&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://tytso.livejournal.com/2009/02/20/&quot;&gt;http://tytso.livejournal.com/2009/02/20/&lt;/a&gt; &lt;a href=&quot;#fnref:55&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:55:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:43&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://rethinkdb.com/blog/page-alignment-on-ssds/&quot;&gt;http://rethinkdb.com/blog/page-alignment-on-ssds/&lt;/a&gt; &lt;a href=&quot;#fnref:43&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:43:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:44&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://rethinkdb.com/blog/more-on-alignment-ext2-and-partitioning-on-ssds/&quot;&gt;http://rethinkdb.com/blog/more-on-alignment-ext2-and-partitioning-on-ssds/&lt;/a&gt; &lt;a href=&quot;#fnref:44&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:16&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Trim_(computing)&quot;&gt;http://en.wikipedia.org/wiki/Trim_(computing)&lt;/a&gt; &lt;a href=&quot;#fnref:16&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:40&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://superuser.com/questions/228657/which-linux-filesystem-works-best-with-ssd/&quot;&gt;http://superuser.com/questions/228657/which-linux-filesystem-works-best-with-ssd/&lt;/a&gt; &lt;a href=&quot;#fnref:40&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:40:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:56&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;https://wiki.debian.org/SSDOptimization&quot;&gt;https://wiki.debian.org/SSDOptimization&lt;/a&gt; &lt;a href=&quot;#fnref:56&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:56:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:56:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:56:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:57&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://wiki.gentoo.org/wiki/SSD&quot;&gt;http://wiki.gentoo.org/wiki/SSD&lt;/a&gt; &lt;a href=&quot;#fnref:57&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:57:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:57:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:58&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;https://wiki.archlinux.org/index.php/Solid_State_Drives&quot;&gt;https://wiki.archlinux.org/index.php/Solid_State_Drives&lt;/a&gt; &lt;a href=&quot;#fnref:58&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:58:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:58:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:59&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;https://www.kernel.org/doc/Documentation/block/cfq-iosched.txt&quot;&gt;https://www.kernel.org/doc/Documentation/block/cfq-iosched.txt&lt;/a&gt; &lt;a href=&quot;#fnref:59&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:60&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.danielscottlawrence.com/blog/should_i_change_my_disk_scheduler_to_use_NOOP.html&quot;&gt;http://www.danielscottlawrence.com/blog/should_i_change_my_disk_scheduler_to_use_NOOP.html&lt;/a&gt; &lt;a href=&quot;#fnref:60&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:61&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.phoronix.com/scan.php?page=article&amp;amp;item=linux_iosched_2012&quot;&gt;http://www.phoronix.com/scan.php?page=article&amp;amp;item=linux_iosched_2012&lt;/a&gt; &lt;a href=&quot;#fnref:61&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:62&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.velobit.com/storage-performance-blog/bid/126135/Effects-Of-Linux-IO-Scheduler-On-SSD-Performance&quot;&gt;http://www.velobit.com/storage-performance-blog/bid/126135/Effects-Of-Linux-IO-Scheduler-On-SSD-Performance&lt;/a&gt; &lt;a href=&quot;#fnref:62&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:63&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.axpad.com/blog/301&quot;&gt;http://www.axpad.com/blog/301&lt;/a&gt; &lt;a href=&quot;#fnref:63&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sun, 17 Jul 2016 20:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/17/coding-for-ssd-part-5/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/17/coding-for-ssd-part-5/</guid>
        
        <category>ssd</category>
        
        <category>nand-flash</category>
        
        <category>garbage-collection</category>
        
        <category>LBA</category>
        
        <category>PBA</category>
        
        <category>block</category>
        
        <category>page</category>
        
        <category>clustered-block</category>
        
        
      </item>
    
      <item>
        <title>개발자를 위한 SSD (Coding for SSD) - Part 4 : 고급 기능과 내부 병렬 처리</title>
        <description>&lt;p&gt;이번 챕터에서는 SSD의 주요 기능인 TRIM과 Over-provisioning에 대해서 간단히 살펴보도록 하겠다.
또한 SSD의 내부 병렬 처리와 클러스터링 블록에 대해서도 같이 살펴보도록 하겠다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part4_1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;section&quot;&gt;5. 고급 기능&lt;/h2&gt;

&lt;h3 id=&quot;trim&quot;&gt;5.1. TRIM&lt;/h3&gt;

&lt;p&gt;용응 프로그램이 SSD의 모든 논리 블록 주소에 파일을 기록했다고 가정해보자.
그러면 SSD는 풀(full)로 사용되었다고 생각될 수 있다. 이제 이 모든 파일들이 지워졌다고 가정해보자.
파일 시스템은 SSD가 100% 비어 있다고 보지만,
SSD 컨트롤러는 호스트로부터 삭제된 논리 블록의 주소를 알지 못하기 때문에 실제 SSD 드라이브는 여전히 100% 사용중이라고 생각하게 된다.
SSD 컨트롤러는 호스트의 파일 시스템으로부터 덮어 쓰기 명령이 전달될 때에만 그 영역이 빈 공간이라고 판단할 수 있게 되는 것이다.&lt;/p&gt;

&lt;p&gt;이때 Garbage-collection 프로세스는 삭제된 파일과 연관된 블록들을 지울(Erase) 것이다.
결과적으로 블록이 “stale” 데이터를 가지고 있다는 것을 알아내는 순간 삭제(Erase)하는 대신 지연 처리되는 것인데, 이는 성능을 심각하게 떨어뜨리게 된다.
또 다른 우려 사항은 삭제된 파일들을 SSD 컨트롤러는 모르기 때문에,
Garbage-collection은 Wear leveling을 위해서 매번 삭제된 파일들의 데이터도 계속 복사해서 새로운 페이지로 저장해야 한다.
이는 SSD의 Write Amplication을 더 높이게 되고, 호스트의 포그라운드 워크로드의 처리를 방해하게 된다.&lt;/p&gt;

&lt;p&gt;지연된 삭제(Erase)로 인한 문제점을 해결하기 위한 방법이 TRIM 명령이다.
TRIM 명령은 운영 체제에서 의해서 해당 논리 공간이 더 이상 필요치 않다는 메시지를 SSD 컨트롤러로 전달해 주는 것이다.
이렇게 SSD 컨트롤러가 불필요한 블록들에 대한 정보를 가지고 있다면,
Garbage-collection 프로세스는 더 이상 불필요한 페이지들을 복사해서 이동하지 않아도 되며 언제든지 필요할 때 삭제(Erase)할 수 있게 되는 것이다.
TRIM 명령은 SSD 컨트롤러와 운영 체제 그리고 파일 시스템 3가지 컴포넌트가 모두 TRIM을 지원할 때에만 사용 가능하다.&lt;/p&gt;

&lt;p&gt;TRIM 명령의 위키피디아 페이지&lt;sup id=&quot;fnref:16&quot;&gt;&lt;a href=&quot;#fn:16&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;에는 TRIM 명령을 지원하는 운영 체제와 파일 시스템의 목록을 나열하고 있다.
ATA TRIM 명령은 리눅스 커널 2.6.33 버전부터 지원되기 시작했다.
하지만 ext2와 ext3 파일 시스템은 여전히 TRIM 명령을 지원하지 않으며, ext4와 XFS 파일 시스템은 TRIM 명령을 지원한다.
Mac OS 10.6.8 그리고 HFS+ 파일 시스템은 TRIM 명령을 지원하며,
윈도우 7에서는 SATA 인터페이스를 사용하는 SSD에서만 TRIM 명령을 지원하며 PCI Express 인터페이스에서는 지원하지 않는다.&lt;/p&gt;

&lt;p&gt;대 부분의 SSD 드라이브는 TRIM 명령을 지원하며, 앞으로의 쓰기 성능 향상을 위해서 Garbage-collection이 최대한 빨리 처리되도록 해줄 것이다.
그래서 가능하다면 TRIM이 지원되는 SSD를 사용할 것을 권장하며, 또한 운영 체제와 파일 시스템 레벨에서 TRIM이 가능하도록 준비하는 것이 좋다.&lt;/p&gt;

&lt;h3 id=&quot;over-provisioning&quot;&gt;5.2. Over-provisioning&lt;/h3&gt;

&lt;p&gt;Over-provisioning은 단순히 논리적인 블록보다 물리적인 블록의 수가 더 많도록 해주는 것인데,
일정 비율의 물리 블록을 SSD 컨트롤러는 볼 수 있지만 운영 체제나 파일 시스템은 보지 못하도록 예약해두는 것이다.
전문 SSD 제조사는 이미 7 ~ 25% 정도의 Over-provisioning 공간을 보유하고 있으며&lt;sup id=&quot;fnref:13&quot;&gt;&lt;a href=&quot;#fn:13&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;,
사용자는 단순히 가능한 물리 공간의 크기보다 작은 크기로 파티셔닝함으로써 더 많은 Over-provisioning 공간을 생성할 수 있다.
예를 들어서, 100GB SSD 드라이브에 90GB 파티션을 생성하면 나머지 10GB는 자동으로 Over-provisioning 공간으로 남겨지는 것이다.
Over-provisioning 공간은 운영 체제 레벨에서는 보이지 않더라도, SSD 컨트롤러는 여전히 그 공간을 볼 수 있고 사용할 수 있다.
SSD 제조사가 Over-provisioning 공간을 제공하는 주된 이유는 NAND 플래시 셀의 제한된 수명 주기를 극복하는 것이다.
보이지 않는 Over-provisioning 블록은 운영 체제에 보여지는 공간의 블록들이 수명이 다 되는 경우에 자동으로 대체되어서 사용된다.&lt;/p&gt;

&lt;p&gt;AnandTech는 Over-provisioning 공간이 SSD의 성능과 수명에 미치는 영향을 보여주는 재미있는 게시물&lt;sup id=&quot;fnref:34&quot;&gt;&lt;a href=&quot;#fn:34&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;을 공개했다.
그들의 연구에 따르면 드라이브 공간의 25%를 Over-provisioning 공간으로 예약해 두면 엄청난 성능 향상을 얻을 수 있다는 것이다.
또 다른 재미있는 게시물을 Percona에서도 공개했는데,
Intel 320 SSD로 테스트한 결과 SSD의 공간 사용률이 높아질수록 쓰기 스루풋은 점점 떨어진다는 것&lt;sup id=&quot;fnref:38&quot;&gt;&lt;a href=&quot;#fn:38&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;이다.&lt;/p&gt;

&lt;p&gt;왜 이런 현상이 발생하는 것일까?
Garbage-collection은 “stale” 페이지를 삭제(Erase)하기 위해서 백그라운드로 SSD 드라이브가 한가한 시간을 이용한다.
삭제(Erase) 오퍼레이션은 일반적인 데이터 쓰기보다 느리게 실행되므로 SSD가 지속적으로 과도한 랜덤 쓰기 부하가 있는 시스템에서는
Garbage-collection이 “stale” 페이지를 삭제(Erase)하기도 전에 “free” 페이지를 모두 소진해버리게 되는 것이다.
이때 FTL은 포그라운드의 랜덤 쓰기를 따라가지 못하고, Garbage-collection 프로세스는 호스트로부터 포그라운드 쓰기 요청이 들어오면 그때 동시에 삭제(Erase) 작업을 같이 하게 된다.
아래 그림 7에서 보여지는 바와 같이 벤치마킹의 결과에서 성능이 급격하게 떨어지는 것을 확인할 수 있다.
그래서 Over-provisioning 공간은 많은 쓰기 워크로드를 흡수해주는 버퍼 공간으로 작동하게 되는 것이다.
그리고 이 버퍼가 Garbage-collection이 워크로드를 따라잡을 수 있도록 충분한 시간을 만들어주는 것이다.
충분한 크기의 Over-provisioning 공간은 SSD가 사용되는 시스템의 워크로드에 따라 다른데,
일반적으로 지속적인 랜덤 쓰기 워크로드 환경에서는 25% 정도의 Over-provisioning 공간이 권장&lt;sup id=&quot;fnref:34:1&quot;&gt;&lt;a href=&quot;#fn:34&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;된다.
만약 워크로드가 그렇게 무겁지 않다면, 10 ~ 15% 정도의 Over-provisioning 공간으로도 충분할 수 있다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;over-provisioning-wear-leveling----&quot;&gt;Over-provisioning은 Wear-leveling과 성능 향상에 도움이 된다&lt;/h5&gt;

  &lt;p&gt;SSD 드라이브는 단순히 물리적인 공간보다 더 적은 크기로 논리적인 공간을 설정(포맷)하면 Over-provisioning 공간을 만들 수 있다.
남은 공간은 사용자에게는 보이지 않지만, SSD 컨트롤러는 여전히 그 공간을 사용할 수 있다.
Over-provisioning은 NAND 플래시 셀의 제한된 수명을 극복하기 위한 Wear-leveling 메커니즘에도 도움이 된다.
쓰기 부하가 그렇게 심하지 않은 경우에는 10 ~ 15% 정도의 Over-provisioning 공간으로도 충분하다.
하지만 지속적인 랜덤 쓰기가 과도하게 발생하는 경우에는 25% 정도의 Over-provisioning 공간이 성능을 향상시켜 줄 것이다.
Over-provisioning 공간은 NAND 플래시 블록의 버퍼처럼 작동하기 때문에,
쓰기가 포화(Write saturation)되는 시점에도 Garbage-collection이 과도한 쓰기를 견딜 수 있도록 해준다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Over-provisioning은 TRIM 명령이 지원되지 않는 경우에도 성능 향상을 제공할 것으로 예상(이는 단순히 나의 추측이며, 이를 증명할만한 문서는 찾지 못했다)할 수 있다.
75%의 공간만 운영 체제가 사용하며 나머지 25%의 공간이 Over-provisioning으로 설정된 시스템을 가정해보자.
SSD 컨트롤러는 전체 공간을 모두 사용할 수 있기 때문에 돌아가면서 이 100%의 공간이 사용되었다가 삭제될 것이다.
하지만 어느 한 순간에는 75%의 NAND 플래시 메모리 공간만 사용될 것이다.
이는 나머지 25%의 물리 메모리 공간은 실제 논리 블록 주소와 맵핑되어 있지 않기 때문에,
SSD 컨트롤러는 안전하게 해당 영역이 데이터를 가지고 있지 않다고 판단할 수 있게 되는 것이다.
그래서 Over-provisioning 공간에 대해서는 미리 Garbage-collection을 처리할 수 있으며,
이는 TRIM 명령이 지원되지 않는다 하더라도 TRIM의 효과를 낼 수 있게 되는 것이다.&lt;/p&gt;

&lt;h3 id=&quot;secure-erase&quot;&gt;5.3. Secure Erase&lt;/h3&gt;

&lt;p&gt;일부 SSD 컨트롤러는 ATA Secure Erase 기능을 제공하는데,
이는 사용자가 기록했던 모든 데이터를 삭제하고 FTL 맵핑 테이블을 초기화함으로써 SSD 드라이브를 초기 상태로 만들어서 성능을 회복(초기 상태로)시키는 것이 주요 목적이다.
하지만 ATA Secure Erase 기능이 NAND 플래시 메모리 셀의 P/E Cycle 한계를 초기화 시켜주는 것은 아니다.
이 기능은 스펙상으로는 상당히 기대되는 기능이지만, 이는 제조사의 기능 구현이 얼마나 정확한지에 따라 다르다.
2011년 Wei(외 여러 연구원)은 12개 모델의 SSD에 대한 조사 결과, 단지 8개 모델만 ATA Secure Erase 기능을 제공했으며
이중에서 3개 모델은 잘못된 방식(Buggy implementation)으로 구현되어 있었다&lt;sup id=&quot;fnref:11&quot;&gt;&lt;a href=&quot;#fn:11&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;성능과 관련된 사항은 중요하다. 하지만 보안과 관련된 문제는 더 중요하다. 그렇지만 보안과 관련된 사항은 이 문서의 주제와는 거리가 있다.
SSD의 데이터를 좀 더 신뢰성 있게 삭제(Erase)하는 방법에 대한 Stack Overflow 토의&lt;sup id=&quot;fnref:48&quot;&gt;&lt;a href=&quot;#fn:48&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:49&quot;&gt;&lt;a href=&quot;#fn:49&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;들이 있으니, 더 자세한 내용은 이 게시물들을 참조하도록 하자.&lt;/p&gt;

&lt;h3 id=&quot;native-command-queueing-ncq&quot;&gt;5.4. Native Command Queueing (NCQ)&lt;/h3&gt;

&lt;p&gt;NCQ(Native Command Queueing)는 SSD 드라이브가 내부적인 병렬 처리 능력을 이용해서 동시에 여러 호스트 명령을 처리할 수 있도록 해주는 Serial ATA의 기능이다&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;.
SSD 드라이브의 낮은 레이턴시에 더불어 최신의 SSD 드라이브는 호스트로부터의 레이턴시를 극복하기 위해서 NCQ를 사용한다.
예를 들어서 NCQ는 호스트 CPU가 바쁠때에는 드라이버가 항상 명령을 처리할 수 있도록 유입되는 명령의 우선순위를 높여줄 수 있다&lt;sup id=&quot;fnref:39&quot;&gt;&lt;a href=&quot;#fn:39&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;.
빠른 응답 속도를 위해서 새로운 드라이브는 NCQ를 사용하는데, NCQ는 호스트의 명령을 큐잉하여 우선순위를 재설정하고 때로는 지연처리를 하기도 한다&lt;sup id=&quot;fnref:39:1&quot;&gt;&lt;a href=&quot;#fn:39&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h3 id=&quot;power-loss-protection&quot;&gt;5.5. 전력 차단 보호 (Power-loss protection)&lt;/h3&gt;

&lt;p&gt;SSD 드라이브가 설치된 컴퓨터가 집에 있든지 데이터센터에 있든지 전원 실패(Power Failure, 정전)은 피할 수 없다.
일부 제조사는 SSD 드라이버에 수퍼 커패시터(Super capacity)를 포함해서,
컴퓨터의 전원 공급이 차단되었을 때 요청된 I/O를 처리할 수 있을 정도의 충분한 전원을 내장하여 항상 SSD 드라이버가 일관된 상태를 유지하도록 해준다.
하지만 문제는 모든 SSD 드라이버가 수퍼 커패시터나 전원 차단에 대한 보호 장치를 내장하고 있는 것은 아니며,
SSD 드라이브의 제품 소개서에 항상 명시하는 것도 아니다.
Secure Erase 기능과 같이 전원 보호 장치가 제대로 구현되었는지 명확하지 않다.&lt;/p&gt;

&lt;p&gt;2013년 Zheng(외 여러 연구원)은 15개 제품의 SSD 드라이브를 테스트&lt;sup id=&quot;fnref:72&quot;&gt;&lt;a href=&quot;#fn:72&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;했다.
다양한 전원 실패 상황으로 테스트를 진행한 결과, 15개 제품 중 13개 제품에서는 데이터 손실이 발생했으며 데이터 손상도 발생했다.
Luke Kenneth Casson Leighton의 전원 실패(Power Fault) 테스트에서는 4개 제품중 3개 SSD 드라이브의 데이터가 손상되었으며,
나머지 1개(Intel SSD 드라이브)만 일관된 상태를 유지했다&lt;sup id=&quot;fnref:73&quot;&gt;&lt;a href=&quot;#fn:73&quot; class=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;SSD는 아직은 상당히 젊은 기술이기 때문에, 전원 실패에 대한 데이터 손상 문제는 머지않아 해결될 것으로 생각된다.
하지만 현재 시점에서는 무정전 전원 장치(UPS, uninterruptible power supply)에 대한 투자는 충분히 필요할 것으로 보인다.
또한 다른 데이터 저장 솔루션과 마찬가지로 SSD도 정기적인 백업이 필요할 것으로 보인다.&lt;/p&gt;

&lt;h2 id=&quot;ssd---&quot;&gt;6. SSD의 내부 병렬 처리&lt;/h2&gt;

&lt;h3 id=&quot;io--&quot;&gt;6.1. 제한된 I/O 버스 대역폭&lt;/h3&gt;

&lt;p&gt;물리적인 한계로 인해서, 비동기 방식의 NAND 플래시 I/O 버스는 32~40MB/s의 대역폭 이상을 서비스할 수 없다&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;.
SSD 제조사의 입장에서 성능을 향상시킬 수 있는 유일한 방법은 다수의 패키지가 병렬로 처리되거나 인터리빙(interleave) 모드로 작동하도록 설계를 변경하는 것이다.
인터리빙 모드의 자세한 설명은 Kim et al. 의 2013년 문서인 “Parameter-Aware I/O Management for Solid State Disks (SSDs)”의 2.2를 참조하도록 하자.&lt;/p&gt;

&lt;p&gt;SSD아키텍처에서 여러 레벨의 내부적인 병렬 처리 능력을 묶어서, 여러 칩에 걸쳐서 동시에 2개 이상의 블록을 액세스할 수 있다.
이렇게 동시 접근 가능한 블록을 묶어서 “Clustered block”이라고 한다. SSD 드라이버의 내부적인 병렬 처리에 관련된 상세한 내용은 이 문서의 방향은 아니지만,
Clustered block과 병렬 처리의 레벨에 대해서는 간략하게 살펴보도록 하겠다.
이 주제에 관련된 더 상세한 내용에 대해서 살펴보고자 한다면, 이 두개&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;13&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:3:1&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;의 문서를 살펴보도록 하자.
더불어 copyback 명령 및 Plane간 데이터 전송등 고급 기능에 대한 내용은 이 문서[5]를 더 참조해보자.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;internal-parallelism&quot;&gt;내부 병렬 처리 (Internal parallelism)&lt;/h5&gt;

  &lt;p&gt;내부적으로 여러 레벨의 병렬 처리 능력이 NAND 플래시 칩의 다른 위치에서 2개 이상의 블록을 동시에 읽을 수 있도록 해주는데,
이를 “Clustered block”이라고 한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;section-1&quot;&gt;6.2. 병렬 처리&lt;/h3&gt;

&lt;p&gt;그림 6은 NAND 플래시 패키지들이 계층형으로 구성되어 있는 SSD 드라이브의 내부를 보여주고 있다.
SSD 드라이브는 채널(Channel), 패키지(Package), 칩(Chip), 플레인(Plane), 그리고 블록(Block)과 페이지(Page)등의 다양한 레벨로 구성되어 있다.
문서 &lt;sup id=&quot;fnref:3:2&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;에 설명된 것처럼, 아래와 같은 병렬 처리 능력을 제공한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Channel-level parallelism. 플래시 컨트롤러는 여러 채널을 통해서 플래시 패키지와 통신한다. 이 채널들은 서로 독립적이며 동시에 액세스 가능하다. 각 개별 채널은 여러 패키지에 의해서 공유된다.&lt;/li&gt;
  &lt;li&gt;Package-level parallelism. 채널의 패키지들은 독립적으로 접근 가능하다. 동일 채널을 공유하는 패키지들은 인터리빙 모드로 동시에 명령 실행에 사용될 수 있다.&lt;/li&gt;
  &lt;li&gt;Chip-level parallelism. 패키지는 하나 또는 두개의 칩(Chips, 때로는 “dies”라고도 불림)을 가지며, 각 칩들은 독립적으로 병렬 접근이 가능하다.&lt;/li&gt;
  &lt;li&gt;Plane-level parallelism. 각 칩은 2개 이상의 플레인을 가지는데, 동일 오퍼레이션(Read, Write, Erase)은 하나의 칩내의 여러 플레인에 대해서 동시 실행이 될 수 있다. 플레인은 블록들을 가지며, 각 블록들은 다시 페이지들을 가진다. 또한 플레인은 플레인 단위의 오퍼레이션에 사용되는 레지스터를 가진다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part4_2.jpg&quot; alt=&quot;그림 6: NAND 플래시 패키지&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;clustered-blocks&quot;&gt;6.3. Clustered blocks&lt;/h3&gt;

&lt;p&gt;여러 칩에 걸쳐서 한번에 액세스될 수 있는 여러 개의 블록을 “Clustered block”&lt;sup id=&quot;fnref:2:1&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;13&lt;/a&gt;&lt;/sup&gt;이라고 하는데,
RAID 시스템&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;14&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:5:1&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;에서 사용되는 스트라이핑(striping)과 비슷한 아이디어이다.&lt;/p&gt;

&lt;p&gt;한번에 접근 가능한 논리 블록 주소들은 개별 플래시 패키지에서 서로 다른 SSD 칩들에 걸쳐서 스트라이핑된다.
이는 FTL의 맵핑 알고리즘 덕분에 가능하며, 이는 Clustered block에 포함되는 블록들이 서로 연속된 주소이든지 관계없이 독립적이다.
스트라이핑 블록들은 여러 채널을 동시에 사용할 수 있으며, 그 채널들의 대역폭을 묶어서 생각할 수 있다.
또한 읽기와 쓰기 그리고 삭제(Erase) 오퍼레이션을 병렬로 처리할 수 있다.
이는 Clustered block 사이즈 또는 그 배수 크기의 I/O 오퍼레이션은 SSD의 다양한 내부 병렬 처리 능력을 최대한 활용할 수 있다는 것을 보장한다.
Clustered block에 대한 자세한 내용은 섹션 8.2와 8.3을 참조하도록 하자.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;This articles are translated to Korean with original author’s(&lt;a href=&quot;http://www.goossaert.com/&quot;&gt;Emmanuel Goossaert&lt;/a&gt;) permission. Really appreciate his effort and sharing.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Original articles :&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/&quot;&gt;Part 1: Introduction and Table of Contents&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-2-architecture-of-an-ssd-and-benchmarking/&quot;&gt;Part 2: Architecture of an SSD and Benchmarking&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-3-pages-blocks-and-the-flash-translation-layer/&quot;&gt;Part 3: Pages, Blocks, and the Flash Translation Layer&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-parallelism/&quot;&gt;Part 4: Advanced Functionalities and Internal Parallelism&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-5-access-patterns-and-system-optimizations/&quot;&gt;Part 5: Access Patterns and System Optimizations&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-6-a-summary-what-every-programmer-should-know-about-solid-state-drives/&quot;&gt;Part 6: A Summary – What every programmer should know about solid-state drives&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:16&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Trim_(computing)&quot;&gt;http://en.wikipedia.org/wiki/Trim_(computing)&lt;/a&gt; &lt;a href=&quot;#fnref:16&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:13&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Write_amplification&quot;&gt;http://en.wikipedia.org/wiki/Write_amplification&lt;/a&gt; &lt;a href=&quot;#fnref:13&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:34&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.anandtech.com/show/6489&quot;&gt;http://www.anandtech.com/show/6489&lt;/a&gt; &lt;a href=&quot;#fnref:34&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:34:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:38&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.ssdperformanceblog.com/2011/06/intel-320-ssd-random-write-performance/&quot;&gt;http://www.ssdperformanceblog.com/2011/06/intel-320-ssd-random-write-performance/&lt;/a&gt; &lt;a href=&quot;#fnref:38&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:11&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;https://www.usenix.org/legacy/event/fast11/tech/full_papers/Wei.pdf&quot;&gt;Reliably Erasing Data From Flash-Based Solid State Drives, Wei et al., 2011&lt;/a&gt; &lt;a href=&quot;#fnref:11&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:48&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://security.stackexchange.com/questions/12503/can-wiped-ssd-data-be-recovered&quot;&gt;http://security.stackexchange.com/questions/12503/can-wiped-ssd-data-be-recovered&lt;/a&gt; &lt;a href=&quot;#fnref:48&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:49&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://security.stackexchange.com/questions/5662/is-it-enough-to-only-wipe-a-flash-drive-once&quot;&gt;http://security.stackexchange.com/questions/5662/is-it-enough-to-only-wipe-a-flash-drive-once&lt;/a&gt; &lt;a href=&quot;#fnref:49&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://bit.csc.lsu.edu/~fchen/paper/papers/hpca11.pdf&quot;&gt;Essential roles of exploiting internal parallelism of flash memory based solid state drives in high-speed data processing, Chen et al, 2011&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:3:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:3:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:39&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Native_Command_Queuing&quot;&gt;http://en.wikipedia.org/wiki/Native_Command_Queuing&lt;/a&gt; &lt;a href=&quot;#fnref:39&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:39:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:72&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;https://www.usenix.org/conference/fast13/technical-sessions/presentation/zheng&quot;&gt;Understanding the Robustness of SSDs under Power Fault, Zheng et al., 2013&lt;/a&gt; — &lt;a href=&quot;https://news.ycombinator.com/item?id=7047118&quot;&gt;discussion on HN&lt;/a&gt; &lt;a href=&quot;#fnref:72&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:73&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://lkcl.net/reports/ssd_analysis.html&quot;&gt;http://lkcl.net/reports/ssd_analysis.html&lt;/a&gt; - &lt;a href=&quot;https://news.ycombinator.com/item?id=6973179&quot;&gt;discussion on HN&lt;/a&gt; &lt;a href=&quot;#fnref:73&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://research.microsoft.com/pubs/63596/usenix-08-ssd.pdf&quot;&gt;Design Tradeoffs for SSD Performance, Agrawal et al., 2008&lt;/a&gt; &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:5:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://csl.skku.edu/papers/CS-TR-2010-329.pdf&quot;&gt;Parameter-Aware I/O Management for Solid State Disks (SSDs), Kim et al., 2012&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:2:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.cse.ohio-state.edu/hpcs/WWW/HTML/publications/papers/TR-09-2.pdf&quot;&gt;Understanding Intrinsic Characteristics and System Implications of Flash Memory based Solid State Drives, Chen et al., 2009&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sat, 16 Jul 2016 20:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/16/coding-for-ssd-part-4/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/16/coding-for-ssd-part-4/</guid>
        
        <category>ssd</category>
        
        <category>nand-flash</category>
        
        <category>garbage-collection</category>
        
        <category>LBA</category>
        
        <category>PBA</category>
        
        <category>block</category>
        
        <category>page</category>
        
        <category>clustered-block</category>
        
        
      </item>
    
      <item>
        <title>개발자를 위한 SSD (Coding for SSD) - Part 3 : 페이지 &amp; 블록 &amp; FTL(Flash Translation Layer)</title>
        <description>&lt;p&gt;이번 챕터에서는 데이터 쓰기가 Block과 Page 레벨에서 어떻게 처리되는지,
그리고 쓰기 시에 발생하는 “Write Amplication”과 “Wear Leveling”의 기본적인 개념을 살펴보도록 하겠다.
추가로 FTL(Flash Translation Layer)이 무엇인지,
그리고 FTL의 2가지 목적인 논리적 블록 맵핑(Logical Block Mapping, 여기에서는 Hybrid Log Block Mapping 위주로)과 Garbage-collection도 같이 살펴볼 것이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part3_1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;section&quot;&gt;3. 기본 오퍼레이션&lt;/h2&gt;

&lt;h3 id=&quot;erase&quot;&gt;3.1. 읽기 &amp;amp; 쓰기 &amp;amp; 삭제(Erase)&lt;/h3&gt;

&lt;p&gt;NAND 플래시 메모리의 구성 특성상, 특정 셀을 단독으로 읽고 쓰는 작업은 불가능하다.
메모리는 그룹핑되어 있으며, 아주 특별한 방법으로만 접근할 수 있다.
그래서 NAND 플래시 메모리의 특별한 방법을 숙지하는 것은 SSD의 데이터 구조를 최적화하고 작동 방식을 이해하는데 있어서 꼭 필요한 부분이다.
이번 섹션에서는 SSD의 읽고 쓰기 그리고 삭제(Erase) 오퍼레이션이 실행되는 방법들을 살펴 보도록 하겠다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;reads-are-aligned-on-page-size&quot;&gt;읽기는 페이지 사이즈 단위로 실행(Reads are aligned on page size)&lt;/h5&gt;

  &lt;p&gt;한번에 하나의 페이지보다 작은 크기의 데이터를 읽을 수는 없다.
물론 사용자는 운영 체제에게 단 하나의 바이트만 읽기를 요청할 수는 있지만,
실제 SSD는 하나의 페이지를 통째로 읽은 다음 불 필요한 데이터는 모두 버리고 사용자가 요청한 한 바이트만 반환하는 것이다.
즉 불필요한 데이터를 많이 읽게 되는 것이다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;writes-are-aligned-on-page-size&quot;&gt;쓰기는 페이지 사이즈 단위로 실행 (Writes are aligned on page size)&lt;/h5&gt;

  &lt;p&gt;쓰기를 실행할 때에도 SSD는 페이지 단위로, 하나의 페이지 또는 여러 개의 페이지로 실행된다.
그래서 단 하나의 바이트만 기록하는 경우에도 반드시 전체 페이지가 기록되어야 한다.
이렇게 필요 이상으로 쓰기가 발생하는 것(Write Overhead)을 “Write Amplication”이라고 하는데,
이는 섹션 3.3에서 자세히 설명하고 있다.
SSD의 페이지에 데이터를 쓰는 것을 “프로그램 (program)”한다 라고도 하며,
많은 SSD 관련 문서에서 쓰기(Write)와 프로그램(Program)은 자주 혼용되기도 한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;overwrite---pages-cannot-be-overwritten&quot;&gt;페이지는 덮어 쓰기(Overwrite)될 수 없다 (Pages cannot be overwritten)&lt;/h5&gt;

  &lt;p&gt;NAND 플레시 메모리의 페이지는 반드시 “free” 상태일때에만 쓰기를 할 수 있다.
데이터가 변경되면, 페이지의 내용은 내부 레지스터로 복사된 후 레지스터에서 변경되어 새로운 “free” 상태의 페이지로 기록되는 것이다.
이를 “Read-Modify-Write”라고 한다.
SSD에서 데이터는 다른 페이지로 이동하지 않고 변경될 수 없다(in-place update가 불가능).
이렇게 변경된 데이터가 새로운 페이지에 완전히 기록되면, 원본 페이지는 “stale”로 마킹되고 삭제(Erase)되기 전까지 그 상태로 남게 된다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;erase-----erases-are-aligned-on-block-size&quot;&gt;삭제(Erase)는 블록 사이즈 단위로 실행 (Erases are aligned on block size)&lt;/h5&gt;

  &lt;p&gt;페이지는 덮어 쓰기가 불가능하기 때문에 한번 “stale” 상태로 된 페이지는 반드시 삭제(Erase)하는 작업을 거쳐서 “free” 상태로 전이할 수 있다.
그러나 삭제는 단일 페이지 단위로 처리될 수 없고, 그 페이지가 포함된 블록을 통째로 삭제해야 한다.
사용자는 읽기와 쓰기 명령만 데이터 액세스를 위해서 사용할 수 있으며, 삭제 명령은 SSD 컨트롤러가 “free” 공간이 필요할 때
자동적으로 내부 명령을 실행해서 Garbage-collection을 실행할 때 사용된다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;section-1&quot;&gt;3.2. 쓰기 예제&lt;/h3&gt;

&lt;p&gt;아래의 그림 4는 SSD에 데이터가 기록되는 과정을 보여주고 있다.
설명을 단순화하기 위해서, 이 그림에서는 단 2개의 블록과 각 블록의 4개의 페이지만을 가지고 있도록 그려졌지만 여전히 NAND 플래시 패키지의 전체 구성을 표현하고 있다.
그림의 각 단계에서 오른쪽의 텍스트는 어떤 일이 발생하고 있는지를 설명하고 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part3_2.jpg&quot; alt=&quot;그림 4: SSD의 데이터 쓰기&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Initial Configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;처음 상태로, 2000번 블록은 “free” 상태이며, 1000번 블록은 3개의 이미 사용된 “used” 페이지(PPN=0,1,2)와 1개의 “free” 페이지(PPN=3)를 가지고 있다.
여기에서 PPN은 물리 페이지 번호(Physical Page Number)를 의미한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Writing a page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1000번 블록의 PPN=0 페이지가 “x’“로 업데이트되었다.
페이지는 덮어 쓰기될 수 없으므로 기존 “x” 데이터를 가진 PPN=0 페이지는 “stale” 상태로 바뀌고
새로운 버전의 데이터가 “free” 페이지였던 PPN=3 페이지로 기록되었다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Erasing a block&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Garbage-collection은 1000번 블록의 모든 유효한 데이터(“stale” 상태의 PPN=0는 남기고)를 2000번 블록으로 복사한다.
새로운 쓰기를 받아들이기 위해서 1000번 블록은 삭제된다. 블록은 지정된 횟수(P/E cycles)만큼만 삭제 될 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;write-amplification&quot;&gt;3.3. Write amplification&lt;/h3&gt;

&lt;p&gt;쓰기는 페이지 사이즈에 맞춰서(Aligned) 실행되므로, 페이지 사이즈에 일치하지 않는 모든 쓰기는
필요 이상의 부가적인 쓰기(Write amplification &lt;sup id=&quot;fnref:13&quot;&gt;&lt;a href=&quot;#fn:13&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;)를 필요로 한다.
한 바이트 쓰기는 결국 하나의 페이지를 통째로 쓰기해야 하므로,
페이지 사이즈가 16KB인 SSD에서는 16KB를 기록해야 하고 이는 상당히 비효율적이다.&lt;/p&gt;

&lt;p&gt;그러나 이것만 SSD의 문제점은 아니다. 필요 이상의 데이터를 쓰게 되면, 필요 이상의 내부 오퍼레이션을 유발하게 된다.
페이지 크기에 맞춰지지 않은 쓰기는 먼저 해당 페이지의 데이터를 캐시로 읽어야 하며,
다시 페이지에 기록되어야 하기 때문에 즉시 페이지에 기록하는 것보다 느리게 작동한다.
이런 현상을 “read-modify-write”라고 하는데, 가능하다면 이런 현상은 피하는 것이 좋다 &lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-2&quot;&gt;페이지 사이즈보다 작은 데이터 쓰기는 피하자&lt;/h5&gt;

  &lt;p&gt;“Write Amplication”과 “Read-Modify-Write” 현상을 최소화하기 위해서, NAND 플래시 페이지의 크기보다 작은 데이터의 쓰기는 가능하면 피하도록 하자.
현재 최대 페이지 사이즈가 16KB이므로, 16KB 이상의 데이터 쓰기를 권장한다.
이 크기는 SSD 모델에 따라서 가변적인데, 앞으로 SSD가 발전하면서 페이지의 크기가 더 증가할 수도 있고 그때에는 데이터 쓰기의 단위를 더 늘려야 할 필요도 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;align-writes&quot;&gt;쓰기 맞춤 (Align writes)`&lt;/h5&gt;

  &lt;p&gt;데이터의 쓰기는 단일 페이지의 크기에 맞추거나, 여러 페이지의 크기에 맞춰서 실행하도록 하자.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;buffer-small-writes&quot;&gt;작은 데이터의 쓰기는 버퍼링 (Buffer small writes)`&lt;/h5&gt;

  &lt;p&gt;스루풋을 최대화하기 위해서, 가능하면 작은 쓰기는 메모리에 버퍼링했다가 버퍼가 가득 차면 단일 쓰기로 최대한 많은 데이터를 기록하도록 하자.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;wear-leveling&quot;&gt;3.4. Wear leveling&lt;/h3&gt;

&lt;p&gt;섹션 1.1에서 살펴보았듯이, 프로그램-삭제(P/E Cycles) 회수가 제한되어 있으므로 NAND 플래시 셀은 제한된 수명을 가지게 된다.
예를 들어서 하나의 블록에만 데이터를 읽고 쓰는 가상의 SSD를 가정해보면, 이 블록은 아주 빨리 P/E 사이클 제한을 넘어서게 되어서 사용하지 못하게 될 것이다.
그러면 SSD컨트롤러는 이 블록을 “사용 불가능”으로 마킹하게 된다. 결과적으로 SSD의 전체 사용 가능한 공간이 줄어들게 된다.
500GB 용량의 SSD 드라이브를 구매했는데 2년 후에는 250GB 공간만 남게 된다면, 얼마나 짜증나겠는가?&lt;/p&gt;

&lt;p&gt;이러한 이유로 SSD 컨트롤러의 중요한 역할 중 하나는, SSD의 전체 블록에 대해서 P/E cycle이 골고루 분산되도록 쓰기(“Wear leveling”)를 실행하는 것이다.
이상적으로는 모든 블록이 P/E Cycle 한계에 동시에 도달하여 한번에 모든 블록이 사용 불가능 상태로 만드는 것이다 &lt;sup id=&quot;fnref:12&quot;&gt;&lt;a href=&quot;#fn:12&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:14&quot;&gt;&lt;a href=&quot;#fn:14&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;최고의 “Wear leveling”을 위해서, SSD는 쓰기가 발생하면 현명하게 블록을 선택해야 하며 때로는 특정 블록을 주위로 옮겨야 할 수도 있다.
이 과정에서 또 다른 “Write Amplication”이 발생하는 것이다.
그래서 블록 관리는 “Write Amplication”과 “Wear Leveling”의 사이에서 적절히 타협점을 찾아야 하는 것이다.
그래서 SSD 제조사들은 Garbage-collection과 같은 “Wear Leveling”을 위한 기능들을 가진 제품을 출시하고 있는 것이다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;wear-leveling-1&quot;&gt;Wear leveling&lt;/h5&gt;

  &lt;p&gt;NAND 플래시 셀은 너무 빈번하게 쓰기 삭제 과정을 거치면 사용 불가능 상태(wearing off)가 되기 때문에,
셀 간의 작업을 분산하여 각 블록들이 P/E Cycle 한계에 동시에 도달하도록 하는 것이 FTL(Flash Translation Layer)의 중요한 목표 중 하나이다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;ftl-flash-translation-layer&quot;&gt;4. FTL (Flash Translation Layer)&lt;/h2&gt;

&lt;h3 id=&quot;ftl-&quot;&gt;4.1. FTL의 필요성&lt;/h3&gt;

&lt;p&gt;업계에 SSD가 이렇게 쉽게 받아들여지게 만든 중요한 요소 중 하나는 SSD가 HDD와 동일한 호스트 인터페이스를 이용한다는 것이다.
물론 현재의 LBA(Logical Block Address) 어레이는 덮어쓰기가 가능한 HDD에서만 적합한 요소이며, SSD에는 적합하진 않지만 말이다.
이 때문에 NAND 플래시 메모리는 내부적인 특성을 숨기고 LBA 어레이를 호스트로 노출하기 위해서 부가적인 컴포넌트를 필요로 한다.
이 컴포넌트를 FTL(Flash Translation Layer)하고 하며, SSD 컨터롤러 내부에 위치하고 있다.
FTL은 아주 중요한 역할을 담당하며, 논리적 블록 맵핑(Logical Block Mapping)과 Garbage-collection 2개의 중요한 부분을 담당한다.&lt;/p&gt;

&lt;h3 id=&quot;logical-block-mapping&quot;&gt;4.2. 논리적 블록 맵핑 (Logical block mapping)&lt;/h3&gt;

&lt;p&gt;논리적인 블록 맵핑은 호스트 영역의 논리 주소(LBA, Logical Block Address)를 NAND 플래시 메모리의 물리적 주소(PBA, Physical Block Address)로 변환해주는 역할을 담당한다.
블록 맵핑은 LBA와 PBA로 구성된 테이블을 가지며, 이 맵핑 테이블은 빠른 액세스를 위해서 SSD의 메모리(RAM)에 저장되며,
전원이 꺼지거나 만약의 경우를 대비해서 SSD의 플래시 메모리에도 저장된다.
SSD의 전원이 켜지면 플래시 메모리에 저장된 맵핑 테이블을 읽어서 메모리(RAM)에 로딩된다 &lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:5:1&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;블록 맵핑을 구현하는 단순한 방법은 페이지 단위로 맵핑 테이블을 구성하는 것이다. 이 방법은 아주 유연하지만,
맵핑 테이블 자체가 아주 많은 메모리를 사용하게 된다는 큰 단점이 있다.
필요한 메모리가 커지면 SSD의 생산 단가가 상당히 높아지게 된다.
이를 해결하기 위한 방법으로는 페이지 단위가 아니라 블록 단위의 맵핑을 이용할 수도 있다.
SSD 드라이브가 256개의 페이지를 가지고 있다고 가정해보면,
블록 단위의 맵핑은 페이지 단위의 맵핑보다는 256배 적은 메모리를 필요로 하게 되므로 그만큼 적은 메모리가 필요하게 되는 것이다.
그러나 페이지 하나만 기록되어도 될 정도의 작은 데이터를 자주 업데이트하는 경우에는 불필요하게 블록을 통째로 기록해야 하기 때문에 불필요한 쓰기가 많이 발생하게 된다.
이는 SSD의 “Write Amplication”을 증가시키기 때문에 블록 단위의 맵핑은 상당히 비효율적이라고 볼 수 있다 &lt;sup id=&quot;fnref:1:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:2:1&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;블록 레벨과 페이지 레벨 맵핑의 트레이드 오프(Trade-off)는 성능과 용량의 문제라고 볼 수 있다.
그래서 일부 연구원들에 의해서 “Hybrid” 한 방법&lt;sup id=&quot;fnref:10&quot;&gt;&lt;a href=&quot;#fn:10&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;들이 제시되었는데,
이 중에서 가장 일반적인 방법이 Log Structrued 파일 시스템과 비슷한 형태의 Log-block 맵핑이다.
유입되는 쓰기 오퍼레이션은 시퀀셜하게 로그 블록에 기록되고,
로그 블록이 꽉 채워지면 동일한 LBN(Logical block Number)을 가지는 블록의 데이터와 병합하여 새로운 “free” 블록으로 기록하는 방법이다.
이 방법에서 로그 블록은 몇 개(a few)만 유지되기 때문에, 페이지 단위의 맵핑은 아주 소량만 관리되면 된다 &lt;sup id=&quot;fnref:9&quot;&gt;&lt;a href=&quot;#fn:9&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:10:1&quot;&gt;&lt;a href=&quot;#fn:10&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;아래의 그림 5는 단순화한 Hybrid log block FTL을 보여주고 있는데,
각 블록은 4개의 페이지만을 가지고 있다.
4번의 쓰기가 FTL에 의해서 처리되고 모든 페이지들이 가득 데이터를 가지게 될 것이다.
논리적인 페이지 번호 5와 9는 &lt;code class=&quot;highlighter-rouge&quot;&gt;LBN=1&lt;/code&gt;과 연결되며, &lt;code class=&quot;highlighter-rouge&quot;&gt;LBN=1&lt;/code&gt;은 물리 주소 1000번과 연결되어 있다.
초기 LBN=1이 로그 블록 맵핑 테이블에 있을 때는, 모든 물리 페이지의 옵셋은 NULL이며 1000번 로그 블록은 비어 있는 상태이다.&lt;/p&gt;

&lt;p&gt;처음 &lt;code class=&quot;highlighter-rouge&quot;&gt;b’&lt;/code&gt;가 &lt;code class=&quot;highlighter-rouge&quot;&gt;LPN=5&lt;/code&gt;에 기록되고 로그 블록 맵핑 테이블(PBN=1000, log block #1000)에 의해서 &lt;code class=&quot;highlighter-rouge&quot;&gt;LBN=1&lt;/code&gt;로 연결된다.
즉 페이지 &lt;code class=&quot;highlighter-rouge&quot;&gt;b’&lt;/code&gt;는 1000번 블록의 옵셋 0 위치에 기록된 것이다.
맵핑의 메타 데이터가 업데이트되어야 하는데, 이를 위해서 물리 옵셋이 논리 옵셋 1(예를 들어서)의 위치에 NULL에서 0으로 저장된다.&lt;/p&gt;

&lt;p&gt;쓰기 오퍼레이션은 계속 유입되고 그에 맞게 맵핑 메타 데이터도 변경된다.
1000번 로그 블록이 가득 채워지면, 3000번 논리 데이터 블록과 병합된다.
이 정보는 데이터 블록 맵핑 테이블을 통해서 확인할 수 있다.
병합 작업의 결과는 “free” 상태였던 9000번 블록에 기록되는데,
이 작업이 끝나면 1000번과 3000번 블록은 삭제(Erase)되어 “free” 블록이 되고 9000번 블록은 데이터 블록이 된다.
데이터 블록 맵핑 테이블의 &lt;code class=&quot;highlighter-rouge&quot;&gt;LBN=1&lt;/code&gt;을 위한 메타 데이터는 처음 3000번 데이터 블록에서 9000번 데이터 블록으로 업데이트된다.&lt;/p&gt;

&lt;p&gt;여기에서 중요한 것은 4번의 쓰기가 2개의 LPN에만 집중되어 있다는 것이다.
로그 블록 맵핑 방법은 병합이 진행되는 동안 “Write Amplication”을 줄이기 위해서,
&lt;code class=&quot;highlighter-rouge&quot;&gt;b’&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;d’&lt;/code&gt;는 무시하고 더 최신의 &lt;code class=&quot;highlighter-rouge&quot;&gt;b”&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;d”&lt;/code&gt;만 데이터 블록에 저장한다는 것이다.
마지막으로 로그 블록이 병합되기 전에 최근 업데이트된 데이터를 읽으려고 하면,
그 데이터는 로그 블록에서 읽어야 한다. 물론 이미 병합된 데이터라면 데이터 블록을 읽어야 할 것이다.
이 때문에 읽기 요청은 로그 블록 맵핑 테이블과 데이터 블록 맵핑 테이블을 모두 읽어야 하는 이유인데, 이는 그림 5에 보여지고 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part3_3.jpg&quot; alt=&quot;그림 5: Hybrid 로그 블록 FTL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;로그 블록 FTL은 더 나은 최적화를 할 수 있도록 해주는데, 그 중에서도 중요한 것은 “switch-merge”(“swap-merge”이라고도 함)이다.
논리 블록의 주소가 한번에 기록된다고 가정해보자.
이는 그 논리 주소의 새로운 데이터들이 동일한 로그 블록에 기록된다는 것을 의미한다.
이 로그 블록은 전체 논리 블록의 데이터를 모두 가지기 때문에,
이 로그 블록을 별도의 병합하여 새로운 블록으로 옮기는 과정은 불필요한 작업(병합전과 병합후의 블록이 동일한 데이터를 가지게 될 것이므로)이 될 것이다.
이런 경우에는 단순히 로그 블록 맵핑을 거치지 않고 데이터 블록 맵핑을 바로 변경할 수 있으면(데이터 블록 맵핑의 메타 데이터만 변경하면 되므로) 더 빠를 것이다.
이렇게 데이터 블록 맵핑 테이블에서 데이터 블록과 로그 블록을 바꾸는(switch) 것을 “switch-merge”라고 한다.&lt;/p&gt;

&lt;p&gt;로그 블록 맵핑 스키마는 많은 논문의 주제가 되곤 했으며, FAST (Fully Associative Sector Translation),
수퍼 블록 맵핑 그리고 Flexible Group Mapping&lt;sup id=&quot;fnref:10:2&quot;&gt;&lt;a href=&quot;#fn:10&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; 등과 같은 발전을 이루어 왔다.
Mitsubishi 알고리즘과 SSR&lt;sup id=&quot;fnref:9:1&quot;&gt;&lt;a href=&quot;#fn:9&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; 등의 맵핑 스키마도 있다.
아래는 FTL과 맵핑 스키마를 배우기 위한 좋은 자료들이다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“A Survey of Flash Translation Layer“, 정 태선 외, 2009&lt;sup id=&quot;fnref:9:2&quot;&gt;&lt;a href=&quot;#fn:9&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;“A Reconfigurable FTL (Flash Translation Layer) Architecture for NAND Flash-Based Applications“, 박 찬익 외, 2008 &lt;sup id=&quot;fnref:10:3&quot;&gt;&lt;a href=&quot;#fn:10&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;ftl-flash-translation-layer-1&quot;&gt;FTL (Flash Translation Layer)&lt;/h5&gt;

  &lt;p&gt;FTL(Flash Translation Layer)은 호스트의 LBA(Logical Block Address)와 드라이브의 PBA(Physical Block Address)를 맵핑해주는 SSD 컨트롤러의 컴포넌트이다.
가장 최근의 드라이브는 Log Structure 파일 시스템과 같이 작동하는 “hybrid log-block mapping” 또는 그 파생 알고리즘을 구현하고 있다.
이 알고리즘은 랜덤 쓰기를 시퀀셜 쓰기처럼 핸들링할 수 있도록 해준다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;section-3&quot;&gt;4.3. 업계 상황&lt;/h3&gt;

&lt;p&gt;2014년 2월 2일 현재 위키피디아&lt;sup id=&quot;fnref:64&quot;&gt;&lt;a href=&quot;#fn:64&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;에는 70여개의 SSD 제조사가 나열되어 있는데, 재미있는 것은 단 11개의 제조사만 SSD 컨트롤러를 생산하고 있다.
제조사는 단 11개&lt;sup id=&quot;fnref:65&quot;&gt;&lt;a href=&quot;#fn:65&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;만 있다.
그리고 11개의 제조사중에서 삼성과 인텔을 포함한 단 4개의 제조사만 자사를 위한 SSD 컨트롤러를 생산하고 있으며,
나머지 7개사는 SSD 컨트롤러를 생산하여 다른 SSD 제조사로 판매하고 있는 것이다.
이는 단지 7개 회사가 SSD 마켓의 90% 정도의 SSD 컨트롤러를 제공하고 있다는 것을 의미한다.&lt;/p&gt;

&lt;p&gt;이 90%의 수치중에서 어떤 SSD 컨트롤러 회사가 어떤 SSD 드라이브 제조사로 공급하고 있는지는 모르겠지만,
파레토 법칙으로 고려해보면, 단지 2~3개의 컨트롤러 제조사가 대 부분의 시장을 점유하고 있을 것으로 보인다.
이런 이유로 삼성이나 인텔등 전용 SSD 컨틀롤러를 사용하는 4개 회사를 제외하면,
나머지 회사들의 SSD 드라이브는 거의 모두 동일한 SSD 컨트롤러를 사용중이며 매우 비슷하게 작동할 것이라는 것이다.&lt;/p&gt;

&lt;p&gt;SSD 컨트롤러의 일부인 맵핑 스키마는 SSD 드라이브의 전체적인 성능을 결정하기 때문에 SSD의 크리티컬한 컴포넌트라고 할 수 있다.
경쟁이 심한 SSD 시장에서 서로 자사의 FTL 구현 알고리즘을 상세한 내용을 전혀 공개하지 않는 이유가 이 때문인 것이다.
그래서 FTL 알고리즘에 대한 내용들이 많이 공유되어 있지만,
얼마나 많은 제조사들이 그 FTL 알고리즘을 구현하고 있는지는 어떤 모델이나 브랜드에 적용하고 있는지 알려지지 않는다&lt;/p&gt;

&lt;p&gt;“&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt; Essential roles of exploiting internal parallelism of flash memory based solid state drives in high-speed data processing, Chen et al, 2011”의 저자는
워크로드를 분석해서 SSD 드라이브가 사용중인 맵핑 알고리즘을 리버스 엔지니어링(Reverse Engineering)할 수 있다고 주장한다.
개인적으로 나는 칩으로부터 바이너리 코드 자체를 리버스 엔지니어링하지 않는 이상,
특정 드라이버에서 어떤 맵핑 정책이 사용되는지 정확히 판단하기는 어렵다고 생각한다.
또한 특정 워크로드에서는 어떤 맵핑 알고리즘이 사용되는지 예측하기는 더 어렵다.&lt;/p&gt;

&lt;p&gt;세상에는 수많은 맵핑 알고리즘이 있으며, 그 수많은 제품들의 펌웨어를 리버스 엔지니어링한다는 것은 엄청난 시간이 소모될 것이다.
그럼에도 불구하고 맵핑 방법의 모든 소스 코드를 구했다고 한들 무슨 이점이 있을까?
종종 새로운 프로젝트를 위한 시스템의 요건은 일반적으로 교체될 수 있는 하드웨어를 이용해서 조금 더 나은 결과를 내는 것이다.
그래서 하나의 맵핑 알고리즘을 위해서 서비스를 최적화하는 것이 다른 맵핑 알고리즘에서도 좋은 성능을 보장하는 것이 아니기 때문에,
그다지 가치 있는 최적화는 아닌 것으로 볼 수 있다.
하나의 맵핑 알고리즘에만 적합한 최적화가 필요한 경우는 그 하드웨어만 지속적으로 사용해야 하는 임베디드 시스템과 같은 경우이다.&lt;/p&gt;

&lt;p&gt;이러한 이유들로 인해서, SSD가 사용중인 맵핑 알고리즘을 알아내는 것은 큰 이점이 없다고 생각된다.
맵핑 스키마에서 한가지 알아야 할 중요한 것은 LBA와 PBA 사이의 주소 변환이며,
이를 위해서 Hybrid Log Block 맵핑 또는 그로부터 파생된 맵핑 알고리즘이다.
결과적으로 맵핑 테이블과 메타 데이터를 변경하는 오버헤드를 최소화해주기 때문에,
NAND 플래시 블록의 크기보다 큰 데이터 청크를 쓰기하는 것은 효율적이다.&lt;/p&gt;

&lt;h3 id=&quot;garbage-collection&quot;&gt;4.4. Garbage collection&lt;/h3&gt;

&lt;p&gt;섹션 4.1과 4.2에서 소개되었듯이, SSD 드라이브의 페이지는 덮어쓰기 될 수 없다.
만약 페이지의 데이터가 업데이트되어야 한다면,
새로운 버전의 데이터는 “free” 페이지에 기록되어야 하고 예전 버전의 데이터가 기록된 페이지는 “stale”로 삭제 마킹되어야 한다.
블록이 “stale” 상태의 페이지들을 가지고 있다면, 그들은 재사용되기 위해서는 먼저 삭제(Erase)되어야 한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;garbage-collection-1&quot;&gt;Garbage collection&lt;/h5&gt;

  &lt;p&gt;SSD 컨트롤러의 Garbage-collection 프로세스는 “stale” 상태의 페이지들이 삭제(Erase)되어 새로운 쓰기 데이터를 저장할 수 있도록 해주는 과정이다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;단순히 데이터 쓰기 오퍼레이션(250 ~ 1500 마이크로 초)에 비해서 블록을 삭제(Erase)하는 과정(1500 ~ 3500 마이크로 초)은 많은 시간이 소요되므로,
이런 부차적인 삭제(Erase) 작업은 쓰기 속도를 느리게 만든다. 그래서 일부 컨트롤러는 백그라운드 Garbage-collection을 도입하고 있다.
백그라운드 Garbage-collection은 Idle Collection이라고도 알려져 있는데,
이는 SSD 컨트롤러가 한가한 시간에 주기적으로 “stale” 페이지를 “free” 상태로 만들어 준다.
이렇게 충분한 “free” 상태의 페이지가 준비되어 있으면 유저 쓰기는 느려지지 않고 충분히 빠른 쓰기 속도를 보장할 수 있게 되는 것이다&lt;sup id=&quot;fnref:1:2&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;.
다른 SSD 컨트롤러는 Parallel Garbage-collection 방식을 구현하고 있는데,
이는 호스트의 쓰기 요청과 동시에 Garbage-collection을 실행하는 방식이다&lt;sup id=&quot;fnref:13:1&quot;&gt;&lt;a href=&quot;#fn:13&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;호스트로부터 쓰기 요청이 올 때 동시에 Garbage-collection이 필요할 정도의 과도한 쓰기 부하를 필요로 하는 워크로드는 흔하지 않다.
이런 때에는 Garbage-collection이 백그라운드로 실행되는 것은 호스트로부터 전달되는 포그라운드(Foreground) 명령 실행을 방해할 수 있다 &lt;sup id=&quot;fnref:1:3&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;.
TRIM 명령과 Over-provisioning은 이런 현상을 줄여줄 수 있는 좋은 솔루션인데, 이는 섹션 6.1과 6.2에서 살펴보도록 하겠다.&lt;/p&gt;

&lt;p&gt;백그라운드 오퍼레이션은 포그라운드 오퍼레이션에 악영향을 미칠 수 있다.
Garbage-collection과 같은 백그라운드 오퍼레이션은 호스트로부터 오는 포그라운드 명령에 나쁜 영향을 미칠 수 있다.
특히 이런 현상은 작은 데이터의 랜덤 쓰기가 지속적으로 발생하는 시스템에서는 더욱 더 나쁜 영향을 미치게 될 것이다.&lt;/p&gt;

&lt;p&gt;블록이 이동되어야 하는 조금 덜 중요한 이유중 하나는 읽기 방해(Read disturb)이다.
읽기는 주의 셀들의 상태를 변경할 수도 있다.
그래서 블록은 일정 회수 이상의 읽기가 수행된 이후 다른 위치로 옮겨져야 한다&lt;sup id=&quot;fnref:14:1&quot;&gt;&lt;a href=&quot;#fn:14&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.
데이터가 변경되는 비율은 중요한 요소이다.
어떤 데이터는 아주 뜸하게 변경되는데, 이런 데이터를 콜드(cold) 또는 정적(static) 데이터라고 한다.
반면 아주 빈번하게 변경되는 데이터들도 있는데, 이를 핫(hot) 또는 동적(dynamic) 데이터라고 한다.
만약 페이지가 일부는 콜드 그리고 나머지 일부는 핫 데이터를 가진다면,
Wear Leveling을 위해서 핫 데이터가 Garbage-collection될 때마다 콜드 데이터도 같이 옮겨 다녀야 할 것이다.
하지만 이렇게 핫 데이터와 함께 콜드 데이터들이 따라 다녀야 한다면, “Write Amplication”은 더 심해질 것이다.
이런 현상은 콜드 데이터와 핫 데이터를 서로 다른 페이지로 분리함으로써 피할 수 있다.
이 방법의 단점은 콜드 데이터를 가진 페이지들은 덜 자주 삭제(Erase)될 것이고,
이로 인해서 SSD 컨트롤러는 콜드 데이터와 핫 데이터를 가진 페이지들을 Wear Leveling을 위해서 주기적으로 스왑(swap) 해줘야 한다.
데이터의 변경 빈도는 응용 프로그램 레벨에서 결정되기 때문에,
FTL은 하나의 페이지에 얼마나 핫 데이터와 콜드 데이터가 저장될지 예측할 수 있는 방법이 없다.
SSD에서 성능을 향상시키기 위한 방법으로는 핫 데이터와 콜드 데이터를 최대한 다른 페이지로 분리하는 것이며,
이는 Garbage-collection이 좀 더 효율적으로 작동하도록 해준다&lt;sup id=&quot;fnref:8&quot;&gt;&lt;a href=&quot;#fn:8&quot; class=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-4&quot;&gt;콜드 데이터와 핫 데이터 분리&lt;/h5&gt;

  &lt;p&gt;빈번하게 변경되는 데이터를 핫 데이터라고 하며, 그렇지 않은 데이터를 콜드 데이터라고 한다.
만약 핫 데이터와 콜드 데이터가 동일 페이지에 저장된다면,
핫 데이터가 변경될 때마다 콜드 데이터는 핫 데이터와 함께 Read-Modify-Write 오퍼레이션에 같이 포함되어 복사되어야 한다.
또한 Wear-leveling을 위해서 콜드 데이터도 같이 계속 다른 페이지로 이동되어야 한다.
콜드 데이터와 핫 데이터는 최대한 분리해야 Garbage-collection을 효율적으로 처리될 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-5&quot;&gt;핫 데이터 버퍼링&lt;/h5&gt;

  &lt;p&gt;매우 빈번하게 변경되는 핫 데이터는 최대한 버퍼링되었다가 SSD에 덜 자주 업데이트되도록 하는 것이 좋다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-6&quot;&gt;불필요한 데이터는 한번에 많이 삭제&lt;/h5&gt;

  &lt;p&gt;데이터가 더 이상 필요치 않거나 삭제해야 할 때에는 최대한 모아서 한번(단일 오퍼레이션)에 삭제하는 것이 좋다.
이렇게 단일 오퍼레이션으로 삭제하는 것이 Garbage-collection 처리가 한번에 큰 영역을 처리하도록 해주며 그와 동시에 내부 프레그멘테이션을 최소화시켜 줄 것이다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;This articles are translated to Korean with original author’s(&lt;a href=&quot;http://www.goossaert.com/&quot;&gt;Emmanuel Goossaert&lt;/a&gt;) permission. Really appreciate his effort and sharing.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Original articles :&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/&quot;&gt;Part 1: Introduction and Table of Contents&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-2-architecture-of-an-ssd-and-benchmarking/&quot;&gt;Part 2: Architecture of an SSD and Benchmarking&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-3-pages-blocks-and-the-flash-translation-layer/&quot;&gt;Part 3: Pages, Blocks, and the Flash Translation Layer&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-parallelism/&quot;&gt;Part 4: Advanced Functionalities and Internal Parallelism&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-5-access-patterns-and-system-optimizations/&quot;&gt;Part 5: Access Patterns and System Optimizations&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-6-a-summary-what-every-programmer-should-know-about-solid-state-drives/&quot;&gt;Part 6: A Summary – What every programmer should know about solid-state drives&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:13&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Write_amplification&quot;&gt;http://en.wikipedia.org/wiki/Write_amplification&lt;/a&gt; &lt;a href=&quot;#fnref:13&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:13:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://csl.skku.edu/papers/CS-TR-2010-329.pdf&quot;&gt;Parameter-Aware I/O Management for Solid State Disks (SSDs), Kim et al., 2012&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:2:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://research.microsoft.com/pubs/63596/usenix-08-ssd.pdf&quot;&gt;Design Tradeoffs for SSD Performance, Agrawal et al., 2008&lt;/a&gt; &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:5:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:12&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Solid-state_drive&quot;&gt;http://en.wikipedia.org/wiki/Solid-state_drive&lt;/a&gt; &lt;a href=&quot;#fnref:12&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:14&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Flash_memory&quot;&gt;http://en.wikipedia.org/wiki/Flash_memory&lt;/a&gt; &lt;a href=&quot;#fnref:14&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:14:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.cse.ohio-state.edu/hpcs/WWW/HTML/publications/papers/TR-09-2.pdf&quot;&gt;Understanding Intrinsic Characteristics and System Implications of Flash Memory based Solid State Drives, Chen et al., 2009&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:1:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:1:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:1:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:10&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://idke.ruc.edu.cn/people/dazhou/Papers/a38-park.pdf&quot;&gt;A Reconfigurable FTL (Flash Translation Layer) Architecture for NAND Flash-Based Applications, Park et al., 2008&lt;/a&gt; &lt;a href=&quot;#fnref:10&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:10:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:10:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:10:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:9&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://idke.ruc.edu.cn/people/dazhou/Papers/AsurveyFlash-JSA.pdf&quot;&gt;A Survey of Flash Translation Layer, Chung et al., 2009&lt;/a&gt; &lt;a href=&quot;#fnref:9&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:9:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:9:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:64&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/List_of_solid-state_drive_manufacturers&quot;&gt;http://en.wikipedia.org/wiki/List_of_solid-state_drive_manufacturers&lt;/a&gt; &lt;a href=&quot;#fnref:64&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:65&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/List_of_flash_memory_controller_manufacturers&quot;&gt;http://en.wikipedia.org/wiki/List_of_flash_memory_controller_manufacturers&lt;/a&gt; &lt;a href=&quot;#fnref:65&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://bit.csc.lsu.edu/~fchen/paper/papers/hpca11.pdf&quot;&gt;Essential roles of exploiting internal parallelism of flash memory based solid state drives in high-speed data processing, Chen et al, 2011&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:8&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;https://www.usenix.org/legacy/event/fast12/tech/full_papers/Min.pdf&quot;&gt;SFS: Random Write Considered Harmful in Solid State Drives, Min et al., 2012&lt;/a&gt; &lt;a href=&quot;#fnref:8&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 15 Jul 2016 20:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/15/coding-for-ssd-part-3/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/15/coding-for-ssd-part-3/</guid>
        
        <category>ssd</category>
        
        <category>nand-flash</category>
        
        <category>garbage-collection</category>
        
        <category>LBA</category>
        
        <category>PBA</category>
        
        <category>block</category>
        
        <category>page</category>
        
        <category>clustered-block</category>
        
        
      </item>
    
      <item>
        <title>개발자를 위한 SSD (Coding for SSD) - Part 2 : SSD의 아키텍처와 벤치마킹</title>
        <description>&lt;p&gt;이 챕터에서는 NAND 플래시 메모리의 기본적인 내용과 셀 타입 그리고 SSD의 기본적인 내부 아키텍처에 대해서 살펴보고,
추가로 SSD의 벤치마킹 방법과 벤치마킹 결과를 해석하는 부분도 살펴보도록 하겠다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part2_1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;ssd-&quot;&gt;1. SSD의 구조&lt;/h2&gt;

&lt;h3 id=&quot;nand---&quot;&gt;1.1. NAND 플래시 메모리 셀&lt;/h3&gt;

&lt;p&gt;SSD(Solid-State Drive)는 플래시 메모리를 기반으로한 저장 매체이다. 비트들은 Floating-Gate 트랜지스터로 구성된 셀에 저장된다.
SSD는 모든 컴포넌트가 전기 장치이며, HDD와 같은 기계 장치를 가지고 있지 않다.&lt;/p&gt;

&lt;p&gt;Floating-Gate 트랜지스터에 전압이 가해지면서 셀의 비트가 쓰여지거나 읽혀지게 된다.
트랜지스터는 NOR 플래시 메모리와 NAND 플래시 메모리 두 종류가 있는데(여기에서는 NAND의 NOR 플래시 메모리의 차이에 대해서 깊이 있게 살펴보지는 않을 것이다),
이 게시물에서는 많은 제조사들이 채택하고 있는NAND 플래시 메모리에 대해서만 살펴보도록 하겠다. NAND와 NOR 플래시 메모리의 상세한 차이에 대해서 궁금하다면,
Lee Hutchinson&lt;sup id=&quot;fnref:31&quot;&gt;&lt;a href=&quot;#fn:31&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;의 문서를 살펴보도록 하자.&lt;/p&gt;

&lt;p&gt;NAND 플래시 메모리 모듈의 중요한 속성은 수명이 제한적(Wearing-off)이라는 것이다.
트랜지스터는 셀에 전자를 저장하면서 쓰기를 하게 되는데,
매번 P/E(Program &amp;amp; Erase, Program은 쓰기를 의미) 사이클마다 일부 전자가 오류로 인해서 트랜지스터에 갇히게 된다.
그리고 이렇게 갇힌 전자들이 쌓여서 일정 수준을 넘어서게 되면, 그 셀은 사용 불가능한 상태가 되는 것이다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section&quot;&gt;수명 제한&lt;/h5&gt;

  &lt;p&gt;각 셀은 최대 P/E 사이클을 가지는데, 이 사이클을 넘어서면 결함 셀로 간주된다.
NAND 플래시 메모리는 수명 제한을  가지는데, 이 수명 제한은 NAND 플래시 메모리의 타입(SLC, MLC, TLC)에 따라서 조금씩 차이가 있다&lt;sup id=&quot;fnref:31:1&quot;&gt;&lt;a href=&quot;#fn:31&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;최근 연구에 따르면, NAND 플래시 메모리 칩에 높은 열이 가해지면 각 셀에 갇혀있던(프로그램되어 있던) 전자들이 사라진다는 것이 확인되었다&lt;sup id=&quot;fnref:14&quot;&gt;&lt;a href=&quot;#fn:14&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:51&quot;&gt;&lt;a href=&quot;#fn:51&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.
또한 아직 연구중이며 언제 시중에 이런 제품이 출시될지 모르지만, SSD의 수명을 상당히 높일 수 있는 방법도 있다.
현재 시중에 출시되는 SSD의 메모리 셀 타입은:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;SLC (Single level cell), SLC 트랜지스터는 단 하나의 비트만 저장할 수 있지만 상대적으로 긴 수명을 가짐&lt;/li&gt;
  &lt;li&gt;MLC (Multiple level cell), MLC 트랜지스터는 2 비트를 저장할 수 있으며, SLC에 비해서 상대적으로 레이턴시(응답 속도)가 높고 짧은 수명을 가짐&lt;/li&gt;
  &lt;li&gt;TLC (Triple-level cell), TLC 트랜지스터는 3 비트를 저장할 수 있지만, MLC보다 레이턴시가 높고 수명이 더 짧음&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;section-1&quot;&gt;메모리 셀 타입&lt;/h5&gt;

  &lt;p&gt;SSD는 플래시 메모리 기반의 저장 매체이다. 비트는 셀에 저장되며, 메모리 셀은 3가지 타입이 있다. SLC는 1비트, MLC는 2비트 그리고 TLC는 3비트를 저장할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;표1은 각 NAND 플래시 셀 타입의 상세한 정보를 보여주고 있다. 비교를 위해서 HDD와 메인 메모리(RAM) 그리고 L1/L2 캐시도 같이 포함하였다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;SLC&lt;/th&gt;
      &lt;th&gt;MLC&lt;/th&gt;
      &lt;th&gt;TLC&lt;/th&gt;
      &lt;th&gt;HDD&lt;/th&gt;
      &lt;th&gt;RAM&lt;/th&gt;
      &lt;th&gt;L1 cache&lt;/th&gt;
      &lt;th&gt;L2 cache&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;P/E cycles&lt;/td&gt;
      &lt;td&gt;100k&lt;/td&gt;
      &lt;td&gt;10k&lt;/td&gt;
      &lt;td&gt;5k&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bits per cell&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Seek latency (μs)&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;9000&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Read latency (μs)&lt;/td&gt;
      &lt;td&gt;25&lt;/td&gt;
      &lt;td&gt;50&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;2000-7000&lt;/td&gt;
      &lt;td&gt;0.04-0.1&lt;/td&gt;
      &lt;td&gt;0.001&lt;/td&gt;
      &lt;td&gt;0.004&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Write latency (μs)&lt;/td&gt;
      &lt;td&gt;250&lt;/td&gt;
      &lt;td&gt;900&lt;/td&gt;
      &lt;td&gt;1500&lt;/td&gt;
      &lt;td&gt;2000-7000&lt;/td&gt;
      &lt;td&gt;0.04-0.1&lt;/td&gt;
      &lt;td&gt;0.001&lt;/td&gt;
      &lt;td&gt;0.004&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Erase latency (μs)&lt;/td&gt;
      &lt;td&gt;1500&lt;/td&gt;
      &lt;td&gt;3000&lt;/td&gt;
      &lt;td&gt;5000&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;표1: 다른 메모리 컴포넌트와 NAND 플래시 메모리의 특성 및 레이턴시 비교&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Notes
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;*&lt;/code&gt; metric is not applicable for that type of memory&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Sources
    &lt;ul&gt;
      &lt;li&gt;P/E cycles &lt;sup id=&quot;fnref:20&quot;&gt;&lt;a href=&quot;#fn:20&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
      &lt;li&gt;SLC/MLC latencies &lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
      &lt;li&gt;TLC latencies &lt;sup id=&quot;fnref:23&quot;&gt;&lt;a href=&quot;#fn:23&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
      &lt;li&gt;Hard disk drive latencies &lt;sup id=&quot;fnref:18&quot;&gt;&lt;a href=&quot;#fn:18&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:19&quot;&gt;&lt;a href=&quot;#fn:19&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:25&quot;&gt;&lt;a href=&quot;#fn:25&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
      &lt;li&gt;RAM latencies &lt;sup id=&quot;fnref:30&quot;&gt;&lt;a href=&quot;#fn:30&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:52&quot;&gt;&lt;a href=&quot;#fn:52&quot; class=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
      &lt;li&gt;L1 and L2 cache latencies &lt;sup id=&quot;fnref:52:1&quot;&gt;&lt;a href=&quot;#fn:52&quot; class=&quot;footnote&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;동일 량의 트랜지스터로 더 많은 비트들을 저장할 수 있다면, 당연히 제조 단가는 낮아지게 된다.
SLC 타입의 SSD는 MLC 타입보다 신뢰성이 높고 더 오랜 시간 사용할 수 있지만, 제조 비용이 크다.
그래서 대 부분의 SSD는 MLC 또는 TLC 타입이며, 엔터프라이즈급의 SSD만 SLC를 사용한다.
워크로드에 맞게 적절한 메모리 타입을 선정하는 것이 중요한데, 여기서 워크로드라 함은 얼마나 SSD에 데이터가 자주 기록되는지를 의미한다.
예를 들어서 쓰기 빈도가 아주 높다면 SLC 타입의 SSD가 최선이며,
(동영상 스트리밍을 위한 저장소와 같이) 쓰기는 많지 않지만 읽기가 매우 많다면 TLC가 가장 적절한 선택이 될 것이다.
게다가 실제 서비스 수준의 워크로드로 수행된 벤치마크의 결과에 의하면, 실제 TLC 타입의 메모리 수명은 크게 문제되지 않는 것으로 보인다 &lt;sup id=&quot;fnref:36&quot;&gt;&lt;a href=&quot;#fn:36&quot; class=&quot;footnote&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;nand----1&quot;&gt;NAND 플래시 페이지와 블록&lt;/h5&gt;

  &lt;p&gt;셀들은 Block으로 그룹핑 되어 있으며, Block들은 다시 Plane으로 그룹핑되어 있다.
SSD를 읽고 쓰기시에 최소 접근 단위는 Page라고 하며, Page는 개별로 삭제(Erase)될 수 없고 반드시 삭제는 Block 단위로만 수행될 수 있다.
NAND 플래시 메모리의 Page 크기는 2KB, 4KB, 8KB, 16KB로 다양한데, 대 부분 SSD의 Block은 128개 또는 256개의 Page를 가진다.
이는 SSD 제조사별로 Block의 크기가 256KB에서 4MB까지 다양하다는 것을 의미한다.
예를 들어서 삼성 SSD 840 EVO는 2048KB의 Block 크기를 가지며 각 Block은 256개의 8KB 페이지를 가진다.
Page와 Block의 접근 방법에 대해서는 섹션 3.1에서 자세히 살펴보도록 하겠다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;ssd--1&quot;&gt;1.2. SSD의 구성&lt;/h3&gt;

&lt;p&gt;아래의 그림 1은 SSD의 주요 컴포넌트들을 도식화한 것이다.
이는 이미 다양한 문서(&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;13&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;14&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:6&quot;&gt;&lt;a href=&quot;#fn:6&quot; class=&quot;footnote&quot;&gt;15&lt;/a&gt;&lt;/sup&gt;)들에서 공개된 내용들이며, 간단히 개요만 표시한 것이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part2_2.jpg&quot; alt=&quot;그림 1: Solid-State Drive의 아키텍처&quot; /&gt;&lt;/p&gt;

&lt;p&gt;사용자의 요청은 호스트 인터페이스를 통해서 유입되는데,
이 글을 작성하는 시점에는 가장 일반적인 인터페이스 방식은 ATA(SATA)와 PCI Express(PCIe) 타입이다.
SSD 컨트롤러에 장착된 프로세서가 명령을 받아서 플래시 컨트롤러로 전달하게 된다.
SSD는 자체적으로 보드에 내장된 메모리(RAM)을 가지는데, 일반적으로 이 메모리는 맵핑 정보를 저장하거나 캐시 용도로 사용된다.
상세한 맵핑 정책에 대해서는 섹션 4에서는 살펴보도록 하겠다.
SSD는 여러 개의 Channel을 통해서 서로 NAND 플래시 메모리 패키지로 구성된다.
Channel에 대한 상세한 내용은 섹션 6에서 살펴보도록 하겠다.&lt;/p&gt;

&lt;p&gt;아래의 그림 2와 3(StorageReview.com의 게시물&lt;sup id=&quot;fnref:26&quot;&gt;&lt;a href=&quot;#fn:26&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:27&quot;&gt;&lt;a href=&quot;#fn:27&quot; class=&quot;footnote&quot;&gt;17&lt;/a&gt;&lt;/sup&gt;을 참조)은 실생활에서 사용되는 SSD가 어떻게 생겼는지를 보여주고 있다.
그림 2는 2013년 8월에 출시된 512GB 삼성 840 Pro SSD이다. 기판에서 보이듯이, 주요 컴포넌트는:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1 SATA 3.0 인터페이스&lt;/li&gt;
  &lt;li&gt;1 SSD 컨트롤러 (삼성 MDX S4LN021X01-8030)&lt;/li&gt;
  &lt;li&gt;1 RAM 모듈 (256 MB DDR2 삼성 K4P4G324EB-FGC2)&lt;/li&gt;
  &lt;li&gt;8 MLC NAND-플래시 모듈, 각 모듈은 64 GB (삼성 K9PHGY8U7A-CCK0)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part2_3.jpg&quot; alt=&quot;그림 2: 삼성 SSD 840 Pro (512 GB)&quot; /&gt;
&lt;img src=&quot;/files/coding_for_ssd_part2_4.jpg&quot; alt=&quot;그림 2: 삼성 SSD 840 Pro (512 GB)&quot; /&gt;
그림 제공: StorageReview.com &lt;sup id=&quot;fnref:26:1&quot;&gt;&lt;a href=&quot;#fn:26&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;그림 3은 2013년 후반기에 출시된 마이크론(Micron) P420m Enterprise PCIe 이며, 주요 컴포넌트는:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;8 레인(lane) PCI Express 2.0 인터페이스&lt;/li&gt;
  &lt;li&gt;1 SSD 컨트롤러&lt;/li&gt;
  &lt;li&gt;1 RAM 모듈 (DRAM DDR3)&lt;/li&gt;
  &lt;li&gt;64 MLC NAND-플래시 모듈(32 채널), 각 모듈은 32GB (마이크론 31C12NQ314 25nm)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;전체 메모리 공간은 2048GB이지만, 프로비저닝(over-provisioning) 영역을 제외하고 1.4TB 사용 가능.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part2_5.jpg&quot; alt=&quot;그림 3: Micron P420m Enterprise PCIe (1.4 TB)&quot; /&gt;
&lt;img src=&quot;/files/coding_for_ssd_part2_6.jpg&quot; alt=&quot;그림 3: Micron P420m Enterprise PCIe (1.4 TB)&quot; /&gt;
그림 제공: StorageReview.com &lt;sup id=&quot;fnref:27:1&quot;&gt;&lt;a href=&quot;#fn:27&quot; class=&quot;footnote&quot;&gt;17&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h3 id=&quot;ssd--&quot;&gt;1.3. SSD의 생산 공정&lt;/h3&gt;

&lt;p&gt;많은 SSD 제조사는 SSD 생산을 위해서 SMT(Surface-Mount Technology)를 사용하는데,
SMT 생산 과정에서는 전자 부품들이 회로 기판(PCBs)위에 직접 장착된다.
SMT 생산 라인은 기계들이 줄지어 있고, 각 기계들이 부품을 기판위에 놓거나 놓여진 부품들을 납땜하는 등의 각 작업들을 담당하게 된다.
전체 생산 공정에서 여러번의 품질 체크 과정도 수행된다.
SMT 라인의 사진이나 동영상은 Steve Burke&lt;sup id=&quot;fnref:67&quot;&gt;&lt;a href=&quot;#fn:67&quot; class=&quot;footnote&quot;&gt;18&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:68&quot;&gt;&lt;a href=&quot;#fn:68&quot; class=&quot;footnote&quot;&gt;19&lt;/a&gt;&lt;/sup&gt;의 글(캘리포니아 파운틴 밸리의 Kingston Technologies사를 방문했을 때 사진과
Cameron Wilmot이 작성한 타이완&lt;sup id=&quot;fnref:69&quot;&gt;&lt;a href=&quot;#fn:69&quot; class=&quot;footnote&quot;&gt;20&lt;/a&gt;&lt;/sup&gt;의 Kingston 공장 사진)에서 참조할 수 있다.&lt;/p&gt;

&lt;p&gt;그 이외에도 2개의 참조할 만한 영상이 있는데, 첫번째 동영상은 Micron사의 “Crucial SSDs”&lt;sup id=&quot;fnref:70&quot;&gt;&lt;a href=&quot;#fn:70&quot; class=&quot;footnote&quot;&gt;21&lt;/a&gt;&lt;/sup&gt;이며 두번째는 Kingston&lt;sup id=&quot;fnref:71&quot;&gt;&lt;a href=&quot;#fn:71&quot; class=&quot;footnote&quot;&gt;22&lt;/a&gt;&lt;/sup&gt;에 관련된 것이다.
두번째 동영상은 “Steve Burke”가 작성한 글의 일부인데, 이 글의 하단에 포함시켜 두었다.
Kingston사의 Mark Tekunoff는 SMT 라인 투어를 시켜 주었으며,
동영상의 모든 사람들은 즐겁게 정전기 방지용 파자마를 입고 있는 모습을 볼 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.youtube.com/watch?v=3s7KG6QwUeQ&quot;&gt;&lt;img src=&quot;http://img.youtube.com/vi/3s7KG6QwUeQ/0.jpg&quot; alt=&quot;RAM &amp;amp; SSD &amp;amp; 메인보드는 어떻게 만들어지는가?&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;section-2&quot;&gt;2. 벤치마킹과 성능 메트릭&lt;/h2&gt;

&lt;h3 id=&quot;section-3&quot;&gt;2.1. 기본 벤치마킹&lt;/h3&gt;

&lt;p&gt;아래의 표2는 여러 SSD 드라이브들의 랜덤과 시퀀셜 워크로드에서 스루풋을 보여주고 있다.
비교를 위해서 2008년과 2013년에 출시된 SSD를 HDD와 메모리(RAM)과 함께 나열해 보았다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Samsung 64 GB&lt;/th&gt;
      &lt;th&gt;Intel X25-M&lt;/th&gt;
      &lt;th&gt;Samsung 840 EVO&lt;/th&gt;
      &lt;th&gt;Micron P420m&lt;/th&gt;
      &lt;th&gt;HDD&lt;/th&gt;
      &lt;th&gt;RAM&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Brand/Model&lt;/td&gt;
      &lt;td&gt;Samsung (MCCDE64G5MPP-OVA)&lt;/td&gt;
      &lt;td&gt;Intel X25-M (SSDSA2MH080G1GC)&lt;/td&gt;
      &lt;td&gt;Samsung (SSD 840 EVO mSATA)&lt;/td&gt;
      &lt;td&gt;Micron P420m&lt;/td&gt;
      &lt;td&gt;Western Digital Black 7200 rpm&lt;/td&gt;
      &lt;td&gt;Corsair Vengeance DDR3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Memory cell type&lt;/td&gt;
      &lt;td&gt;MLC&lt;/td&gt;
      &lt;td&gt;MLC&lt;/td&gt;
      &lt;td&gt;TLC&lt;/td&gt;
      &lt;td&gt;MLC&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release year&lt;/td&gt;
      &lt;td&gt;2008&lt;/td&gt;
      &lt;td&gt;2008&lt;/td&gt;
      &lt;td&gt;2013&lt;/td&gt;
      &lt;td&gt;2013&lt;/td&gt;
      &lt;td&gt;2013&lt;/td&gt;
      &lt;td&gt;2012&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Interface&lt;/td&gt;
      &lt;td&gt;SATA 2.0&lt;/td&gt;
      &lt;td&gt;SATA 2.0&lt;/td&gt;
      &lt;td&gt;SATA 3.0&lt;/td&gt;
      &lt;td&gt;PCIe 2.0&lt;/td&gt;
      &lt;td&gt;SATA 3.0&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Total capacity&lt;/td&gt;
      &lt;td&gt;64 GB&lt;/td&gt;
      &lt;td&gt;80 GB&lt;/td&gt;
      &lt;td&gt;1 TB&lt;/td&gt;
      &lt;td&gt;1.4 TB&lt;/td&gt;
      &lt;td&gt;4 TB&lt;/td&gt;
      &lt;td&gt;4 x 4 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pages per block&lt;/td&gt;
      &lt;td&gt;128&lt;/td&gt;
      &lt;td&gt;128&lt;/td&gt;
      &lt;td&gt;256&lt;/td&gt;
      &lt;td&gt;512&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Page size&lt;/td&gt;
      &lt;td&gt;4 KB&lt;/td&gt;
      &lt;td&gt;4 KB&lt;/td&gt;
      &lt;td&gt;8 KB&lt;/td&gt;
      &lt;td&gt;16 KB&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Block size&lt;/td&gt;
      &lt;td&gt;512 KB&lt;/td&gt;
      &lt;td&gt;512 KB&lt;/td&gt;
      &lt;td&gt;2048 KB&lt;/td&gt;
      &lt;td&gt;8196 KB&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sequential reads (MB/s)&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;254&lt;/td&gt;
      &lt;td&gt;540&lt;/td&gt;
      &lt;td&gt;3300&lt;/td&gt;
      &lt;td&gt;185&lt;/td&gt;
      &lt;td&gt;7233&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sequential writes (MB/s)&lt;/td&gt;
      &lt;td&gt;92&lt;/td&gt;
      &lt;td&gt;78&lt;/td&gt;
      &lt;td&gt;520&lt;/td&gt;
      &lt;td&gt;630&lt;/td&gt;
      &lt;td&gt;185&lt;/td&gt;
      &lt;td&gt;5872&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4KB random reads (MB/s)&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
      &lt;td&gt;23.6&lt;/td&gt;
      &lt;td&gt;383&lt;/td&gt;
      &lt;td&gt;2292&lt;/td&gt;
      &lt;td&gt;0.54&lt;/td&gt;
      &lt;td&gt;5319 **&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4KB random writes (MB/s)&lt;/td&gt;
      &lt;td&gt;5.5&lt;/td&gt;
      &lt;td&gt;11.2&lt;/td&gt;
      &lt;td&gt;352&lt;/td&gt;
      &lt;td&gt;390&lt;/td&gt;
      &lt;td&gt;0.85&lt;/td&gt;
      &lt;td&gt;5729 **&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4KB Random reads (KIOPS)&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;98&lt;/td&gt;
      &lt;td&gt;587&lt;/td&gt;
      &lt;td&gt;0.14&lt;/td&gt;
      &lt;td&gt;105&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4KB Random writes (KIOPS)&lt;/td&gt;
      &lt;td&gt;1.5&lt;/td&gt;
      &lt;td&gt;2.8&lt;/td&gt;
      &lt;td&gt;90&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;0.22&lt;/td&gt;
      &lt;td&gt;102&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;표2: 다른 저장 매체와 SSD 드라이브의 스루풋 및 특성 비교&lt;/p&gt;

&lt;p&gt;Notes&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;*&lt;/code&gt; metric is not applicable for that storage solution&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;**&lt;/code&gt; measured with 2 MB chunks, not 4 KB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Metrics&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MB/s: Megabytes per Second&lt;/li&gt;
  &lt;li&gt;KIOPS: Kilo IOPS, i.e 1000 Input/Output Operations Per Second&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sources&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Samsung 64 GB &lt;sup id=&quot;fnref:21&quot;&gt;&lt;a href=&quot;#fn:21&quot; class=&quot;footnote&quot;&gt;23&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Intel X25-M &lt;sup id=&quot;fnref:2:1&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;13&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:28&quot;&gt;&lt;a href=&quot;#fn:28&quot; class=&quot;footnote&quot;&gt;24&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Samsung SSD 840 EVO &lt;sup id=&quot;fnref:22&quot;&gt;&lt;a href=&quot;#fn:22&quot; class=&quot;footnote&quot;&gt;25&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Micron P420M &lt;sup id=&quot;fnref:27:2&quot;&gt;&lt;a href=&quot;#fn:27&quot; class=&quot;footnote&quot;&gt;17&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Western Digital Black 4 TB &lt;sup id=&quot;fnref:25:1&quot;&gt;&lt;a href=&quot;#fn:25&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Corsair Vengeance DDR3 RAM &lt;sup id=&quot;fnref:30:1&quot;&gt;&lt;a href=&quot;#fn:30&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;호스트 인터페이스는 성능을 결정하는 중요한 요소중 하나이다.
최근에 출시되는 SSD의 일반적인 인터페이스는 SATA 3.0 또는 PCI Express 3.0이다.
SATA 3.0 인터페이스는 6 Gbit/s 정도의 데이터 전송량을 낼 수 있는데, 이는 초당 550MB 정도의 성능이다.
그리고 PCIe 3.0 인터페이스에서는 레인(lane)당 8 GT/s(GT/s 는 초당 전송가능한 Giga를 의미, Gigatransfers/second) 데이터 전송이 가능한데,
이는 대략 1 GB/s 정도이다.
PCIe 3.0 인터페이스는 최소 1개 이상의 레인(lane)을 가지고 있는데,
4개 레인을 가진 SSD는 SATA 3.0 인터페이스보다 8배나 빠른 4 GB/s 전송 속도를 낼 수 있다.
일부 엔터프라이즈 SSD중에는 SAS (Serial Attached SCSI) 인터페이스를 장착한 제품들도 있는데,
이들은 12 GBit/s정도의 전송 속도를 제공하지만 실제 SAS 인터페이스를 장착한 제품은 많지 않다.&lt;/p&gt;

&lt;p&gt;최근 출시되는 대 부분의 SSD들은 SATA 3.0의 최대 전송 속도인 550MB/s를 초과할 정도의 성능을 가지고 있으므로,
현재는 대부분 인터페이스가 병목 지점인 경우가 많다.
PCI Express 3.0이나 SAS 인터페이스를 장착한 SSD는 엄청난 성능 향상을 제공할 것이다.&lt;sup id=&quot;fnref:15&quot;&gt;&lt;a href=&quot;#fn:15&quot; class=&quot;footnote&quot;&gt;26&lt;/a&gt;&lt;/sup&gt;
SATA보다 빠른 PCI Express 와 SAS 인터페이스 제조사에서 주로 사용되는 호스트 인터페이스는
SATA 3.0 (550 MB/s)과 PCI Express 3.0 (레인당 1 GB/s, 다중 채널 사용)이다.
SAS(Serial Attached SCSI) 또한 엔터프라이즈 SSD에 사용되는데,
PCIe와 SAS 인터페이스는 SATA보다 훨씬 빠른 성능을 제공하지만, 그만큼 가격도 비싼 편이다.&lt;/p&gt;

&lt;h3 id=&quot;pre-conditioning&quot;&gt;2.2. 프리 컨디셔닝 (Pre-conditioning)&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you torture the data long enough, it will confess.&lt;/p&gt;

  &lt;p&gt;— Ronald Coase&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SSD 제조사가 제공하는 제품의 데이터 시트에는 놀라울 정도의 성능 메트릭들로 채워져 있다.
마케팅 효과를 위해서 제조사들은 항상 반짝이는 수치들을 보여주기 위해서 방법을 찾아내는 것처럼 보인다.
그 수치들이 진정으로 뭔가를 의미하는지 모르겠지만, 실제 데이터 시트의 수치와 운영 시스템에서 사용되는 제품의 성능 예측은 다른 문제이다.&lt;/p&gt;

&lt;p&gt;Marc Bevand가 작성한 문서&lt;sup id=&quot;fnref:66&quot;&gt;&lt;a href=&quot;#fn:66&quot; class=&quot;footnote&quot;&gt;27&lt;/a&gt;&lt;/sup&gt;는 일반적인 SSD 벤치마크의 결함에 대해서 언급하고 있는데,
이 문서에서 Marc Bevand는 SSD의 LBA(Logical Block Addressing) 크기 설정에 대한 언급없이 랜덤 쓰기 성능을 명시하거나
Queue depth를 1로 설정하고 테스트한 결과를 레포팅하는 것은 적절하지 못하다고 이야기하고 있다.
또한 이 이외에도 벤치마크 도구의 버그나 잘못된 사용으로 인한 케이스들도 다양하다.&lt;/p&gt;

&lt;p&gt;SSD의 성능을 정확하게 예측하는 것은 어려운 부분이다.
많은 하드웨어 리뷰 블로그들이 10여분 정도의 테스트 실행 후, 그 결과를 두고 충분히 신뢰성 있다라고 이야기하고 있다.
그러나 SSD는 지속적인 랜덤 쓰기(SSD의 전체 공간이 30분에서 3시간 정도 저장할 수 있는 수준의 워크로드)가 발생하는 테스트 환경에서만 성능 저하를 보여준다.
그래서 어느정도의 쓰기 부하를 발생(이를 “pre-conditioning” &lt;sup id=&quot;fnref:50&quot;&gt;&lt;a href=&quot;#fn:50&quot; class=&quot;footnote&quot;&gt;28&lt;/a&gt;&lt;/sup&gt;라고 함)시킨 후, 벤치마크가 좀 더 신뢰성 있는 테스트라고 볼 수 있는 것이다.
아래의 그림 7은 StorageReview.com &lt;sup id=&quot;fnref:26:2&quot;&gt;&lt;a href=&quot;#fn:26&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt;으로부터 가져온 것인데, 다양한 SSD에서 “pre-conditioning”의 효과를 잘 보여주고 있다.
좀더 명확한 성능 저하는 30분 정도 지난 이후 시점부터 나타나는데,
이때부터 모든 SSD 드라이브에 대해서 스루풋은 떨어지고 레이턴시(응답 속도)는 커지는 것을 확인할 수 있다.
그리고 4시간정도 경과한 후에는 성능이 더 떨어져서 최하 수준으로 수렴하는 것을 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/coding_for_ssd_part2_7.jpg&quot; alt=&quot;그림 7: 여러 SSD에서 “pre-conditioning”의 효과&quot; /&gt;
그림 제공: StorageReview.com &lt;sup id=&quot;fnref:26:3&quot;&gt;&lt;a href=&quot;#fn:26&quot; class=&quot;footnote&quot;&gt;16&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;그림 7에서 나타나는 현상은 섹션 5.2에서 설명된 것인데, 랜덤 쓰기가 많이 발생해서 SSD가 서스테이닝 모드(Sustaing mode)로 들어가면,
SSD의 Garbage-collection이 사용자의 요청을 따라가지 못하게 된다.
사용자의 요청이 들어올때마다 Garbage-collection이 먼저 블록을 지워야(Erase)하기 때문에,
호스트로부터 오는 사용자 요청과 백그라운드로 실행되는 Garbage-collection이 서로 경합하게 된다.
모든 종류의 워크로드에서 SSD 드라이브가 어떻게 반응하고 어떤 성능을 보여주는지를 확인하는 적절한 모델인지 아닌지는 명확치 않지만,
사람들은 SSD가 보여줄 수 있는 최악의 상황을 만들어 내기 위해서 “Pre-conditioning”을 자주 사용한다.&lt;/p&gt;

&lt;p&gt;여러 제조사들의 다양한 모델들을 비교하기 위한 공통적인 방법과 SSD에서 가능한 최악의 상태 확인은 필요하다.
하지만 최악의 상황에서 가장 좋은 성능을 내는 SSD가 실제 운영 서버용으로 최적이라는 것을 보장하지는 않는다.
많은 운영 환경에서 SSD는 하나의 시스템에서만 사용된다. 그 시스템은 그 시스템만의 특정한 워크로드를 가지기 때문에,
여러 SSD 드라이브에 대해서 조금 더 정확한 비교를 위해서는 동일한 워크로드로 모두 테스트를 수행해야 한다.
지속적인 랜덤 쓰기를 이용한 “Pre-conditioning”이 여러 종류의 SSD에 대해서 적절한 비교 방법이라 할지라도,
가능하다면 대상 서비스의 워크로드를 직접 구현한 벤치마크로 주의해서 진행해야 하는 것이다.
실제 대상 서비스의 워크로드에 따라서 가장 좋은 성능의 SSD가 최적의 선택이 아닐 수도 있는데,
인하우스(in-house)로 개발된 벤치마크 도구는 이러한 오버 스펙을 방지하여 경제적인 절약 효과를 유도하기도 한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h5 id=&quot;ssd--2&quot;&gt;어려운 SSD 벤치마킹&lt;/h5&gt;

  &lt;p&gt;결국 테스트도 사람이 수행하는 것이므로, 모든 벤치마킹이 에러를 회피할 수 있도록 해주는 것은 아니다.
그러므로 제조사나 써드파티(Third-party)에서 제공하는 벤치마크 결과를 참조할 때는 주의해야 하며,
여러 곳으로부터 테스트 결과를 참조하는 것이 좋다.
가능하다면 당신의 서비스 워크로드를 잘 표현할 수 있는 인 하우스 벤치마킹 도구로 사용하고자 하는 SSD를 테스트하도록 하자.
마지막으로 당신의 시스템이나 서비스에서 필요로 하는 성능 메트릭에 집중하도록 하자.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;section-4&quot;&gt;2.3. 워크로드와 메트릭&lt;/h3&gt;

&lt;p&gt;성능 벤치마크들은 모두 다양하지만 동일한 파라미터를 사용하며, 결과 또한 동일한 메트릭으로 성능 정보를 제공한다.
이번 섹션을 통해서 이러한 파라미터와 결과 메트릭을 해석하는 데 있어서 인사이트를 제공해줄 수 있기를 바란다.&lt;/p&gt;

&lt;p&gt;일반적으로 사용되는 파라미터는 다음과 같다:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;워크로드 타입: 사용자로부터 수집되는 데이터에 기반한 특정 패턴의 벤치마크 또는 동일한 시퀀셜 또는 랜덤 데이터 접근의 일반적인 타입 (예: 랜덤 쓰기만 테스트)&lt;/li&gt;
  &lt;li&gt;읽기와 쓰기를 적정 퍼센트로 배분해서 동시에 읽기 쓰기 부하 발생 (예: 30% 읽기와 70% 쓰기)&lt;/li&gt;
  &lt;li&gt;큐 길이(Queue Length): SSD 드라이브로 읽고 쓰기 명령을 전송하는 동시 쓰레드의 개수를 의미&lt;/li&gt;
  &lt;li&gt;접근(읽고 쓰기)하는 데이터 청크의 크기 (4 KB, 8 KB, 기타..)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;벤치마크의 결과는 다른 메트릭으로 표시되는데, 가장 일반적인 메트릭은:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;스루풋(Throughput): 전송 속도를 나타내며, 일반적으로 KB/s 또는 MB/s를 사용한다. 이 메트릭은 시퀀셜 읽고 쓰기용 벤치마크 결과에 사용된다.&lt;/li&gt;
  &lt;li&gt;IOPS: 초당 읽고 쓰기(Input &amp;amp; Ouput) 회수이며, 각 읽고 쓰기 오퍼레이션은 동일한 데이터 청크 크기가 사용(일반적으로는 4KB 청크가 사용됨)된다. 이 메트릭은 랜덤 읽고 쓰기 벤치마크에서 사용된다. &lt;sup id=&quot;fnref:17&quot;&gt;&lt;a href=&quot;#fn:17&quot; class=&quot;footnote&quot;&gt;29&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;레이턴시(Latency): 입출력 명령이 전달된 후 응답을 받기까지의 시간을 의미하며, 일반적으로는 μs(마이크로 초) 또는 ms(밀리 초)가 사용된다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;스루풋(throughput)은 쉽게 이해되는 반면, IOPS는 조금 감을 잡기가 어려울 수도 있다.
예를 들어서, 디스크가 4KB 청크에 대해서 초당 1000 IOPS를 처리한다면 이를 스루풋(throughput)으로 계산하면 1000 x 4096 = 4 MB/s가 되는 것이다.
결과적으로 가능하면 청크 사이즈가 크면 클수록, 높은 IOPS는 높은 스루풋으로 환산될 수 있는 것이다.&lt;/p&gt;

&lt;p&gt;조금 더 이해를 돕기 위해서, 몇 천개의 파일에 아주 조금씩 업데이트하는 로깅 시스템에서 10k IOPS를 처리한다고 가정해보자.
업데이트는 매우 많은 파일에 걸쳐져 있기 때문에 스루풋은 20MB/s정도밖에 안될 것이다.
그런데 만약 이 로깅 시스템이 하나의 파일에만 시퀀셜하게 기록한다고 가정하면, 200MB/s 정도의 향상된 스루풋을 보이게 될 것이다.
이 예제는 랜덤과 시퀀셜 입출력을 비교 설명하기 위해서 만들어낸 수치이긴 하지만, 실제 서비스 환경에서 본인이 경험했던 것이기도 하다.&lt;/p&gt;

&lt;p&gt;스루풋과 IOPS의 차이를 이해하기 위한 또 하나의 예는, 높은 스루풋이 반드시 빠른 시스템을 의미하는 것은 아니다.
만약 레이턴시가 높다면, 아무리 스루풋이 높다고 하더라도 전체적인 시스템의 처리는 느린 것일 수 있다.
예를 들어서 25개 데이터베이스 접속해야 하는 단일 쓰레드 프로세스를 생각해보자.
각 컨넥션은 20ms의 레이턴시를 가질 때, 매번 컨넥션을 생성하는 데에 20ms가 소요되므로,
전체 25개의 컨넥션을 생성하는 데에는 25 x 20ms = 500ms가 소요될 것이다.
이때 우리 서버가 아무리 대역폭이 넓은 좋은 네트워크 카드를 장착하고 있다 하더라도,
이 가상의 프로세스는 레이턴시로 인해서 여전히 느리게 처리될 것이다.&lt;/p&gt;

&lt;p&gt;적어도 이 섹션에서 기억해야 할 중요한 부분은, 각각의 벤치마킹 메트릭은 시스템의 다른 면을 보여주기 때문에 모든 메트릭에 집중해야 한다는 것이다.
또한 이런 메트릭을 제대로 이해하고 있어야 시스템의 병목 현상이 발생했을 때, 정확한 원인을 찾을 수 있을 것이기 때문이다.
벤치마킹 결과를 분석하고 특정 SSD 모델을 선택할 때, SSD를 장착하고자 하는 시스템에서 어떤 메트릭이 가장 크리티컬한 요소인지를 파악해야 한다.
물론 섹션 2.2에서 소개된 바와 같이 인하우스로 개발된 벤치마킹 도구를 대체할 만한 테스트 도구는 없을 것이다.
Jeremiah Peschka&lt;sup id=&quot;fnref:46&quot;&gt;&lt;a href=&quot;#fn:46&quot; class=&quot;footnote&quot;&gt;30&lt;/a&gt;&lt;/sup&gt; 가 작성한 “IOPS are a scam”라는 문서도 많은 도움이 될 것이다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;This articles are translated to Korean with original author’s(&lt;a href=&quot;http://www.goossaert.com/&quot;&gt;Emmanuel Goossaert&lt;/a&gt;) permission. Really appreciate his effort and sharing.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Original articles :&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/&quot;&gt;Part 1: Introduction and Table of Contents&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-2-architecture-of-an-ssd-and-benchmarking/&quot;&gt;Part 2: Architecture of an SSD and Benchmarking&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-3-pages-blocks-and-the-flash-translation-layer/&quot;&gt;Part 3: Pages, Blocks, and the Flash Translation Layer&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-parallelism/&quot;&gt;Part 4: Advanced Functionalities and Internal Parallelism&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-5-access-patterns-and-system-optimizations/&quot;&gt;Part 5: Access Patterns and System Optimizations&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-6-a-summary-what-every-programmer-should-know-about-solid-state-drives/&quot;&gt;Part 6: A Summary – What every programmer should know about solid-state drives&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:31&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://arstechnica.com/information-technology/2012/06/inside-the-ssd-revolution-how-solid-state-disks-really-work/&quot;&gt;http://arstechnica.com/information-technology/2012/06/inside-the-ssd-revolution-how-solid-state-disks-really-work/&lt;/a&gt; &lt;a href=&quot;#fnref:31&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:31:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:14&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Flash_memory&quot;&gt;http://en.wikipedia.org/wiki/Flash_memory&lt;/a&gt; &lt;a href=&quot;#fnref:14&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:51&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.theregister.co.uk/2012/12/03/macronix_thermal_annealing_extends_life_of_flash_memory/&quot;&gt;http://www.theregister.co.uk/2012/12/03/macronix_thermal_annealing_extends_life_of_flash_memory/&lt;/a&gt; &lt;a href=&quot;#fnref:51&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:20&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://centon.com/flash-products/chiptype&quot;&gt;http://centon.com/flash-products/chiptype&lt;/a&gt; &lt;a href=&quot;#fnref:20&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.cse.ohio-state.edu/hpcs/WWW/HTML/publications/papers/TR-09-2.pdf&quot;&gt;Understanding Intrinsic Characteristics and System Implications of Flash Memory based Solid State Drives, Chen et al., 2009&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:23&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.anandtech.com/show/6337/samsung-ssd-840-250gb-review/2&quot;&gt;http://www.anandtech.com/show/6337/samsung-ssd-840-250gb-review/2&lt;/a&gt; &lt;a href=&quot;#fnref:23&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:18&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Hard_disk_drive&quot;&gt;http://en.wikipedia.org/wiki/Hard_disk_drive&lt;/a&gt; &lt;a href=&quot;#fnref:18&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:19&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Hard_disk_drive_performance_characteristics&quot;&gt;http://en.wikipedia.org/wiki/Hard_disk_drive_performance_characteristics&lt;/a&gt; &lt;a href=&quot;#fnref:19&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:25&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.storagereview.com/wd_black_4tb_desktop_hard_drive_review_wd4003fzex&quot;&gt;http://www.storagereview.com/wd_black_4tb_desktop_hard_drive_review_wd4003fzex&lt;/a&gt; &lt;a href=&quot;#fnref:25&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:25:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:30&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.storagereview.com/corsair_vengeance_ddr3_ram_disk_review&quot;&gt;http://www.storagereview.com/corsair_vengeance_ddr3_ram_disk_review&lt;/a&gt; &lt;a href=&quot;#fnref:30&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:30:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:52&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html&quot;&gt;http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html&lt;/a&gt; &lt;a href=&quot;#fnref:52&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:52:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:36&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://us.hardware.info/reviews/4178/10/hardwareinfo-tests-lifespan-of-samsung-ssd-840-250gb-tlc-ssd-updated-with-final-conclusion-final-update-20-6-2013&quot;&gt;http://us.hardware.info/reviews/4178/10/hardwareinfo-tests-lifespan-of-samsung-ssd-840-250gb-tlc-ssd-updated-with-final-conclusion-final-update-20-6-2013&lt;/a&gt; &lt;a href=&quot;#fnref:36&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://csl.skku.edu/papers/CS-TR-2010-329.pdf&quot;&gt;Parameter-Aware I/O Management for Solid State Disks (SSDs), Kim et al., 2012&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:2:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://bit.csc.lsu.edu/~fchen/paper/papers/hpca11.pdf&quot;&gt;Essential roles of exploiting internal parallelism of flash memory based solid state drives in high-speed data processing, Chen et al, 2011&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:6&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://instartlogic.com/resources/research_papers/aanand-tunable_efficient_ssd_indexes.pdf&quot;&gt;Design Patterns for Tunable and Efficient SSD-based Indexes, Anand et al., 2012&lt;/a&gt; &lt;a href=&quot;#fnref:6&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:26&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.storagereview.com/samsung_ssd_840_pro_review&quot;&gt;http://www.storagereview.com/samsung_ssd_840_pro_review&lt;/a&gt; &lt;a href=&quot;#fnref:26&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:26:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:26:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:26:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:27&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.storagereview.com/micron_p420m_enterprise_pcie_ssd_review&quot;&gt;http://www.storagereview.com/micron_p420m_enterprise_pcie_ssd_review&lt;/a&gt; &lt;a href=&quot;#fnref:27&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:27:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:27:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:67&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.gamersnexus.net/guides/956-how-ssds-are-made&quot;&gt;http://www.gamersnexus.net/guides/956-how-ssds-are-made&lt;/a&gt; &lt;a href=&quot;#fnref:67&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:68&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.gamersnexus.net/guides/1148-how-ram-and-ssds-are-made-smt-lines&quot;&gt;http://www.gamersnexus.net/guides/1148-how-ram-and-ssds-are-made-smt-lines&lt;/a&gt; &lt;a href=&quot;#fnref:68&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:69&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;http://www.tweaktown.com/articles/4655/kingston_factory_tour_making_of_an_ssd_from_start_to_finish/index.html&quot;&gt;http://www.tweaktown.com/articles/4655/kingston_factory_tour_making_of_an_ssd_from_start_to_finish/index.html&lt;/a&gt; &lt;a href=&quot;#fnref:69&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:70&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.youtube.com/watch?v=DvA9koAMXR8&quot;&gt;http://www.youtube.com/watch?v=DvA9koAMXR8&lt;/a&gt; &lt;a href=&quot;#fnref:70&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:71&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.youtube.com/watch?v=3s7KG6QwUeQ&quot;&gt;http://www.youtube.com/watch?v=3s7KG6QwUeQ&lt;/a&gt; &lt;a href=&quot;#fnref:71&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:21&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.thessdreview.com/our-reviews/samsung-64gb-mlc-ssd/&quot;&gt;http://www.thessdreview.com/our-reviews/samsung-64gb-mlc-ssd/&lt;/a&gt; &lt;a href=&quot;#fnref:21&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:28&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.storagereview.com/intel_x25-m_ssd_review&quot;&gt;http://www.storagereview.com/intel_x25-m_ssd_review&lt;/a&gt; &lt;a href=&quot;#fnref:28&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:22&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.anandtech.com/show/7594/samsung-ssd-840-evo-msata-120gb-250gb-500gb-1tb-review&quot;&gt;http://www.anandtech.com/show/7594/samsung-ssd-840-evo-msata-120gb-250gb-500gb-1tb-review&lt;/a&gt; &lt;a href=&quot;#fnref:22&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:15&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Serial_ATA&quot;&gt;http://en.wikipedia.org/wiki/Serial_ATA&lt;/a&gt; &lt;a href=&quot;#fnref:15&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:66&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://blog.zorinaq.com/?e=29&quot;&gt;http://blog.zorinaq.com/?e=29&lt;/a&gt; &lt;a href=&quot;#fnref:66&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:50&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://searchsolidstatestorage.techtarget.com/feature/The-truth-about-SSD-performance-benchmarks&quot;&gt;http://searchsolidstatestorage.techtarget.com/feature/The-truth-about-SSD-performance-benchmarks&lt;/a&gt; &lt;a href=&quot;#fnref:50&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:17&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/IOPS&quot;&gt;http://en.wikipedia.org/wiki/IOPS&lt;/a&gt; &lt;a href=&quot;#fnref:17&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:46&quot;&gt;
      &lt;p&gt; &lt;a href=&quot;http://www.brentozar.com/archive/2013/09/iops-are-a-scam/&quot;&gt;http://www.brentozar.com/archive/2013/09/iops-are-a-scam/&lt;/a&gt; &lt;a href=&quot;#fnref:46&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Thu, 14 Jul 2016 20:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/14/coding-for-ssd-part-2/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/14/coding-for-ssd-part-2/</guid>
        
        <category>ssd</category>
        
        <category>nand-flash</category>
        
        <category>garbage-collection</category>
        
        <category>LBA</category>
        
        <category>PBA</category>
        
        <category>block</category>
        
        <category>page</category>
        
        <category>clustered-block</category>
        
        
      </item>
    
      <item>
        <title>개발자를 위한 SSD (Coding for SSD) - Part 1 : 목차</title>
        <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;현재 개발중인 &lt;a href=&quot;http://codecapsule.com/2012/11/07/ikvs-implementing-a-key-value-store-table-of-contents/&quot;&gt;Key/Value Store&lt;/a&gt;가
SSD를 최적으로 사용하도록 하기 위해서는 SSD의 내부적인 특성이나 작동 방식에 대해서 정확한 이해가 필요했다.
인터넷에는 이미 많은 SSD 관련 자료들이 있지만, 대 부분은 부족하거나 잘못된 정보들이 많으며, 제대로 정리된 문서를 찾기는 쉽지 않았다.
결국 내 프로그램이 SSD를 최적으로 사용하도록 하기 위해서는 상당히 많은 문서들과 벤치마크 자료들을 찾고 살펴 봐야 했다.&lt;/p&gt;

&lt;p&gt;내가 알게 된 사실들과 결론이, 다른 사람들에게도 많은 도움이 될 것이라는 생각을 하게 되었고 그래서 이미 온라인에 공개된 많은 정보들을 30 페이지 분량의 실용적인 지식을 담은 글을 쓰게 되었다.
단순히 블로그에 올릴 수 있는 분량이 아니어서, 좀 더 압축할 수 있는 수준으로 내용을 분류하여 5개의 챕터로 분류하였다.
전체 목차는 이 글의 아래에서 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;“Coding for SSDs”의 요약 정보를 담고 있는 Part 6은 가장 중요한 부분인데,
이는 아마도 급하게 SSD 관련 프로그램을 작성해야 하는 사람에게는 큰 도움이 될 것으로 보인다.
요약 챕터에는 SSD의 기본적인 정보뿐만 아니라, SSD로부터 최적의 방법으로 데이터를 읽고 쓰기 위한 패턴에 대해서도 언급하고 있다.&lt;/p&gt;

&lt;p&gt;“Coding for SSDs”의 또다른 중요 포인트는 이 글의 내용이 내가 개발하고 있는 Key-Value 스토어 프로젝트와는 무관하기 때문에,
Key-Value 스토리지에 대한 별도의 선행 지식이 필요치 않다는 것이다.
아직 날짜가 정확히 결정된 것은 아니지만,
Key-Value 스토리지가 SSD의 장점을 최대한 발휘하기 위해서 어떻게 Hash table을 구현해야 하는지에 대한 포스트도 계획하고 있다.&lt;/p&gt;

&lt;p&gt;안타깝게도 내가 추천하는 최적의 SSD 액세스 패턴을 증명해줄 별도의 프로그램을 작성하지는 않았다는 것은 후회스러운 부분이긴 하다.
하지만 그런 코드가 있었다면, 나는 시중에 출시된 수 많은 SSD 드라이브들에 대해서 성능 테스트를 진행했어야 할 것이다.
이는 시간적인 부분뿐만 아니라 경제적으로도 많은 어려움이 있었을 것이다. 나는 내가 찾은 자료들을 아주 꼼꼼하고 비판적으로 검토하면서 이 자료를 작성했다. 혹시 내가 추천하는 내용중 잘못되거나 이상한 부분이나 궁금한 부분이 있다면 코멘트를 남겨 주길 바란다.&lt;/p&gt;

&lt;p&gt;마지막으로, 우측 상단의 Subscription 패널에 등록하면 Code Capsule에 게재되는 새로운 게시물에 대해서 E-mail을 수신을 할 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;table-of-content&quot;&gt;Table of Content&lt;/h2&gt;

&lt;h6 id=&quot;part-1-&quot;&gt;Part 1: 목차&lt;/h6&gt;

&lt;h6 id=&quot;part-2-ssd--20160714coding-for-ssd-part-2&quot;&gt;Part 2: &lt;a href=&quot;/2016/07/14/coding-for-ssd-part-2/&quot;&gt;SSD의 아키텍처와 벤치마킹&lt;/a&gt;&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;SSD의 구조
        &lt;ul&gt;
          &lt;li&gt;1.1. NAND 플래시 메모리 셀&lt;/li&gt;
          &lt;li&gt;1.2. SSD의 구성&lt;/li&gt;
          &lt;li&gt;1.3. SSD의 생산 공정&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;벤치마킹과 성능 메트릭
        &lt;ul&gt;
          &lt;li&gt;2.1. 기본 벤치마킹&lt;/li&gt;
          &lt;li&gt;2.2. 프리 컨디셔닝 (Pre-conditioning)&lt;/li&gt;
          &lt;li&gt;2.3. 워크로드와 메트릭&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;part-3-----ftlflash-translation-layer20160715coding-for-ssd-part-3&quot;&gt;Part 3: &lt;a href=&quot;/2016/07/15/coding-for-ssd-part-3/&quot;&gt;페이지 &amp;amp; 블록 &amp;amp; FTL(Flash Translation Layer)&lt;/a&gt;&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;기본 오퍼레이션
        &lt;ul&gt;
          &lt;li&gt;3.1. 읽기 &amp;amp; 쓰기 &amp;amp; 삭제&lt;/li&gt;
          &lt;li&gt;3.2. 쓰기 예제&lt;/li&gt;
          &lt;li&gt;3.3. Write amplification&lt;/li&gt;
          &lt;li&gt;3.4. Wear leveling&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;FTL (Flash Translation Layer)
        &lt;ul&gt;
          &lt;li&gt;4.1. FTL의 필요성&lt;/li&gt;
          &lt;li&gt;4.2. 논리적 블록 맵핑 (Logical block mapping)&lt;/li&gt;
          &lt;li&gt;4.3. 업계 상황&lt;/li&gt;
          &lt;li&gt;4.4. Garbage collection&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;part-4-----20160716coding-for-ssd-part-4&quot;&gt;Part 4: &lt;a href=&quot;/2016/07/16/coding-for-ssd-part-4/&quot;&gt;고급 기능과 내부 병렬 처리&lt;/a&gt;&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;고급 기능
        &lt;ul&gt;
          &lt;li&gt;5.1. TRIM&lt;/li&gt;
          &lt;li&gt;5.2. Over-provisioning&lt;/li&gt;
          &lt;li&gt;5.3. Secure Erase&lt;/li&gt;
          &lt;li&gt;5.4. Native Command Queueing (NCQ)&lt;/li&gt;
          &lt;li&gt;5.1. 전력 차단 보호 (Power-loss protection)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;SSD의 내부 병렬 처리
        &lt;ul&gt;
          &lt;li&gt;6.1. 제한된 I/O 버스 대역폭&lt;/li&gt;
          &lt;li&gt;6.2. 병렬 처리&lt;/li&gt;
          &lt;li&gt;6.3. Clustered blocks&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;part-5----20160717coding-for-ssd-part-5&quot;&gt;Part 5: &lt;a href=&quot;/2016/07/17/coding-for-ssd-part-5/&quot;&gt;접근 방법과 시스템 최적화&lt;/a&gt;&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;액세스 패턴
        &lt;ul&gt;
          &lt;li&gt;7.1. 시퀀셜과 랜덤 I/O의 정의&lt;/li&gt;
          &lt;li&gt;7.2. 쓰기&lt;/li&gt;
          &lt;li&gt;7.3. 읽기&lt;/li&gt;
          &lt;li&gt;7.4. 동시 읽고 쓰기&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;시스템 최적화
        &lt;ul&gt;
          &lt;li&gt;8.1. 파티션 얼라인먼트 (Partition alignment)&lt;/li&gt;
          &lt;li&gt;8.2. 파일 시스템 파라미터&lt;/li&gt;
          &lt;li&gt;8.3. 운영 체제의 I/O 스케줄러&lt;/li&gt;
          &lt;li&gt;8.4. 스왑 (Swap)&lt;/li&gt;
          &lt;li&gt;8.5. 임시 파일&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;part-6----ssd----20160718coding-for-ssd-part-6&quot;&gt;Part 6: &lt;a href=&quot;/2016/07/18/coding-for-ssd-part-6/&quot;&gt;요약 – 개발자가 SSD에 대해서 알아야 할 것들&lt;/a&gt;&lt;/h6&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;This articles are translated to Korean with original author’s(&lt;a href=&quot;http://www.goossaert.com/&quot;&gt;Emmanuel Goossaert&lt;/a&gt;) permission.&lt;br /&gt;
    Really appreciate his effort and sharing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Original articles :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/&quot;&gt;Part 1: Introduction and Table of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-2-architecture-of-an-ssd-and-benchmarking/&quot;&gt;Part 2: Architecture of an SSD and Benchmarking&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-3-pages-blocks-and-the-flash-translation-layer/&quot;&gt;Part 3: Pages, Blocks, and the Flash Translation Layer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-parallelism/&quot;&gt;Part 4: Advanced Functionalities and Internal Parallelism&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-5-access-patterns-and-system-optimizations/&quot;&gt;Part 5: Access Patterns and System Optimizations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://codecapsule.com/2014/02/12/coding-for-ssds-part-6-a-summary-what-every-programmer-should-know-about-solid-state-drives/&quot;&gt;Part 6: A Summary – What every programmer should know about solid-state drives&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 13 Jul 2016 20:00:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/13/coding-for-ssd-part-1/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/13/coding-for-ssd-part-1/</guid>
        
        <category>ssd</category>
        
        <category>nand-flash</category>
        
        <category>garbage-collection</category>
        
        <category>LBA</category>
        
        <category>PBA</category>
        
        <category>block</category>
        
        <category>page</category>
        
        <category>clustered-block</category>
        
        
      </item>
    
      <item>
        <title>kakao 기술 블로그가 GitHub Pages로 간 까닭은</title>
        <description>&lt;p&gt;kakao 기술 블로그는 올해 초 &lt;a href=&quot;https://ghost.org&quot;&gt;Ghost&lt;/a&gt; 블로깅 플랫폼을 사용해서 오픈했으나,
최근 &lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;와 &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;으로 바뀌었습니다.
이 글에서는 kakao 기술 블로그를 &lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;로 옮기면서 &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;에 위해 추가한
기능들 - 태그별 포스트 목록 페이지, 작성자별 포스트 목록 페이지, 사이트맵 - 을 소개합니다.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;prologue&quot;&gt;Prologue&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/89caada5a1ddde2795b892cae089b4da1667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;오픈 초기에는 kakao는 &lt;a href=&quot;https://brunch.co.kr&quot;&gt;브런치&lt;/a&gt;, &lt;a href=&quot;http://www.tistory.com&quot;&gt;티스토리&lt;/a&gt;, &lt;a href=&quot;http://blog.daum.net&quot;&gt;다음블로그&lt;/a&gt;, &lt;a href=&quot;https://plain.is&quot;&gt;플레인&lt;/a&gt;까지
여러가지의 블로그스러운(?) 서비스를 하고 있으면서
정작 기술 블로그는 &lt;a href=&quot;https://ghost.org&quot;&gt;Ghost&lt;/a&gt;을 사용하는 것에 대해 사내외에서 많은 의문을 제기했었죠.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/326692ab97f80a3e1dd36120231d666b1667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.tistory.com&quot;&gt;티스토리&lt;/a&gt;, &lt;a href=&quot;https://brunch.co.kr&quot;&gt;브런치&lt;/a&gt;, &lt;a href=&quot;https://wordpress.org&quot;&gt;설치형 WordPress&lt;/a&gt;, 자체 개발까지 다양한 솔루션을 검토했지만,
생뚱맞게도 &lt;a href=&quot;https://ghost.org&quot;&gt;Ghost&lt;/a&gt;을 사용하기로 했습니다.
&lt;a href=&quot;https://guides.github.com/features/mastering-markdown/&quot;&gt;Markdown&lt;/a&gt; 지원, 작성과 발행 권한 분리, 커스텀 디자인
그리고 기능 추가의 용이성(단순 블로그 외에도 많은 기능이 계획되어 있습니다)을 고려했습니다.
가장 결정적인 이유는 운영자의 &lt;strong&gt;개취&lt;/strong&gt;였는다는 설도 있습니다만…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/01a3b97731f42b4efc929ebd7fa376431667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;그러나, 운영 과정에서 블로그 외의 기능 개발이 보류되면서
&lt;a href=&quot;https://ghost.org&quot;&gt;Ghost&lt;/a&gt;를 선택한 가장 큰 이유였던 &lt;strong&gt;기능 추가의 용이성&lt;/strong&gt;은 의미가 없어졌고,
운영자의 &lt;strong&gt;개취&lt;/strong&gt;만이 남았습니다.
무엇보다도(!) 기술 블로그 만의 &lt;strong&gt;Geek스러움&lt;/strong&gt;이 부족했습니다.&lt;/p&gt;

&lt;p&gt;그래서… 바꿨습니다!&lt;/p&gt;

&lt;h2 id=&quot;github-pages--jekyll&quot;&gt;&lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt; 와 &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;는 아시다시피 GitHub 저장소의 내용을 웹 페이지로 서비스해주는 기능입니다.
공짜로 웹 서버를 구축할 수 있지만, 스태틱 컨텐츠만 서비스할 수 있어서 제한적인 용도(예: 뻔한 오픈소스 소개/문서 사이트)에 주로 사용됩니다.
그러나, 여기에 &lt;a href=&quot;https://staticsitegenerators.net&quot;&gt;정적 사이트 생성기&lt;/a&gt;가 결합되면 활용의 폭이 훨씬 넓어집니다.
그 중에서도 &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;은 &lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;가 기본으로 지원하는 정적 사이트 생성기로,
소스(주로 &lt;a href=&quot;https://guides.github.com/features/mastering-markdown/&quot;&gt;Markdown&lt;/a&gt;)를 깃헙에 올리면 &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;을 실행해서 정적 사이트를 생성하는 과정을 깃헙 서버가 자동으로 수행하고,
그것을 웹 페이지로 서비스합니다.&lt;/p&gt;

&lt;p&gt;자세한 내용은 &lt;a href=&quot;https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages&quot;&gt;GitHub Pages에서 Jekyll을 정적 사이트 생성기로 사용하기&lt;/a&gt;에게 맡기고,
이 글에서는 kakao 기술 블로그를 만들면서 추가한 기능들만 소개하겠습니다.&lt;/p&gt;

&lt;h3 id=&quot;section&quot;&gt;태그별 포스트 목록 페이지&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;은 직접적으로 태깅(tagging)을 지원하지 않습니다.
이를 지원하는 여러가지 플러그인들을 테스트 해보았지만, 아쉽게도 &lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;와 함께 쓸 수 없는 경우가 많았습니다.
그래서 &lt;a href=&quot;https://jekyllrb.com/docs/collections/&quot;&gt;Jekyll의 Collections 기능&lt;/a&gt;을 활용해서 간단하게 만들었습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt; 파일에 다음과 같이 &lt;code class=&quot;highlighter-rouge&quot;&gt;collections&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;defaults&lt;/code&gt; 설정을 추가합니다:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;collections&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tags/:path/&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tags&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tag&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_tags&lt;/code&gt; 디렉토리 안에 있는 파일 목록을 수집해서 &lt;code class=&quot;highlighter-rouge&quot;&gt;/tags/...&lt;/code&gt;로 서비스되도록 HTML 파일을 생성하라는 설정입니다.
(디렉토리 이름 앞의 &lt;code class=&quot;highlighter-rouge&quot;&gt;_&lt;/code&gt;는 오타가 아닙니다. 설정에는 &lt;code class=&quot;highlighter-rouge&quot;&gt;_&lt;/code&gt;가 없고, 디렉토리 이름에는 &lt;code class=&quot;highlighter-rouge&quot;&gt;_&lt;/code&gt;가 있습니다.)&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_layouts&lt;/code&gt; 디렉토리에 &lt;strong&gt;태그별 포스트 목록&lt;/strong&gt;을 보여줄 템플릿 파일
&lt;a href=&quot;https://raw.githubusercontent.com/kakao/kakao.github.io/master/_layouts/tag.html&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;tag.html&lt;/code&gt;&lt;/a&gt;을 만듭니다.
참고로, &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;은 &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid&lt;/a&gt; 템플릿을 사용합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
layout: default
---
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;navbar&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h5&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;link-back&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Main&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/h5&amp;gt;&lt;/span&gt;
    {% include share.html %}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cover&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;
     &lt;span class=&quot;err&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;background-image: url({{ page.image }});&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ page.title | default: page.name }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;All Posts in &lt;span class=&quot;nt&quot;&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;{{ page.name }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tag-content&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;role=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;post-list&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            {% for post in site.posts %}
                {% if post.tags contains page.name %}
                    {% include item.html %}
                {% endif %}
            {% endfor %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
        {% include pagination.html %}
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_tags&lt;/code&gt; 디렉토리를 만들고, 그 아래에 각 태그마다
하나의 파일(예: &lt;a href=&quot;https://raw.githubusercontent.com/kakao/kakao.github.io/master/_tags/opensource.md&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;opensource.md&lt;/code&gt;&lt;/a&gt;)을 만듭니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;opensource&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;오픈소스&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/files/covers/opensource.jpg&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://jekyllrb.com/docs/frontmatter/&quot;&gt;Front Matter&lt;/a&gt;의 내용을 살펴보면:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt;: &lt;strong&gt;매칭을 위한 문자열&lt;/strong&gt;(일종의 PK)입니다.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;title&lt;/code&gt;: 태그별 포스트 목록 페이지 상단에 표시되는 것입니다.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;image&lt;/code&gt;: 태그별 포스트 목록 페이지 상단에 표시되는 것으로 없어도 됩니다.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;layout&lt;/code&gt;은 생략했으므로, 위에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;에 설정한 &lt;code class=&quot;highlighter-rouge&quot;&gt;tag&lt;/code&gt;가 됩니다.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;permalink&lt;/code&gt;는 생략했으므로, 위에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;에 설정한 &lt;code class=&quot;highlighter-rouge&quot;&gt;/tags/:path/&lt;/code&gt;가 됩니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;주의: 새로운 태그가 추가되면 새로운 파일을 만들어줘야 합니다.&lt;/p&gt;

  &lt;p&gt;이 부분이 이 글에서 소개하는 태깅 방식의 가장 큰 문제점인데…
플러그인 없이 해결하는 방법을 아직 찾지 못했습니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;태그별 포스트 목록 페이지&lt;/strong&gt;를 생성하는 과정은 다음과 같습니다:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_tags&lt;/code&gt; 디렉토리 아래의 파일목록을 수집해서 템플릿 변수 &lt;code class=&quot;highlighter-rouge&quot;&gt;site.tags&lt;/code&gt; 에 담아둔다.(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;collections&lt;/code&gt; 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;tags&lt;/code&gt;가 있으므로)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_tags&lt;/code&gt; 디렉토리 아래의 파일들(예: &lt;code class=&quot;highlighter-rouge&quot;&gt;_tags/opensource.md&lt;/code&gt; 등)을 처리해서 HTML 파일을 생성한다.(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;collections&lt;/code&gt; 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;output: true&lt;/code&gt; 이므로)&lt;/li&gt;
  &lt;li&gt;각 태그 파일의 데이터를 &lt;code class=&quot;highlighter-rouge&quot;&gt;_layouts/tag.html&lt;/code&gt; 레이아웃 템플릿을 결합해서(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;defaults&lt;/code&gt; 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;layout: tag&lt;/code&gt; 이므로)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;tags/opensource/index.html&lt;/code&gt; 등의 파일에 저장한다.(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;collections&lt;/code&gt; 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;permalink: /tags/:path/&lt;/code&gt; 이므로)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;레이아웃 템플릿(&lt;code class=&quot;highlighter-rouge&quot;&gt;_layouts/tag.html&lt;/code&gt;)에서 핵심적인 &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid&lt;/a&gt; 템플릿 코드를 살펴보면:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;post-list&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    {% for post in site.posts %}
        {% if post.tags contains page.name %}
            {% include item.html %}
        {% endif %}
    {% endfor %}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;모든 포스트 목록(&lt;code class=&quot;highlighter-rouge&quot;&gt;site.posts&lt;/code&gt;)을 순회하면서,
포스트의 태그목록 배열(&lt;code class=&quot;highlighter-rouge&quot;&gt;post.tags&lt;/code&gt;)에 지정한 태그를(&lt;code class=&quot;highlighter-rouge&quot;&gt;page.name&lt;/code&gt;)가 포함되어 있으면
해당 포스트를 출력&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;하는 코드가 눈에 들어올 겁니다.
(주의: 여기서 &lt;code class=&quot;highlighter-rouge&quot;&gt;page&lt;/code&gt; 변수에는 태그 파일의 데이터가 담겨있습니다.)&lt;/p&gt;

&lt;p&gt;단순무식한 &lt;code class=&quot;highlighter-rouge&quot;&gt;O(포스트개수 x 태그개수)&lt;/code&gt; 풀스캔이지만,
이 과정은 정적 사이트 생성 과정에만 이루어지므로 실제 서비스에는 영향이 없습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://jekyllrb.com/docs/datafiles/&quot;&gt;Jekyll의 Data Files 기능&lt;/a&gt;을 이용하면
좀 더 쉽고 효율적으로 만들 수 있을것 같지만, 플러그인을 만들어야 해서 pass!
(포스트가 수만개, 태그가 수만개라면, 그냥 데이터베이스 기반 블로그를 쓰시는 것이 정신 건강에 좋습니다.)&lt;/p&gt;

&lt;h3 id=&quot;section-1&quot;&gt;작성자별 포스트 목록 페이지&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;은 작성자라는 개념(author attribution)을 지원하지 않습니다.
애초에 흔한 웹UI도 없으니, 로그인도 없고, 작성자라는 개념도 없죠.
그래서 태그 분류하기에서 사용했던 방법을 조금 변형해서 작성자로 분류하기를 만들었습니다.
과정은 태깅과 거의 같습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt; 파일에 다음과 같이 &lt;code class=&quot;highlighter-rouge&quot;&gt;collections&lt;/code&gt;와 &lt;code class=&quot;highlighter-rouge&quot;&gt;defaults&lt;/code&gt; 설정을 추가합니다:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;collections&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/authors/:path/&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;authors&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;author&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;설명이 필요없겠죠? 태깅과 거의 같습니다. ;)&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_layouts&lt;/code&gt; 디렉토리에 &lt;strong&gt;작성자별 포스트 목록&lt;/strong&gt;을 보여줄 템플릿 파일
&lt;a href=&quot;https://raw.githubusercontent.com/kakao/kakao.github.io/master/_layouts/author.html&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;author.html&lt;/code&gt;&lt;/a&gt;을 만듭니다:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
layout: default
---
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;navbar&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h5&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;link-back&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Main&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/h5&amp;gt;&lt;/span&gt;
    {% include share.html %}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cover&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;
     &lt;span class=&quot;err&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;cover&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;background-image: url({{ page.cover }});&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cover-author-image&quot;&lt;/span&gt;
             &lt;span class=&quot;err&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;background-image:url({{ page.image }});&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sr-only&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;{{ page.name }}'s profile image&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ page.title }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;All Posts by &lt;span class=&quot;nt&quot;&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ page.name }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;author-content&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;role=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;post-list&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            {% for post in site.posts %}
                {% if post.author == page.name %}
                    {% include item.html %}
                {% endif %}
            {% endfor %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
        {% include pagination.html %}
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;역시나 설명이 필요없겠죠? 태깅과 거의 같습니다. ;)&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_authors&lt;/code&gt; 디렉토리를 만들고, 그 아래에 각 사용자마다
하나의 파일(예: &lt;a href=&quot;https://raw.githubusercontent.com/kakao/kakao.github.io/master/_authors/ryan.md&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ryan.md&lt;/code&gt;&lt;/a&gt;)을 만듭니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ryan&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;라이언&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/files/authors/ryan.jpg&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;작성자별 포스트 목록 페이지&lt;/strong&gt;를 생성하는 과정은 다음과 같습니다:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_author&lt;/code&gt; 디렉토리 아래의 파일목록을 수집해서 템플릿 변수 &lt;code class=&quot;highlighter-rouge&quot;&gt;site.authors&lt;/code&gt; 에 담아둔다.(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 collections 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;authors&lt;/code&gt;가 있으므로)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;_authors&lt;/code&gt; 디렉토리 아래의 파일들(예: &lt;code class=&quot;highlighter-rouge&quot;&gt;_authors/ryan.md&lt;/code&gt; 등…)을 처리해서 HTML 파일을 생성한다.(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;collections&lt;/code&gt; 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;output: true&lt;/code&gt; 이므로)&lt;/li&gt;
  &lt;li&gt;각 태그 파일의 데이터를 &lt;code class=&quot;highlighter-rouge&quot;&gt;_layouts/author.html&lt;/code&gt; 레이아웃 템플릿을 결합해서(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;defaults&lt;/code&gt; 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;layout: author&lt;/code&gt; 이므로)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;authors/ryan/index.html&lt;/code&gt; 등의 파일에 저장한다.(&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;collections&lt;/code&gt; 설정에 &lt;code class=&quot;highlighter-rouge&quot;&gt;permalink: /authors/:path/&lt;/code&gt; 이므로)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/files/blog-copy-paste.jpg&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;이하 동문 생략…&lt;/p&gt;

&lt;h3 id=&quot;section-2&quot;&gt;사이트맵&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://ghost.org&quot;&gt;Ghost&lt;/a&gt;를 선택한 이유 중의 하나가 &lt;a href=&quot;https://en.wikipedia.org/wiki/Search_engine_optimization&quot;&gt;검색 엔진 최적화&lt;/a&gt;였습니다.
별다른 설정없이도 페이지에 meta 태그들도 주렁주렁 많이 달아주고,
&lt;a href=&quot;http://www.sitemaps.org&quot;&gt;사이트맵&lt;/a&gt;도 자동으로 만들어 줍니다.&lt;/p&gt;

&lt;p&gt;물론 &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;에도 &lt;a href=&quot;http://www.sitemaps.org&quot;&gt;사이트맵&lt;/a&gt;을 지원하는 플러그인들이 많습니다만,
역시 &lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;가 발목을 잡습니다.
그래서… 기본 테마에 포함된 &lt;code class=&quot;highlighter-rouge&quot;&gt;feed.xml&lt;/code&gt;를 참조해서 간단하게 만들었습니다.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
layout: null
---
&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml-stylesheet type=&quot;text/xsl&quot; href=&quot;/sitemap.xsl&quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;urlset&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.sitemaps.org/schemas/sitemap/0.9&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  {% for post in site.posts %}
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;{{ site.url }}{{ site.baseurl }}{{ post.url }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
    {% if post.date %}
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;lastmod&amp;gt;&lt;/span&gt;{{ post.date | date_to_xmlschema }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/lastmod&amp;gt;&lt;/span&gt;
    {% endif %}
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;changefreq&amp;gt;&lt;/span&gt;weekly&lt;span class=&quot;nt&quot;&gt;&amp;lt;/changefreq&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;1.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  {% endfor %}
  {% for page in site.pages %}
  {% if page.sitemap %}
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;{{ site.url }}{{ site.baseurl }}{{ page.url }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
    {% if page.date %}
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;lastmod&amp;gt;&lt;/span&gt;{{ page.date | date_to_xmlschema }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/lastmod&amp;gt;&lt;/span&gt;
    {% endif %}
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;changefreq&amp;gt;&lt;/span&gt;{{ sitemap.freq | default: 'monthly' }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/changefreq&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;{{ sitemap.priority | default: '1.0' }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  {% endif %}
  {% endfor %}
  {% for author in site.authors %}
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;{{ site.url }}{{ site.baseurl }}/authors/{{ author.name }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;changefreq&amp;gt;&lt;/span&gt;monthly&lt;span class=&quot;nt&quot;&gt;&amp;lt;/changefreq&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;0.8&lt;span class=&quot;nt&quot;&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  {% endfor %}
  {% for tag in site.tags %}
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;{{ site.url }}{{ site.baseurl }}/tags/{{ tag.name }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;changefreq&amp;gt;&lt;/span&gt;monthly&lt;span class=&quot;nt&quot;&gt;&amp;lt;/changefreq&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;0.8&lt;span class=&quot;nt&quot;&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  {% endfor %}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/urlset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;파일 맨 앞에 &lt;a href=&quot;https://jekyllrb.com/docs/frontmatter/&quot;&gt;Front Matter&lt;/a&gt;는 없어도 있어야 합니다(응?)
포스트(&lt;code class=&quot;highlighter-rouge&quot;&gt;site.posts&lt;/code&gt;)는 모두 포함되고,
페이지(&lt;code class=&quot;highlighter-rouge&quot;&gt;site.pages&lt;/code&gt;)중에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;sitemap&lt;/code&gt;속성이 있는 페이지만 포함됩니다.
그리고, 위에서 만든 태그별 포스트 목록과 작성자별 포스트 목록도 포함됩니다.
차~ㅁ 쉽죠?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://raw.githubusercontent.com/kakao/kakao.github.io/master/sitemap.xml&quot;&gt;sitemap.xml&lt;/a&gt; 파일은 검색 엔진을 위한 것입니다만,
휴먼을 위한 최소한의 배려로, &lt;a href=&quot;https://ghost.org&quot;&gt;Ghost&lt;/a&gt;를 참고해서
&lt;a href=&quot;https://raw.githubusercontent.com/kakao/kakao.github.io/master/sitemap.xsl&quot;&gt;sitemap.xsl&lt;/a&gt;도 추가했습니다.&lt;/p&gt;

&lt;p&gt;그 결과는 &lt;a href=&quot;http://tech.kakao.com/sitemap.xml&quot;&gt;http://tech.kakao.com/sitemap.xml&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/p&gt;

&lt;h2 id=&quot;epilogue&quot;&gt;Epilogue&lt;/h2&gt;

&lt;p&gt;기업의 기술 블로그에 이런 가벼운 내용이 올라가도 될까…라는 고민을 잠시했지만…
&lt;a href=&quot;http://woowabros.github.io/woowabros/2016/06/30/woowabros_cto.html&quot;&gt;우아한형제들의 기술 블로그에 김범준 님이 쓰신 글&lt;/a&gt;을 보고 용기내어 올려봅니다.&lt;/p&gt;

&lt;p&gt;앞으로도 kakao 기술 블로그 많이 사랑해 주세요. 꾸벅~&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://item.kakaocdn.net/do/-26p06+UqCd0OAgiRHNZwHaq4FJCveCBKCNZV-bZscw_/46d01f5ff600e23a970e9ce86c02eff81667fc7b08261b4c493670baa83d5cb9&quot; class=&quot;hcenter&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;전하는 말씀(or 광고)&lt;/p&gt;

  &lt;p&gt;kakao의 오픈소스 업무를 담당할 인재를 모십니다.
&lt;a href=&quot;http://www.kakaocorp.com/recruit/progressRecruit?uid=9789&quot;&gt;지원하기&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
        <pubDate>Thu, 07 Jul 2016 15:25:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/07/tech-blog-story/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/07/tech-blog-story/</guid>
        
        <category>github-pages</category>
        
        <category>jekyll</category>
        
        <category>static-site-generator</category>
        
        
      </item>
    
      <item>
        <title>ADT 활용 예제1: MySQL Shard 데이터 재분배</title>
        <description>&lt;h2 id=&quot;section&quot;&gt;샤딩의 한계&lt;/h2&gt;

&lt;p&gt;카카오의 많은 서비스들이 데이터베이스로써 MySQL을 사용합니다. 그리고 서비스 규모가 커지면 대용량 분산을 위해 샤딩을 합니다.&lt;/p&gt;

&lt;p&gt;카카오에서 많이 사용하는 샤딩 방법으로 크게 두 가지 방식이 있습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;range-based sharding&lt;/li&gt;
  &lt;li&gt;modulus-based sharding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그러나 두 방법 모두 한계가 있습니다.&lt;/p&gt;

&lt;h3 id=&quot;range--&quot;&gt;Range 방식의 한계&lt;/h3&gt;

&lt;p&gt;특정 ID값을 기준으로, ID 범위에 따라 샤드를 나누는 방식입니다. ID값이 증가하는 추이를 보고서 새로운 샤드 추가가 쉽다는 장점이 있습니다. 반면에 디스크 사용량이나 쿼리 처리량의 밸런스가 많이 안 맞는 경우가 발생하기도 합니다.&lt;/p&gt;

&lt;p&gt;아래 그림과 같이 User ID 기준, Range 방식으로 샤딩을 적용한 어떤 서비스가 있다고 가정하겠습니다. 초창기 샤드는 데이터량이 아주 많고 최근에 추가된 샤드는 다른 쿼리 처리량이 매우 많습니다. 그리고 간혹 초창기 사용자들의 충성도가 높은 서비스의 경우, 초기에 추가한 샤드들도 쿼리량이 적지 않은 경우가 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/adt-range-shard.png&quot; alt=&quot;Range-based 샤딩&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;modulus--&quot;&gt;Modulus 방식의 한계&lt;/h3&gt;

&lt;p&gt;[ID값] % [샤드 개수]의 결과 값으로 샤드 위치를 결정하는 방식입니다. Range 방식에 비해 리소스 사용 밸런스가 잘 맞다고 알려져 있습니다. 그러나 이 방식은 샤드 추가가 어렵습니다.&lt;/p&gt;

&lt;p&gt;아래 그림과 같이 3개의 샤드에서 4개의 샤드로 확장을 하려면 기존의 각 샤드마다 데이터 재배치가 필요합니다. 현재 샤드 개수의 배수로 확장하면 그나마 쉽게 샤드 추가를 할 수 있지만, 만약 현재 샤드 개수가 수십~수백이라면 적지 않은 낭비가 발생할 수도 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/adt-modulus-shard.png&quot; alt=&quot;Modulus-based 샤딩&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;shard-scale-inout&quot;&gt;그 외: Shard Scale-in/out&lt;/h3&gt;

&lt;p&gt;클라우드 서비스에서 제공하는 DB를 사용하는 서비스 경우, 간혹 이런 경우가 있습니다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;피크 타임의 수많은 UPDATE 쿼리를 버티기 위해 shard 수를 늘렸습니다.
그런데 새벽에는 DB 머신이 거의 놀고 있어서 낭비가 발생하고 있습니다.
자유롭게 shard scale in/out할 수 있는 방법은 없을까요?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이러한 기존 문제들을 해결하기 위해서는 &lt;strong&gt;각 샤드들의 데이터를 재분배&lt;/strong&gt;가 필요합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;지금부터 ADT를 활용해 하나의 Master를 여러 샤드로 재분배하는 방법을 소개드리겠습니다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/files/adt-shard-rebalancer.png&quot; alt=&quot;Data Copy Layer의 일부를 구현합니다.&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;adt-handler-&quot;&gt;ADT Handler 구현하기&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;본 글에서 예제로 구현할 handler의 소스는 &lt;a href=&quot;https://github.com/kakao/adt/tree/master/adt-handler-parent/adt-handler-mysql-shard-rebalancer&quot;&gt;여기&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;handler-&quot;&gt;구현할 Handler의 역할&lt;/h3&gt;

&lt;p&gt;두 가지의 핸들러를 구현할 예정입니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;BinlogHandler&lt;/strong&gt;: source DB의 변경 사항을 target DB에 실시간 반영&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;TableCrawlHandler&lt;/strong&gt;: binlog handler로 처리하지 못한 나머지 데이터를 처리&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;project-&quot;&gt;1. Project 생성&lt;/h3&gt;

&lt;p&gt;여기서는 Maven 기반으로 project를 생성하도록 하겠습니다.
생성 후 아래와 같이 parent를 지정합니다.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;project&amp;gt;&lt;/span&gt;
    ...
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;parent&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.kakao.adt&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;adt-handler-parent&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.0.1-SNAPSHOT&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;
    ...
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;tablecrawlhandler-&quot;&gt;2. TableCrawlHandler 구현&lt;/h3&gt;

&lt;p&gt;다음과 같이 handler interface 구현체를 만듭니다.
```java
import com.kakao.adt.handler.MysqlCrawlProcessorHandler;
import com.kakao.adt.mysql.crawler.MysqlCrawlData;&lt;/p&gt;

&lt;p&gt;public class TableCrawlHandler implements MysqlCrawlProcessorHandler {&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@Override
public void processData(MysqlCrawlData data) throws Exception {
    // TODO: 여기가 크롤링한 데이터를 처리하는 곳
}

/* 기타 constructor, method, field 등등 생략 */
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;}
```&lt;/p&gt;

&lt;p&gt;그리고 크롤링한 각 데이터를 &lt;code class=&quot;highlighter-rouge&quot;&gt;INSERT IGNORE&lt;/code&gt;를 이용하여 target DB에 복사합니다.
```java
public void processData(MysqlCrawlData data) throws Exception {
    final List&amp;lt;List&lt;object&gt;&amp;gt; rowList = data.getAllRows();
    final Table table = data.getTable();&lt;/object&gt;&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for(final List&amp;lt;Object&amp;gt; row : rowList){
    // 각 row를 insert ignore
    insertIgnore(table, row);
} }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;public void insertIgnore(Table table, List&lt;object&gt; row) throws Exception{
    final int shardIndex = getShardIndex(table, row);
    final String sql = getInsertIgnoreQuery(table);&lt;/object&gt;&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 적절한 target shard에 데이터 복사
executeUpdate(sql, shardIndex, row); }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;public int getShardIndex(Table table, List&lt;object&gt; row){
    // TODO: table과 row를 이용해 적절한 target shard 위치를 구합니다.
    return n;
}
```&lt;/object&gt;&lt;/p&gt;

&lt;h6 id=&quot;insert-ignore---&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;INSERT IGNORE&lt;/code&gt;로 데이터를 복사하는 이유&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;크롤링과 binlog 처리를 동시에 진행할 경우, 데이터 꼬임을 방지하기 위함&lt;/li&gt;
  &lt;li&gt;크롤링만 따로 실행하더라도, 아래와 같은 테이블은 단순 INSERT 시 duplicated key 에러 가능성 있음&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pk&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;uk&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;binloghandler-&quot;&gt;3. BinlogHandler 구현&lt;/h3&gt;

&lt;p&gt;CrawlerHandler와 비슷하게, BinlogHandler용 interface 구현체를 만듭니다.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kakao.adt.handler.MysqlBinlogProcessorHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kakao.adt.mysql.binlog.MysqlBinlogData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BinlogHandler&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MysqlBinlogProcessorHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;processData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MysqlBinlogData&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// TODO: Binary Log 처리하는 곳&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/* 기타 constructor, method, field 등등 생략 */&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;각 binlog event 별로 다음과 같이 target에 적용합니다.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;processData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MysqlBinlogData&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MysqlBinlogHandler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBinlogEventType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rowBefore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getBeforeValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rowAfter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getAfterValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;WRITE_ROWS_EVENT_HANDLER:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;WRITE_ROWS_EVENT_V2_HANDLER:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rowAfter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;DELETE_ROWS_EVENT_HANDLER:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;DELETE_ROWS_EVENT_V2_HANDLER:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;deleteByPk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rowBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;UPDATE_ROWS_EVENT_HANDLER:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;UPDATE_ROWS_EVENT_V2_HANDLER:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* before와 after의 PK값이 다를 경우 */&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;deleteByPk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rowBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rowAfter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shardIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getShardIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getReplcaeQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;executeUpdate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shardIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shardIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getShardIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getDeleteByPkQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;executeUpdate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shardIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getPrimaryKeyValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getShardIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// TODO: table과 row를 이용해 적절한 target shard 위치를 구합니다.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h6 id=&quot;replace--&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;REPLACE&lt;/code&gt;를 사용하는 이유&lt;/h6&gt;

&lt;p&gt;보통 binary log를 이용하여 복구를 할 때 각 이벤트 별로 다음과 같이 DML을 사용할 것입니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;WRITE_EVENT
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
INSERT INTO ... SET @1=[after[1]], @2=[after[2]], ...;
&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;UPDATE_EVENT
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
UPDATE ... SET @1=[after[1]], ... WHERE pk=[before.pk];
&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;DELETE_EVENT
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
DELETE FROM ... WHERE pk=[before.pk]
&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그러나 ADT를 사용할 때는 고려할 사항들이 있습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;에러 등으로 재시작할 경우 이미 처리했던 binlog event를 replay 해야하는 경우 발생함
    &lt;ul&gt;
      &lt;li&gt;ADT는 binlog event 간의 data dependency가 없다면 병렬로 처리함&lt;/li&gt;
      &lt;li&gt;나중에 발생한 이벤트가 먼저 처리될 가능성 있음&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;테이블 크롤링과 동시에 실행될 경우, 크롤러가 insert한 데이터와 충돌이 난다면?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이러한 문제들에 대해, 여기서는 모든 쓰기 작업을 덮어쓰기 정책으로 풀어냅니다.
위의 이벤트별 DML은 아래와 같이 변환할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;WRITE_EVENT&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Normal&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Query&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Overwriting&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;적용&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;의&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;값을&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;하기&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;위해서는&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;의&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;값들도&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;삭제&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Query&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;단순화&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;UPDATE_EVENT&lt;/code&gt;
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
# Normal Query
UPDATE ... SET @1=[after[1]], ... WHERE pk=[before.pk];
&lt;/code&gt;
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
# Unrolling: 삭제 후 삽입
DELETE FROM ... WHERE pk=[before.pk];
INSERT INTO ... SET @1=[after[1]], @2=[after[2]], ...;
&lt;/code&gt;
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
# Overwriting 적용: INSERT와 마찬가지로 Replace로 대체
DELETE FROM ... WHERE pk=[before.pk];
REPLACE INTO ... SET @1=[after[1]], @2=[after[2]], ...;
&lt;/code&gt;
```sql
# PK의 변화가 없다면 REPLACE만 실행해도 됨
if( isPrimaryKeyChanged(before, after) )
  DELETE FROM … WHERE pk=[before.pk];&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;REPLACE INTO … SET @1=[after[1]], @2=[after[2]], …;
```&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DELETE_EVENT&lt;/code&gt;
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
# DELETE 이벤트는 변화 없음
DELETE FROM ... WHERE pk=[before.pk]
&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;build--run&quot;&gt;Build &amp;amp; Run&lt;/h3&gt;

&lt;p&gt;빌드 및 실행 방법은 아래 링크를 참고하세요.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kakao/adt/blob/master/README_ko.md#빌드하기-build&quot;&gt;빌드하기&lt;/a&gt;
&lt;a href=&quot;https://github.com/kakao/adt/blob/master/adt-test/msr_test_script/deploy_system_test.sh&quot;&gt;(example)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kakao/adt/blob/master/README_ko.md#실행하기&quot;&gt;실행하기&lt;/a&gt;
&lt;a href=&quot;https://github.com/kakao/adt/blob/master/adt-test/msr_test_script/startup_adt_binlog_processor.sh&quot;&gt;(example)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;adt----&quot;&gt;ADT 샤드 재분배 실행 전략&lt;/h2&gt;

&lt;p&gt;이번 장에서는 위에서 만든 핸들러를 이용해, 샤드 재분배를 어떻게 할 수 있는지에 대한 내용을 다룹니다.
상황에 따라 적절한 전략이 필요합니다.
아래 나열된 방법들 외에도 여러 아이디어 있으면 공유 부탁드립니다.&lt;/p&gt;

&lt;h3 id=&quot;section-1&quot;&gt;전략 1. 순차적으로 처리&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Table Crawler를 우선 실행
    &lt;ul&gt;
      &lt;li&gt;이 때, 시작 직전 마지막 binlog file 이름을 기록해둘 것&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;크롤링 끝난 후 Binlog Processor를 실행
    &lt;ul&gt;
      &lt;li&gt;크롤링 전 기록해두었던 파일부터 시작
*단점&lt;/li&gt;
      &lt;li&gt;상대적으로 오래 걸릴 수 있음&lt;/li&gt;
      &lt;li&gt;데이터량이 너무 많은 탓에 binlog file이 rotate 되어 삭제된 후라면…?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;binlog---&quot;&gt;전략 2. 크롤링과 binlog 처리를 동시에 실행&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Binlog Processor 우선 실행&lt;/li&gt;
  &lt;li&gt;그 다음에 Table Crawler를 실행
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;INSERT IGNORE&lt;/code&gt; 쿼리 사용&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;단점
    &lt;ul&gt;
      &lt;li&gt;데이터 충돌 가능성 있음&lt;/li&gt;
      &lt;li&gt;예시의 그림은 삭제된 데이터가 target에 그대로 남아있는 상황&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/files/adt-shard-rebalancing-conflict.png&quot; alt=&quot;삭제된 데이터가 target에 남아있는 상황&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;crawler-select--lock-&quot;&gt;전략 2-1. Crawler SELECT할 때 Lock 사용&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Binlog Processor는 평소대로 실행&lt;/li&gt;
  &lt;li&gt;크롤링 시, &lt;code class=&quot;highlighter-rouge&quot;&gt;SELECT&lt;/code&gt; 대신 &lt;code class=&quot;highlighter-rouge&quot;&gt;SELECT .. FOR UPDATE&lt;/code&gt; 사용
&lt;code class=&quot;highlighter-rouge&quot;&gt;sql
SELECT ... FOR UPDATE
&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;FOR UPDATE로 인해 락이 걸린 데이터들은 수정이 불가능하여 binlog에 남지 않음&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;단점
    &lt;ul&gt;
      &lt;li&gt;Master DB 부하 증가 가능성 있음&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;history-&quot;&gt;전략 2-2. 삭제 이벤트에 대한 history 남기기&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Binlog Processor에서 삭제 이벤트 발생 시 몇 분 간 해당 내역을 어딘가에 기록을 남김&lt;/li&gt;
  &lt;li&gt;Table Crawler 핸들러에서 INSERT 직전에 삭제 기록이 있는지 확인
    &lt;ul&gt;
      &lt;li&gt;삭제 기록이 있으면 해당 행은 INSERT 무시&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;단점
    &lt;ul&gt;
      &lt;li&gt;구현이 복잡함. 삭제 기록 캐시에 대한 락 제어 필요&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;section-2&quot;&gt;전략 2-3. 2-1의 변형&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;master DB에 slave 추가&lt;/li&gt;
  &lt;li&gt;추가한 slave를 source로 하여 2-1과 같은 방법으로 실행&lt;/li&gt;
  &lt;li&gt;Table Cralwer가 끝나면 Binlog Processor만 원래의 master로부터 가져오도록 설정 변경 후 재시작&lt;/li&gt;
  &lt;li&gt;단점
    &lt;ul&gt;
      &lt;li&gt;slave를 위해 추가 머신이 필요할 수 있음&lt;/li&gt;
      &lt;li&gt;slave 추가하는데 오래 걸릴 수 있음&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;section-3&quot;&gt;데이터 검증&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;이번 장은 카카오 내부적으로도 아직 구현은 안 된, 이론 검증 단계에 있는 부분입니다.
따라서 간단하게 소개만 드리고 다음 기회에 좀 더 자세한 내용으로 다시 찾아뵙도록 하겠습니다.
적극적인 의견/비판은 언제든 환영입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;위에서 구현한 핸들러가 제대로 작동하는지 확인하기 위해 단위 테스트, 통합 테스트 등도 필요하지만 실사용을 하다보면 미처 테스트에서 검증하지 못한 여러 문제가 발생하기도 합니다. 이러한 문제를 탐지하기 위해 이번 장에서는 샤드 재분배가 올바르게 진행되었는지를 검증하는 방법에 대해 지금까지 고민해온 내용들을 공유하고자 합니다.&lt;/p&gt;

&lt;h3 id=&quot;section-4&quot;&gt;전수 조사&lt;/h3&gt;

&lt;p&gt;데이터의 일부만 비교하여 일정 신뢰도 이상이면 문제가 없는 것으로 간주할지, 아니면 전체 데이터를 조사해야할지부터 고민이 시작되었습니다. 데이터가 너무 많아서 전수조사가 힘든 경우도 있기 때문입니다. 하지만 사용자들의 소중한 데이터를 다루는 것에 있어, 100% 외에는 있을 수 없다는 판단에 전수 조사를 택하기로 했습니다.&lt;/p&gt;

&lt;h3 id=&quot;source-db----&quot;&gt;검증 방안 1. Source DB 데이터 변화가 없을 경우&lt;/h3&gt;

&lt;p&gt;첫번째 방법은 source DB의 데이터 변화가 없을 경우에 가능한 방법입니다. 이 방법이 가능한 상황은 서비스 중단 상태(점검 등의 이유), source DB 자체가 다른 DB의 slave이면서 동시에 replication이 중단된 상태 등이 있습니다. 후자의 경우를 예로 들면 다음과 같이 검증이 가능합니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;상황 가정
    &lt;ul&gt;
      &lt;li&gt;MySQL Server A, B, C1, C2가 있음&lt;/li&gt;
      &lt;li&gt;B는 A의 slave&lt;/li&gt;
      &lt;li&gt;ADT를 이용해 B에서 C1, C2로 데이터 재분배&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;B에서 C로 Table crawler가 완전히 끝날 때까지 대기&lt;/li&gt;
  &lt;li&gt;크롤링 끝난 후 B에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;SLAVE STOP&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;B의 binlog가 C로 완전히 반영될 때까지 대기&lt;/li&gt;
  &lt;li&gt;Binlog Processor도 더 이상 처리할 것이 없으면 B와 C의 데이터를 1:1로 모두 비교&lt;/li&gt;
  &lt;li&gt;전수 조사가 끝나면 Binlog Processor만 중단했던 부분부터 다시 시작&lt;/li&gt;
  &lt;li&gt;B에서 slave 재시작&lt;/li&gt;
  &lt;li&gt;Binlog Processor가 잘 작동하는지 확인하기 위해 주기적으로 3번부터 다시 반복&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 방법은 서비스 영향없이 데이터 검증이 가능하다는 장점입니다.&lt;/p&gt;

&lt;h3 id=&quot;source-db-----1&quot;&gt;검증 방안 2. Source DB의 데이터가 실시간 변경되는 경우&lt;/h3&gt;

&lt;p&gt;source DB 데이터가 끊임없이 바뀌는 중에 검증할 수 있는 방법입니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;다음과 같은 상황이라고 가정
    &lt;ul&gt;
      &lt;li&gt;MySQL Server A, B1, B2가 있음&lt;/li&gt;
      &lt;li&gt;ADT를 이용해 A의 데이터를 B1, B2로 재분배&lt;/li&gt;
      &lt;li&gt;Table Crawler가 끝난 후, Binlog Processor만 작동 중인 상태&lt;/li&gt;
      &lt;li&gt;A의 Binlog도 대부분 B에 반영되어 있는 상태&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;A, B로부터 PK 기준 같은 범위의 값들을 &lt;code class=&quot;highlighter-rouge&quot;&gt;SELECT&lt;/code&gt;하여 일치하는지 비교&lt;/li&gt;
  &lt;li&gt;일치하면 다음 범위로 넘어가서 2번의 방법 반복
    &lt;ul&gt;
      &lt;li&gt;처음에 0 이상 100 미만을 SELECT 했으면 그 다음에는 100 이상 200 미만, 200 이상 300 미만, …&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;일치하지 않으면 잠시 sleep&lt;/li&gt;
  &lt;li&gt;sleep 끝난 후, 같은 범위의 PK로 2번 과정 재시도&lt;/li&gt;
  &lt;li&gt;재시도를 할 때마다 B값이 계속 바뀔 수 있음
    &lt;ul&gt;
      &lt;li&gt;A에서 B로 데이터가 복사되고 있는 중으로 볼 수 있음&lt;/li&gt;
      &lt;li&gt;이런 상황은 재시도 횟수에 포함시키지 않는 것이 적절할 것으로 판단&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;재시도를 몇 회 시도했음에도 B의 데이터는 변화가 없고 일치하지 않으면 문제가 있는 것으로 간주&lt;/li&gt;
  &lt;li&gt;주기적으로 위의 과정들을 반복해서 검증 가능&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 방법의 특징은 실제 서비스되고 있는 데이터를 기준으로 검증을 할 수 있다는 점입니다.&lt;/p&gt;

&lt;p&gt;위의 방법들 외 다른 아이디어, 혹은 의견 등등 언제든 환영입니다.&lt;/p&gt;

&lt;h2 id=&quot;section-5&quot;&gt;맺음말&lt;/h2&gt;

&lt;p&gt;지금까지 ADT를 활용하여 MySQL Shard 데이터 재분배하는 방법에 대해 알아봤습니다.
아직 카카오 내부적으로 검증 단계인 부분도 있지만 많은 분들께 도움이 되길 바랍니다.
더불어 같이 고민이나 의견을 공유하는 것도 언제든 환영입니다.^^&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;커버 이미지 출처: &lt;a href=&quot;https://flic.kr/p/oZgmPi&quot;&gt;The Shard&lt;/a&gt; © &lt;a href=&quot;https://www.flickr.com/photos/davidedamico/&quot;&gt;Davide D’Amico&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;서버 아이콘 출처: https://icons8.com/web-app/1339/server&lt;/li&gt;
  &lt;li&gt;데이터베이스 아이콘 출처: http://www.seaicons.com/database-icon/&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;special thanks to &lt;a href=&quot;http://gywn.net&quot;&gt;성동찬&lt;/a&gt; (한국카카오 카카오뱅크)&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Fri, 01 Jul 2016 16:48:00 +0900</pubDate>
        <link>http://tech.kakao.com/2016/07/01/adt-mysql-shard-rebalancing/</link>
        <guid isPermaLink="true">http://tech.kakao.com/2016/07/01/adt-mysql-shard-rebalancing/</guid>
        
        <category>opensource</category>
        
        <category>almighty-data-transmitter</category>
        
        <category>adt</category>
        
        <category>mysql</category>
        
        <category>sharding</category>
        
        <category>devops</category>
        
        
      </item>
    
  </channel>
</rss>
