20131214

jQuery 1.10 vs 1.11 호환성 체크

서비스 jQuery 1.10.2 → 1.11.0 올려도 되나 싶어 체크한 기록. 결론부터: 거의 문제없음. 다만 자체 플러그인 두 개를 손봐야 했다.

먼저 라인 선택 배경. 2.x로 못 가는 이유는 IE8. 회사 고객 중 공공/대기업 쪽에 IE8이 아직 6% 남아있어서 1.x 유지는 강제. 1.11은 bugfix 중심이고 API 브레이킹이 없다고 공식 changelog에 박혀 있다. 2.1은 기능적으로는 1.11과 같지만 IE<9 지원 코드가 제거된 버전. 같은 릴리스 사이클로 번호만 다르게 간다고 보면 됨.

실제 체크한 항목.

  • $.ajaxstatusCode 콜백 분기 — 동일
  • $.Deferred + .then 체이닝 — 동일. 1.8에 있던 then 리턴값 체이닝 버그는 이미 고쳐진 상태
  • 이벤트 delegation .on("click", "a.btn", fn) — OK
  • 오래된 $.browser 사용 플러그인(slimScroll 0.4) — 여전히 미동작. 1.9부터 제거된 API라 그대로
  • $.live / $.die — 1.9에서 이미 제거. 영향 없음
  • .attr("checked") vs .prop("checked") — 폼 체크박스 상태는 반드시 prop. 1.6 이후 고정인데 코드 리뷰하다 아직 attr 쓰는 부분 발견해서 같이 정리

걸린 건 자체 제작한 IE7 호환 플러그인 두 개. 하나가 $.support.boxModel을 참조했는데 이게 1.8에서 이미 deprecated, 1.9부터 빠진 프로퍼티다. 어쩌다 아직 살아있었는지 모르겠는데 1.10에서는 undefined라 기본값 true로 동작하다가 1.11 올리고 minify된 번들에서 조건문 분기가 꼬였다.

// 예전
if (!$.support.boxModel) {
    el.width(el.width() + padLR + borderLR);
}

// 요즘은 document.compatMode로
// Quirks 모드일 때만 IE5 식 box-sizing이 적용됨
if (document.compatMode !== "CSS1Compat") {
    el.width(el.width() + padLR + borderLR);
}

그리고 또 하나. 커스텀 autocomplete에서 keydown 이벤트를 받는데 이벤트 객체의 which 대신 keyCode를 직접 읽던 부분이 있었다. 1.11에서 normalization 타이밍이 아주 살짝 달라져서 특정 IME 조합 상태일 때 한 번씩 keyCode가 0으로 들어오는 경우를 봤다. e.which로 바꾸니 일관성 회복.

minified 용량도 체크. 1.10.2가 92.6KB, 1.11.0이 93.5KB. CDN hit율(jQuery CDN) 올라가니까 자체 호스팅에서 CDN으로 전환하는 것까지 한 번에 해버림.

<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script>
  // fallback
  window.jQuery || document.write(
    '<script src="/assets/js/jquery-1.11.0.min.js"><\/script>'
  );
</script>

protocol-relative URL(//) 쓰는 이유는 https 페이지에서 mixed content 방지. fallback은 CDN 못 뜨면 로컬 파일. 이거 없으면 한번씩 죽는다.

테스트 돌려 별 이슈 없으면 다음주 화요일 새벽 배포 예정. prod 반영 후 에러 집계 보고 판단.

20131209

MySQL my.cnf innodb_buffer_pool_size 튜닝 메모

DB 서버 메모리 32GB인데 innodb_buffer_pool_size가 2G로 잡혀 있었다. 전임자가 그냥 기본값에서 조금만 올려둔거 그대로 썼다가 느리다고 한참 까였는데 범인이 이거였음. my.cnf 튜닝 메모.

[mysqld]
innodb_buffer_pool_size           = 24G
innodb_buffer_pool_instances      = 8
innodb_log_file_size              = 512M
innodb_log_buffer_size            = 16M
innodb_flush_method               = O_DIRECT
innodb_flush_log_at_trx_commit    = 2
innodb_file_per_table             = 1
innodb_io_capacity                = 2000
innodb_read_io_threads            = 4
innodb_write_io_threads           = 4
query_cache_type                  = 0

buffer_pool_size = 24G. 전체 메모리의 70~75% 가이드라인. OS 파일 캐시(page cache)랑 mysqld 자체가 쓸 메모리를 빼고 나면 이 정도. 다만 O_DIRECT로 가면 page cache를 우회하기 때문에 OS쪽 캐시에 의존하지 않음. 그래도 백업/레플리카 IO, tmpfs, 커넥션별 버퍼(sort/read/join buffer × 동시 접속) 때문에 여유 있어야 함. 실측으로 mysqld RES 27GB + 시스템 기타 3GB 정도 되어서 안정됐다.

buffer_pool_instances = 8. 5.5에서 추가된 옵션. 풀을 여러 조각으로 쪼개서 LRU/flush mutex 경합을 줄인다. 가이드는 pool_size가 1GB 이상이면 인스턴스 수 × 1GB 이상 되도록. 24GB면 8~16 사이 적당. 16까지 가봤는데 우리 워크로드(OLTP 10:1 read:write)에서 8과 16 차이가 거의 없어서 8로 낮춤.

log_file_size = 512M. 쓰기 워크로드에서 redo log가 너무 작으면 dirty page를 더 자주 flush해야 해서 IO가 튄다. 5.5는 redo log 총 크기를 바꾸려면 안전 종료 → ib_logfile0, ib_logfile1 백업 후 삭제 → 재시작 순서가 필요. 5.6부터는 서버 살린 채 바꿀 수 있다는데 아직 5.5 쓰고 있어서 주말 점검창에 적용. 합계 1GB(512M × 2)면 우리 쓰기량 기준 redo가 한 바퀴 돌기까지 25분 정도 걸린다. 재해 발생 시 crash recovery 시간도 이 사이즈에 비례함.

flush_log_at_trx_commit = 2. 1(default, ACID 완전 준수)에서 2로 내림. 매 커밋마다 로그 버퍼를 OS 파일시스템 캐시에 write만 하고 fsync는 초당 한 번. 서버가 통째로 죽으면 마지막 1초 커밋이 날아갈 수 있음. 금융성 데이터가 아니고 서비스 특성상 재처리 가능한 이벤트라 감수. 실측 쓰기 TPS가 대략 1.7배쯤 뜀.

flush_method = O_DIRECT. 더블 버퍼링(OS page cache + innodb buffer pool) 피한다. 단 O_DIRECT_NO_FSYNC는 ext4에선 위험하니 그냥 O_DIRECT. XFS 파일시스템 쓰는 경우라면 fsync가 journal도 같이 밀어주는 이점이 있음.

query_cache_type = 0. 5.5에선 아직 있지만 멀티 코어 환경에선 global lock이 발목 잡는다. 읽기 쿼리도 invalidation 때문에 lock 경합이 남. 프로파일링으로 Waiting for query cache lock 대기가 눈에 띄길래 그냥 껐다. 6 이후로는 deprecate될 분위기.

설정 바꾸고 재시작 직후에는 오히려 체감 더 느림. 버퍼풀이 비어 있어서 모든 조회가 디스크 히트. 핫데이터 강제 예열 필요. 이런 식.

-- 매출/주문 테이블 선두 1M행 읽어 올리기
SELECT SQL_NO_CACHE COUNT(*) FROM orders FORCE INDEX (PRIMARY)
  WHERE id BETWEEN 1 AND 1000000;

SELECT SQL_NO_CACHE /*+ */ AVG(price) FROM items;

스크립트로 자주 읽히는 테이블 top 10 정도를 연속으로 읽어 풀에 올려두면 10분 안에 안정권. 5.6부터는 innodb_buffer_pool_dump_at_shutdown / innodb_buffer_pool_load_at_startup 옵션으로 재시작해도 워밍업 저장/복원 가능한데 지금 버전은 수동.

한 시간쯤 지켜보니 애플리케이션 평균 응답 300ms → 60ms. slow query 빈도도 확 줄었다. Innodb_buffer_pool_read_requests 대비 Innodb_buffer_pool_reads 비율이 3% → 0.2% 수준이면 일단 만족. 내일은 innodb_io_capacity를 SSD 기준으로 더 올려 볼 예정.