2014-05-23

[ZooKeeper] (1) ZNode - ZooKeeper가 data를 저장하는 방법.

 지난번 글에서 ZooKeeper는 일종의 파일시스템을 제공해주어 이를 이용하여 semaphore나 mutex를 만들어 사용할 수 있다고 말했다.
 이때 ZooKeeper가 제공해주는 파일시스템에 저장되는 파일 하나하나를 znode라고 부른다.
 이번에는 znode에 대해서 자세히 설명해보도록 하겠다.

ZNode의 특징

hierarchy

 znode는 unix-like 시스템에서 쓰이는 file system처럼 node 간에 hierarchy namespace를 가지고, 이를 /(slash)를 이용하여 구분한다.
http://zookeeper.apache.org/doc/r3.3.2/zookeeperOver.html

 일반적인 file system과 다른 부분이 있다. ZooKeeper는 file과 directory의 구분이 없이 znode라는 것 하나만을 제공한다. 즉, directory에도 내용을 적을 수 있는, directory와 file 간의 구분이 없는 file system이라는 것이다.
 이는 znode의 큰 특징 중 하나이다. namespace hierarchy를 가지기 때문에 관련 있는 일들을 눈에 보이는 하나의 묶음으로 관리할 수 있으면서, directory가 내용을 가질 수 있게 함으로써(혹은 file 간에 hierarchy를 가진다고 하기도 한다. 원하는 표현으로 말하면 된다.) redundunt한 file을 생성해야 하는 것을 막을 수 있다.

size 제한

 ZooKeeper는 모든 data를 메모리에 저장한다.
 data를 메모리에 저장하기 때문에 jvm의 heap memory를 모든 znode를 올릴 수 있는 충분한 크기로 만들어야 한다.

 심지어 The disk is death to ZooKeeper.라고 말하면서, JVM이 heap memory를 swap 하여 하드에 저장하는 것을 피하도록 설정하는 것을 강요(?)하고 있다.

 data를 저장하는 보통의 파일 시스템이나 DBMS같은 경우 모든 data가 메모리에 올라갈 수 있는 크기로 제한된다는 제약조건은 말도 안 되는 조건이라고 할 것이다.
 하지만 znode의 목적은 data를 저장하는 것이 아니라, distributed 된 시스템 간의 조정을 하기 위함이다. 따라서 znode에는 조정에 필요한 meta data만을 저장하는 것이 기본적은 사용법이고, znode 자체도 크기가 작은 data를 저장할 것이라고 가정하고 구현되어 있기 때문에 각 znode의 크기는 1MB로 제한된다.


Recovery

 ZooKeeper설정파일을 봤으면 모든 data를 메모리에 올린다는 설명이 이상하게 느껴질 것이다. ZooKeeper의 설정파일에는 dataDir을 설정할 수 있게 되어 있다.
 그렇다면 dataDir은 무엇을 위한 것일까?

 dataDir은 zookeeper의 recovery를 위해 사용된다.
 ZooKeeper는 모든 data를 메모리에 들고 있기 때문에 서버가 종료되었다가 재 시작했을 때 자료를 보존할 수 없다. 이때 원래의 자료를 복구할 수 있는 것을 보장하기 위하여 ZooKeeper는 모든 transaction log를 dataDir에 저장한다.
 zookeeper를 재 시작하면 dataDir에서 transaction log를 읽어와서 모든 트랜잭션을 다시 실행하여 data를 복구한다.

 하지만 언제나 transaction log만을 이용하여 자료를 복구한다면, 자료를 복구하는데 시간이 걸리기도 하고, 무엇보다도 로그가 쌓일수록 복구할 자료의 양에 비해서 로그의 크기가 커지는 문제가 생긴다.
 이를 해결하기 위하여 ZooKeeper는 transaction log가 일정 이상이 되면, 지금까지 쌓인 transaction log로 만들 수 있는 data를 하드에 저장하고 transaction log를 지운다. 이 data를 snapshot이라고 하는데, 다음에 복구할 일이 생기면 이 snapshot에서부터 자료를 읽어온 뒤, 추가로 쌓인 transaction log만을 실행시켜 자료를 복구한다.

ZNode의 종류

 znode를 생성할 때 2종류의 옵션을 줄 수 있다.
 하나는 life cycle에 관한 option으로 persistent인지 ephemeral인지를 설정하는 것이고, 다른 하나는 znode의 uniqueness에 관한 option으로 sequential node인지 아닌지를 설정하는 것이다.

Persistent mode와 Ephemeral mode

 Persistent mode와 Ephemeral mode는 znode의 life cycle에 관한 설정으로, 모든 znode는 persistent 하거나 ephemeral 하지만 동시에 둘 다일 수는 없다.

 Persistent mode로 생성된 znode는 명시적으로 삭제될 때까지 지워지지 않는, 우리가 일반적으로 생각하는 file과 같다.
 그렇다면 Ephemeral mode는 어떻게 동작할까?
 Ephemeral mode로 생성된 znode는 ZooKeeper서버와 znode를 생성하도록 요청한 클라이언트 사이의 connection이 종료되면 자동으로 지워진다. ephemeral node의 이런 특성을 이용하여 lock이나 leader election을 구현하기도 한다.

Sequence mode

 Sequence mode는 znode의 uniqueness를 보장하기 위한 것이다.
 sequence mode로 만들어진 znode는 주어진 이름 뒤에 int 범위의 10개의 숫자가 postfix로 붙는다.
 이 숫자는 atomic 하게 증가하여 같은 이름으로 만든 node라고 해도 서로 다른 이름의 znode로 만들어준다.1)

 ephemeral mode와 persistent mode 둘 다 sequence node로 만들 수 있다.
 하지만 ephemeral mode와 sequence mode를 동시에 사용하여 node를 생성하는 것은 문제가 생길 수 있다. ZooKeeper서버와 ZooKeeper클라이언트는 비동기적으로 동작할 뿐 아니라 둘 사이의 connection은 빈번하게 끊길 수 있다.
 따라서 ephemeral + sequence node를 생성하라고 요청하였을 때 성공인지 실패인지 응답이 와야 하지만 실제로는 응답이 오지 않고 timeout이 발생할 수 있다.
 이때 성공인지 실패인지 알기 위해서는 node가 생성되었는지 확인해야 하는데 생성된 znode의 이름을 서버에서 결정하기 때문에 클라이언트는 znode의 생성 여부를 알 방법이 없다.
 ZooKeeper를 사용하기 쉽게 해주는 curator라는 라이브러리에서는 이를 위해 protect mode라는 것을 도입하였다. 이에 대해서는 다음 기회에 curator를 설명할 기회가 있으면 더 자세히 다루도록 하겠다.

ACL (Access Control List)

 znode는 ACL(Access control list)을 이용하여 각각의 znode에 접근권한을 설정할 수 있다.

 하지만 unix-like 파일시스템과 다르게 znode에는 user/group/others라는 개념이 존재하지 않는다. 대신
 하지만 차이가 있는
 ACL은 permission과 scheme
 이때 조심해야 하는 것이 있다. 각 znode의 ACL은 자기 자신의 ACL이고, 자식들에게 recursive 하게 적용되지 않는다는 것이다.
 즉, /some-node에 권한을 설정하였다고 해도 /some-node/child에는 권한이 설정되지 않는다는 것이다.

ACL Permissions

 ZooKeeper에서의 권한은 unix-like 파일시스템의 권한과 크게 다를 것은 없다.
 특정 권한들에 대해 allow flag가 있어서 이것이 어떻게 설정되는가에 따라서 해당 권한을 실행시킬 수 있는지가 결정된다.

 ZooKeeper에서 설정할 수 있는 ACL의 종류는 아래의 5가지이다.
  1. CREATE : 해당 znode의 자식 node를 만들 수 있는 권한.
  2. READ : 해당 znode에서 data를 읽고 와 그 자식들의 목록을 읽을 수 있는 권한.
  3. WRITE : 해당 znode에 값을 쓸 수 있는 권한.
  4. DELETE : 해당 znode의 자식들을 지울 수 있는 권한.
  5. ADMIN : 해당 znode에 권한을 설정할 수 있는 권한.
 unix-like file system과 다른 부분이 2가지 있다.
 첫 번째는 보통의 file system에는 없는 CREATE와 DELETE라는 권한이 존재하여 자식 node를 생성하고 삭제할 수 있는 권한이 있다는 것이다.
 unix-like file system에서 directory는 실제로는 자기 자식의 list를 가지고 있는 file이다.
 그래서 자식을 만들고 지우는 것은 부모 directory에 내용을 변경하는 것이고, 부모 directory에 쓰기 권한이 있는지가 자식을 만들고 지우는 권한이 된다.
 하지만 ZooKeeper에서는 모든 znode가 directory이기도 하고, file이기도 해서 자기 자신에 대한 쓰기 권한과 자식 node에 대한 생성/삭제 권한을 같이 쓸 수 없다.

Schemes

 ZooKeeper는 unix-like 시스템과 다르게 각 znode에 user/group/others라는 개념이 존재하지 않는다.
 대신 scheme이라는 것을 이용하여 권한을 구분하게 되어 있다.
 built in으로 제공되는 설정할 수 있는 scheme은 아래와 같이 4가지가 있다.

  1. WORLD
  2. AUTH
  3. DIGEST
  4. IP
 WORLD는 모든 요청에 대해 허락하는 것이고, AUTH는 authenticated된 session에서 들어오는 요청에 대해서만 허락하는 것이다.
 DIGEST는 username과 password를 보내서 이를 이용하여 만든 MD5 hash값이 같은 요청에 대해서만 처리하는 것이고, IP는 해당 IP에서의 요청만을 처리하도록 하는 것이다.

Stat

 ZNode는 node와 node의 data에 관한 여러 정보를 들고 있고, 이것을 stat이라고 부른다. stat이 가지는 정보는 다음과 같다.
  • czxid : znode를 생성한 트랜잭션의 id
  • mzxid : znode를 마지막으로 수정 트랜잭션의 id
  • ctime : znode가 생성됐을 때의 시스템 시간
  • mtime : znode가 마지막으로 변경되었을 때의 시스템 시간
  • version : znode가 변경된 횟수
  • cversion : znode의 자식 node를 수정한 횟수
  • aversion : ACL 정책을 수정한 횟수
  • ephemeralOwner : 임시 노드인지에 대한 flag
  • dataLength : data의 길이
  • numChildren : 자식 node의 수

1) 이 숫자의 범위는 int 범위로 최대 2147483647까지 올라가고 그보다 커지면 overflow가 발생한다. 문서에는 overflow가 발생했을 때 -2147483647가 된다고 적혀 있지만, 실제로는 -2147483648이다. 이는 단순한 실수로 보인다.

2014-05-13

[ZooKeeper] (0) zookeepr는 무엇인가?

 보통 분산 시스템을 구현할 때, 모든 시스템이 완전히 독립적으로 돌아가는 시스템이 아니라면, 시스템 간의 락, 설정 공유, 리더 선출, atomic 한 연산 등을 구현하는 것이 필요하지만, 분산환경에서 이를 구현하는 것은 매우 어려운 일이다.
 위의 기능들을 구현하기 어렵기 때문에 보통은 apache에서 제작한 zookeeper라는 시스템을 이용하여 분산 시스템 간의 동기화된 작업을 구현한다.
 zookeeper는 위의 기능들을 직접적으로 제공하지는 않지만 이런 일들을 하기 쉽게 해주는 환경을 제공한다.

 zookeepr가 제공해주는 환경이라는 것은 일종의 공유 가능한 file system을 제공해준다. 그러면 사용자가 file을 이용해서 semaphoremutex를 구현하듯이 zookeeper를 이용해서 semaphore나 mutex등을 구현하여 사용하면 된다.

 zookeeper는 분산환경에서의 다음과 같은 특징을 보장해준다.

ZooKeeper의 특징

Atomicity

 zookeeper에서 data의 저장은 원자성을 가진다.
 즉, node를 만들건 node에 data를 update하든 해당 request는 완벽하게 처리되거나 처리되지 않거나 하지 그 중간의 어중간한 상태는 존재하지 않는다.

Consistency

 분산환경에서, 특히나 data를 copy하여 여러 서버에 저장하면서 strong consistency를 보장하는 것은 매우 어려운 일다. 그래서 zookeeper에서는 아래의 2가지 consistency를 보장한다.


 첫 번째는 sequential consistency다.
 즉, 모든 요청은 들어온 순서대로 처리되고, 모든 서버가 요청을 같은 순서로 처리하는 것을 보장하는 것이다.


 두 번째는 eventual consistency다.
 strong consistency와 달리, 모든 요청에 대해 모든 서버가 완벽히 같은 순간에 같은 값을 갖지는 않지만 결국에는 같은 값을 가질 것을 보장하는 것이다.
 즉, 어떤 서버에서는 특정 request가 실행됐지만 다른 서버에서는 실행되지 않는 일은 없다는 것이다.

Durability

 zookeeper는 강한 durability를 보장한다.
 zookeeper에서 data 그 자체는 언제나 메모리에만 존재한다. 하지만 서버의 로컬디스크에 트랜잭션 로그와 스냅 샷을 저장하기 때문에 메모리가 날아가도 트랜잭션 로그와 스냅 샷을 이용하여 data를 복구할 수 있다.


 zookeeper가 어떻게 구현되어서 위의 특징들을 만족시키는지는 다음 기회에 설명하도록 하겠다

2014-05-06

config_script 재작성

 이 컴퓨터 저 컴퓨터 옮겨가면서 작업할 때, 컴퓨터별로 환경을 통일하는 작업은 귀찮은 일이다. 그래서 나는 이런 스크립트를 만들어서 써왔다.
 아직 사용하는데 큰 문제는 없었지만, 그때그때 필요한 기능들을 넣다 보니 스크립트가 난잡해지고, 내가 다른 프로젝트에서 작업할 때 자주 사용하지 않는 bash를 이용해서 만들다 보니 수정이 필요해서 코드를 다시 볼 때, 익숙하지 않아서 실수하는 문제가 생겼다.
 그래서 이번 연휴를 맞아서 스크립트를 완전히 새로 작성하였다.

 이번에 작업에서 초점을 맞춘 부분은 크게 2가지였다.
 첫 번째는 시간이 지나도 알아보기 쉽도록 익숙한 언어로 작업할 것, 두 번째는 수정이 필요하거나 새로운 일이 추가될 때 확장하기 쉽도록 하는 것이었다.

 첫 번째 요구사항을 맞추기 위하여 작업은 python을 이용하였다.
 어차피 하는 대부분 작업이 조건에 맞추어 파일을 옮기고 command를 실행시키는 일들뿐이고 복잡한 일은 없어서 언어는 무엇을 사용해도 상관없어 보였기에 그중에 가장 익숙한 python을 이용했다.

 두 번째 요구사항을 맞추기 위하여 기존의 스크립트를 분석해 보니 대부분의 일이
  1. 본 작업에 앞서 사전작업을 한다.
  2. 작업해야 할 파일 리스트에서 하나씩 꺼내서
    1. source가 있는지 파악하여 에러처리를 하고
    2. destination에 이미 파일이 존재하는지 확인하여 에러처리를 하고
    3. source를 destination으로 복사/링크 등을 한다.
  3. 본 작업이 끝난 뒤 마무리 작업을 한다.
의 순서로 진행되었다.
 그래서 Config라는 abstract class를 만들고, 각각의 단계에 맞춰서
  1. pre()

    1. source_exists()
    2. resolve_conflict()
    3. do()
  2. post()
의 5개의 abstract method를 만들어 원하는 작업마다 Config를 상속받는 class를 만들어 상황에 맞게 위의 method들을 override 하는 방식으로 작업을 진행하였다.
 또한, source_exists/resolve_conflict/do 함수 안에서 공통으로 쓰이는 함수들인 소스의 경로를 알아오는 source_dir/source_path, 목적지의 정보를 알아오는 destination_dir/destination_path, conflict 시 backup 할 경로를 알아오는 backup_path를 override 가능한 함수로 만들었다. 이렇게 해서 특정 경로에서 특정 경로로 symbolic link를 만드는 작업을 할 때는 경로만 재정의하여 사용할 수 있도록 하였다.
 그래서 나온 결과물의 class hierarchy는 위와 같다.

 다음 작업으로 고려하고 있는 것은 맥이나 윈도에서의 환경세팅과 rvm/npm/pip/virtualenv 등 작업에 필요한 일부 패키지들을 직접 컴파일해서 사용할 수 있도록 하는 것이다.
 하지만 당장은 하지 않을 것 같고 조만간에 시간이 나면 더 작업할 것 같다.

2014-05-04

왜 Triple buffering을 사용하는가

 지난번 글에서 double bufferingVSync에 관하여 설명하였다.
 이번 글에서는 VSync가 가지는 문제와 그것을 어떻게 triple buffering이 해결하는지를 적어보도록 하겠다.

 우선 다음 문제를 풀어보자.
vertical interval이 16.6ms마다 있는 display를 사용하고
double buffering을 사용하는 드라이버에서 VSync를 사용한다고 했을 때
back buffer에서 scene 하나를 완성하는 데 걸리는 시간이 16ms인 프로그램을 작성하면
이 프로그램은 몇 fps가 나올 것인가?
 매 frame마다 하나의 scene을 16ms 동안 그리고, 남은 0.6ms는 VSync를 기다리기 때문에 16.6ms마다 한 frame을 그려서 60fps가 나온다.
 VSync를 기다리는 0.6ms가 아쉽기는 하지만 어차피 60fps 이상은 사람 눈으로 잘 구분이 안 되니 큰 손해를 보는 것은 아니다.

 문제는 아래의 상황이다.
vertical interval이 16.6ms마다 있는 display를 사용하고
double buffering을 사용하는 드라이버에서 VSync를 사용한다고 했을 때
back buffer에서 scene 하나를 완성하는 데 걸리는 시간이
홀수 번째 scene은 15ms, 짝수 번째 scene은 17ms가 걸리도록 프로그램을 작성하면
이 프로그램은 몇 fps가 나올 것인가?
 매 프레임 back buffer에 그리는 데 걸리는 평균 시간은 첫 번째 경우와 같다.
 그렇다면 이 케이스에서도 60 fps가 나올까?
 아니다. 홀수 번째 frame에서는 15ms 동안 그림을 그리고 1.6ms 동안 기다리지만, 짝수 번째 frame에서는 back buffer에 그림을 그리는데 17ms가 걸렸기 때문에 첫 번째 vertical interval 때 swap buffer를 하지 못하고, 그 다음 vertical interval까지 기다려야 한다. 즉, 그림이 완성된 뒤 16.2ms만큼 더 기다려야 화면을 바꿀 수 있다는 것이다.
 이렇게 되면 frame 2개를 그리는데 3번의 vertical interval이 필요하므로, 약 40fps가 나오게 된다. 그림을 그리는 데 걸리는 평균 시간이 같은데도 성능은 2/3로 떨어지게 되는 것이다.

 아래의 상황도 한번 보자.
vertical interval이 16.6ms마다 있는 display를 사용하고
double buffering을 사용하는 드라이버에서 VSync를 사용한다고 했을 때
back buffer에서 scene 하나를 완성하는 데 걸리는 시간이 17ms인 프로그램을 작성하면
이 프로그램은 몇 fps가 나올 것인가?
 첫 번째 상황에 비해 scene 하나를 그리는 데 걸리는 시간이 고작 1ms 늘었을 뿐이다.
 하지만 실제로 성능은 하나의 scene을 그리는데 2번의 vertical interval이 필요로 하므로, 원래 성능의 절반 정도인 30fps가 나오게 된다.

 그러면 무조건 vertical interval 사이에 한 frame을 그리도록 프로그램을 작성해서 문제를 해결하면 안 될까?
 음.... 가능하면 그렇게 하는 것이 좋다.
 하지만 프로그램의 실행 시간이라는 것은 기본적으로 환경에 많은 영향을 받기 때문에 예측하기 어렵다. 가능하면 이 현상(이 현상에 이름이 있는지는 모르겠다.)의 근본적인 원인을 해결하는 것이 좋다. 그래서 해결책으로 나온 것이 Triple buffering이다.

Triple buffering은 기본적으로 Double buffering이랑 같지만 2개의 back buffer와 front buffer, 총 3개의 buffer를 사용한다.
 Triple buffering이 어떻게 문제를 해결하는지 이해하기 쉽게 해주는 다음 그림을 보자. 아래는 copy를 하는 경우만 나와 있지만, vsync를 사용한다면 copy든 flip이든 vertical interval 동안만 update가 가능하므로 flip과 큰 차이는 없다.
http://en.wikipedia.org/wiki/Multiple_buffering
 위 그림의 4번 시나리오는 Double buffering과 vsync를 사용하는 경우 중 한 scene을 그리는 시간이 한 frame rate보다 길면 어떻게 되는가를 보여주는 자료다.
 draw B가 끝나는 부분을 자세히 보자. 아쉽게도 draw B는 한 프레임을 약간 넘어가 vertical interval 동안 화면을 갱신하지 못하게 되었다. 이렇게 타이밍을 놓친 scene은 그다음 vertical interval이 되어야 화면에 보일 수 있다. 그전까지는 여전히 draw A에서 그렸던 장면이 화면에 남아 있게 된다. 이것이 위에서 말했던 성능 저하의 원인이다.

 다시 위 그림에서 5번 시나리오를 보자. 이건 4번과 똑같은 일을 triple buffering을 사용하여 처리한 것이다.
 double buffer에서는 draw Abuffer 1에서 video memory로 옮겨진 뒤 Vertical interval이 될 때까지 draw B가 시작하지 못했지만, triple buffering을 사용하는 경우 여분의 buffer가 하나 더 있기 때문에 draw B를 바로 시작할 수 있다.

 이렇게 식으로 triple buffering을 이용하면 VSync로 인한 성능 저하를 줄일 수 있다.
 하지만 surface만큼의 메모리를 더 사용해야 하는 단점이 있어서 아직 대부분의 그래픽 카드에서 기본으로 지원하는 기능은 아니다.
 하지만 새로 나오는 그래픽 카드들에는 그래픽 카드에서 지원하는 기능으로 들어가기 시작했고, 지원 하지 않는 그래픽 카드에서는 FBO를 이용하여 구현할 수 있다.

2014-05-03

copy와 flip - Double buffering의 2가지 기법

 Buffer는 그림을 그리기 위해 메모리에 잡아놓은 영역을 의미한다.
 그래픽 드라이버가 화면을 갱신해야 할 때 이 영역에서 그림을 읽어 화면을 그린다.

 과거의 graphic card(혹은 요새 나오는 저 사양의 embedded 용 graphic card)에서는 single buffering이라는 기법을 이용한다.
 single buffering이란, 말 그대로 한 개의 버퍼만을 사용하는 방식이다. 완성된 그림을 buffer에 그리고, 화면에 그림을 그려야 할 때마다 이 buffer에서 화면으로 그림을 가져가는 것이다.
 가장 기본적인 구현이지만, 이런 방식은 buffer에서 화면으로 가져가는 동안 buffer를 업데이트하지 못한다는 단점이 있다.
 이런 단점을 해결하기 위하여 double buffering이라는 기법을 사용한다.

Double buffering

 Double buffering에서는 front buffer와 back buffer라고 불리는 2개의 buffer를 사용한다. back buffer에 그림을 그리고 한 프레임이 완성되면 back buffer와 front buffer를 바꾼다. (이를 swap buffer라고 한다.) 화면에 그림이 필요할 때는 언제나 front buffer에서 화면을 가져간다.
 화면이 읽어들이는 buffer는 언제나 front buffer이므로 화면을 업데이트할 필요가 있으면 언제나 back buffer에 그리면 된다.
 이때 back buffer에서 front buffer로 옮기는 방식에는 2가지 방법이 있다.

Copy

 첫 번째 방법은 copy라고 부르는 방법이다. 이 방법은 front buffer는 그래픽 메모리에 만든다. back buffer는 그래픽 메모리나 시스템 메모리 어디에 만들어도 상관없지만, 보통 copy를 사용하는 경우는 그래픽 메모리를 아끼기 위한 경우가 많아서 back buffer는 시스템 메모리에 잡는다.
 프로그램이 그림을 그리라고 하면 이를 back buffer에 그리고, swap buffer를 하게 되면 back buffer에 있는 내용을 front buffer로 복사해 가게 된다.
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
 전 frame에 그렸던 그림을 그대로 유지할 수 있어 화면 일부분만을 업데이트하는 경우에는 효율적이다. 하지만 대부분의 3d 엔진은 buffer를 지우고 처음부터 다시 그리기 때문에 이런 방식은 비효율적이다.
 게다가 copy를 사용하는 중에 화면을 업데이트하는 중에 copy가 발생하면 tearing 현상이 발생한다.

Tearing

 Buffer란 기본적으로 memory이기 때문에 차례대로 접근할 수밖에 없다. 따라서 back buffer에서 front buffer로 그림을 복사할 때 위에서부터 순서대로 그림을 그려야 한다. 이 때문에 화면의 일부는 이전 frame을 그리고 나머지는 현재의 frame을 그리는 현상이 생기게 된다.
 그렇게 되면 아래와 같이 찢어지는 화면이 보이게 되는데 그것을 tearing이라고 한다.
http://en.wikipedia.org/wiki/Screen_tearing

Page flipping

 front buffer에 화면을 업데이트하는 순간 전 frame과 새 frame이 겹치는 순간이 있는 것이 문제라면, 한순간에 front buffer를 완성할 수 는 없을까?
 그래서 나온 방법이 Page flipping이라는 방법이다.
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
 Flip은 back buffer와 front buffer를 둘 다 그래픽 메모리에 만들어 놓고, display가 가리키는 buffer를 바꾸는 방식이다.
 이렇게 하면 front buffer에 old frame이 남아 있는 경우는 없다.
 게다가 대부분의 3D 엔진들이 동작하는 방법이 화면을 전부 지우고 새롭게 그림을 그리는 방식이기 때문에 대부분의 경우에 이전 그림을 유지하고 있을 필요도 없고(필요하면 front buffer에서 읽어서 back buffer에 복사하기는 한다), 복사하는 것보다는 포인터만 바꾸는 것이 시간이 조금 걸리기 때문에 더 빠르다.

 flip을 사용하면 front buffer에 이 전 frame과 현재의 frame이 동시에 존재하는 일은 없다. 하지만 flip을 사용해도 tearing은 발생한다. display의 구현 때문이다.
 display는 매 순간 front buffer에서 데이터를 읽어서 화면을 갱신시킨다. 문제는 순식간에 화면을 전부 갱신시키는 것이 아니라 위에서부터 순서대로 갱신시키기 때문이다. 그래서 화면을 갱신하는 중에 flip이 발생하면 old frame과 새 frame이 동시에 나오는 일이 생기게 된다.

 이런 문제를 해결하기 위해 Vertical synchronization이라는 방법을 사용한다.

Vertical Synchronization

 과거 빔을 사용하는 CRT 모니터를 사용하던 시절에는 오른쪽 아래까지 빔을 이용하여 그림을 그리고 나면, 빔을 왼쪽 위로 돌려보내는 시간이 필요로 했다. 이 시간을 Vertical Blanking Interval 혹은 vertical interval이라고 하는데 CRT 모니터를 사용하지 않는 지금도 프로토콜의 하위 호환성을 위하여 데이터를 보내는 VBI를 둔다. 이 interval 동안은 화면을 갱신하지 못하기 때문에 이 기간에 flip이나 copy를 하여 buffer를 갱신한다면 tearing 현상은 발생하지 않는다.
 물론 VSync에도 단점이 없는 것은 아니다. swap buffer를 언제나 vertical interval에만 하므로 frame의 최소 시간이 vertical interval 이하가 되지 못한다.
 하지만 사람의 눈은 보통 일정 이상의 fps를 인식하지 못하고, display의 interval은 보통 그 한계 근처에서 설정되어 있기 때문에 큰 문제는 안 된다.