MySQL의 잠금 특성을 확인하기 위한 실험 결과입니다.
- 읽기 잠금의 동작 특성
- 쓰기 잠금의 동작 특성
- 트랜잭션과 Row 잠금
- 라라벨에서 트랜잭션과 잠금
MySQL의 잠금 특성을 확인하기 위한 실험 결과입니다.
이 글은 그간 내가 짠 코드에 대한 반성이며, 앞으로 더 잘 만들겠다는 약속이며, 이런 실수를 하지 말라는 계몽이기도합니다.
割鷄焉用牛刀(할계언용우도) 닭 잡는 데 어찌 소 잡는 칼을 쓰겠는가?
오해를 하실까봐 미리 쉴드를 칩니다. 제가 좋아하고 자주 인용하는 말입니다.
그런데 바꾸어 생각해보면, 소잡는데 닭 잡는 칼을 쓰는 것도 바보 같은 짓입니다. 일회성으로 사용할 소프트웨어을 개발할 때는 모든 설계 원칙을 지킬 필요 없습니다만, 계속 유지 보수해야 하는 대형 서비스를 개발할 때는 소 잡는 칼을 써야지요.
지난 몇 개월간 저의 포커스는 읽기 쉬운 코드였습니다. “팀에 처음 합류한 신입이 코드를 이해하고 바로 프로젝트에 투입할 수 있는가?” 라는 관점이죠.
컴퓨터가 인식 가능한 코드는 바보라도 작성할 수 있지만, 인간이 이해할 수 있는 코드는 실력 있는 프로그래머만 작성할 수 있다.
- 마틴파울러 "리팩토링"저는 2016년 12월 부터 라라벨 5.2로 개발한 메쉬프라임이라는 서비스 개발에 참여하고 있습니다. 이 서비스는 오픈한 지 대략 1년 됐고 현재는 매일 2만 트랜잭션 정도가 발생하고, 이 트랜잭션에는 네다섯 개 정도의 테이블이 연결되어 있어서, 대략 10만 레코드가 생성됩니다. 오늘 기준으로 master
브랜치에 총 3,495 커밋이 있고, 추가된 코드는 대략 42만 라인입니다. 결코 작은 서비스가 아니죠?
팀 합류 초기에 폭풍같이 몰아치던 기능 추가 프로젝트가 어느 정도 마무리되어, 한 숨 돌리면서, 몇 주전에 라라벨 프레임워크 버전 업을 시도한 적이 있습니다. 운영 서버에 적용할 의도는 아니었지만, 곧 5.5 LTS가 나오니 5.3 -> 5.4까지 마이그레이션 해보자는 취지였지요. 공식 매뉴얼에서 말하는 마이그레이션 가이드를 따라 5.3 마이그레이션을 수행했습니다. 랜딩 페이지가 나오고 로그인도 잘 됐습니다만, API 엔드포인트를 하나씩 호출해 보고 문제점들을 하나씩 잡아가는 과정에서 마이그레이션이 불가하다는 점을 깨닫고 뒤로 물러섰습니다.
왜 그랬을까요? 바로 이 글에서 쓰고자 하는 1) 커플링된 코드와 2) 아키텍처 때문이었습니다. 본문에 나오는 코드는 메쉬프라임과 무관함을 밝힙니다.
이제 나는 읽기 쉬울 뿐만 아니라 유지 보수가 편리한 코드를 개발할 것을 약속합니다.
프로그램이 지닌 가치는 두 종류다. 하나는 1) 현재의 기능이라는 가치이고, 또 하나는 2) 미래의 기능이라는 가치다. 프로그래밍할 때 개발자는 주로 그 프로그램에 현재 무슨 기능을 넣을지에 전념한다. 버그를 수정하든 새 기능을 추가하든, 그것은 프로그램의 성능을 높임으로써 현재 기능의 가치를 높이는 일이다.
프로그램의 현재 기능은 그저 일부에 불과하다는 사실을 깨우치지 않으면 개발자로서 오래 가지 못한다. 오늘 일을 오늘 할 수 있어도 내일 일을 내일 할 능력이 없다면 개발자로서 싪패하게 된다. 오늘 해야 할 일은 알아도 내일 일은 알 수 없는 것이 당연하다. 이런 일, 저런 일, 또는 어쩌면 생각지도 못한 일을 하게 될 수도 있다.
- 켄트 벡상수는 변하지 않는 값, 변수는 변하는 값이라고 배웠습니다. 객체에서도 마치 상수처럼 한 번 생성된 이후에 상태를 변경할 수 없는 객체를 불변(Immutable) 객체라고 합니다.
아래 예제를 살펴 볼까요?
<?php
$a = 1;
$b = $a;
$b = 3;
var_dump($a);
위 코드의 실행 결과는 int(1)
입니다. 변수 $b
에 변수 $a
의 값을 할당한 후, $b
에 새로운 값 3
을 할당했습니다. 당연히 $b
에는 3
이 담겨 있고, $a
에는 1
이 담겨 있을 겁니다. 아주 쉽죠?
그런데, 객체 컨텍스트에서도 이 규칙이 그대로 적용될까요? 클래스는 변수와 함수로 구성된 템플릿이며, 객체란 그 템플릿에 필요한 값을 채워서 완성한 실체입니다. 여튼 위의 예제와 똑같이 동작해야 하지않을까요?
<?php
$a = new stdClass;
$a->name = 'Foo';
$b = $a;
$b->name = 'Bar';
var_dump($b);
// class stdClass#696 (1) {
// public $name =>
// string(3) "Bar"
// }
var_dump($a);
// class stdClass#696 (1) {
// public $name =>
// string(3) "Bar"
// }
변수 $b
에 $a
에 담긴 객체를 그대로 할당한 후, $b
의 $name
속성을 변경했습니다. $b::name
속성 값만 "Bar"
로 바뀔 것이라 생각했지만, $a::name
속성 값도 "Bar"
로 변경되어버렸습니다. var_dump()
로 출력된 결과물에 객체 번호를 보면 힌트를 얻을 수 있습니다. $a
와 $b
모두 #696
으로 같습니다. 즉, 객체 $b
는 객체 $a
의 메모리 번지를 그냥 참조하고 있다고 볼 수 있습니다.
해결법은 의외로 간단합니다. 복제하는거죠. clone
키워드와 객체 번호가 #696
, #697
로 서로 다름을 확인해주세요.
<?php
$a = new stdClass;
$a->name = 'Foo';
$b = clone $a;
$b->name = 'Bar';
var_dump($b);
// class stdClass#697 (1) {
// public $name =>
// string(3) "Bar"
// }
var_dump($a);
// class stdClass#696 (1) {
// public $name =>
// string(3) "Foo"
// }
p7. 아키텍쳐 구성
p11. 도메인을 모델링할 때 기본이 되는 작업은 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것이다.
p17. 엔티티와 밸류
p32. 도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성 시점에 필요한 것을 전달해 주어야 한다. 즉, 생성자를 통해 필요한 데이터를 모두 받아야 한다.
이 슬라이드는 Modern PHP User Group 2017년 3월 정기 모임 발표 자료입니다.