java 와 python 을 공부했던 나는 현재 ruby on rails 로 어플리케이션을 관리하고 있다. python 과 비슷하면서도 다른 ruby 언어의 자유도와 패턴 매칭 기법은 매우 놀라웠다. 그러나 모듈 및 singleton 구조 등을 이해하기는 어려웠다. 지금 ruby 를 다룬지 1년이 넘었지만 자신있는 언어라 하면 ruby 를 언급할 수는 없을 거 같다. 이번에 작업하면서 맞닥뜨린 이슈가 있다. 해당 이슈를 혼자 해결못하였고 다른 팀원들의 도움을 받아 장작 6시간 넘게 삽질한 결과, 아주 놀라운 걸 발견했다(놀랍기보다는 무서운 쪽에 가까운 듯).
ref: https://github.com/rubyconfig/config
ruby on rails 에서 설정 값을 관리하는데 유용하게 사용되는 젬인 config!
이를 이용하면 다음과 같이 세팅 파일을 만들고 사용할 수 있다.
# config/setting.yml
...
groups:
vip:
namespace: very_important_person
...
class Person
...
def send_mail
namespace = Settings.groups.vip.namespace.camelize # 'very_important_person' -> VeryImportantPerson
...
end
end
또 하나 루비에서 자주 사용되는 방법 중 하나인 bang method!
메서드 뒤에 ! 를 붙여주면 메서드의 결과 값을 호출한 자신에게 적용시킨다.
namespace = 'very_important_person'
namespace2 = namespace.tr('_', '-')
puts namespace # very_important_person
puts namespace2 # very-important-person
namespace.tr!('_', '-')
puts namespace # very-important-person
사이드킥으로 워커를 띄우고여러 잡을 돌리던 중 이상한 현상을 목격했다. AWS cloudwatch metric 에 지표를 보내는 잡이 갑자기 이상한 namespace 로 metric 을 전송하는 것이였다. 아래는 해당 사건을 토대로 재구성한 코드이다.
# config/setting.yml
school:
metrics:
namespace: student_count
# app/jobs/my_job.rb
class MyJob < ApplicationJob
attr_accessor :namespace
def perform
namespace = Settings.school.metrics.namespace
aws_client.put_metric(
namespace: namespace.camelize # studentCount 로 메트릭을 쏴줄 것으로 예상
...
)
end
end
당연히 MyJob
에서 'studentCount' 로 메트릭을 전송할 것으로 예상했으나, 이게 웬걸 'Student-count' 네임스페이스로 메트릭이 전송되었다. 이건 세팅 값을 kebab -> camel 케이스로 해야지 나오는 값인데 이렇게 만드는 코드를 도저히 찾을 수가 없었다.
6시간을 삽질하던 중, 한 팀원이 다른 잡에 의심쩍은 코드를 찾아냈다.
# app/jobs/my_job2.rb
class MyJob2 < ApplicationJob
attr_accessor :namespace
def perform
namespace = Settings.school.metrics.namespace
namespace.tr!('_', '-')
...
end
end
도대체 MyJob
과 MyJob2
는 어떻게 관련이 있는걸까?
사이드킥 워커는 프로세스를 띄우고 처리 가능한 쓰레드만큼 잡을 동시에 처리할 수 있다. 잡이 처리되더라도 TERM
시그널을 받지 않는 한 그대로 프로세스가 유지되고, 이후 다른 잡 처리 요청이 오면 바로 처리가 가능하다. 위 MyJob
과 MyJob2
또한 같은 프로세스에서 실행될 것이다.
MyJob2
의 코드를 자세히 들여다 보자.
1. namespace
변수에 Settings.school.metrics.namespace 값을 할당받는다.
2. namespace
변수에 담긴 값에서 _
를 -
로 치환하여 다시 namespace
에 할당받는다.
여기서 namespace
에 담긴 문자열은 당연히 immutable 할 줄 알았다. 그러나 루비에서는 그게 아니였던 것이다..... 즉, 루비에서는 문자열이 mutable 하다. 즉, namespace
에 세팅 값을 할당하였는데, 이때 값이 복사되어 할당되는게 아니라 namespace
와 Settings.school.metrics.namespace
가 같은 곳을 참조하고 있는 것이다. 파이썬이라면
a = 'abc'
b = a
b += 'd'
print(a) # 'abc'
print(b) # 'abcd'
이지만 루비는 a 도 abcd
가 나온다는 것이다.
따라서 namespace.tr!('_', '-')
를 호출한 순간 Settings.school.metrics.namespace
값도 변경되어 다음 MyJob
을 호출할 때 변경된 세팅 값으로 들어가게 된다.