독후감 – The Five Dysfunctions of a Team

이 책이 2002년도에 첫판이 나왔다니까 현재 시점이랑은 17년의 갭이 존재한다. 하지만 최근에 나왔다는 여러 책들과 그 내용을 비교해봐도 팀을 관점에서 17년전이 사고와 현재가 틀리지 않았다. 미국적 사고여서 그런건가 싶기도 하다. 내가 겪어왔던 17년의 세월동안에 리더십에 관련된 이야기들이 한국에서는 몇 번의 변곡점이 있었다고 생각되니까.

픽션식으로 한 회사내에서 이뤄지는 두달 동안의 변화를 이야기식으로 풀어냈다. 글이 흥미진지하다. 하지만 전달해야 할 내용은 줄거리의 흐름에 잘 녹아져있다. 왜 베스트셀러인지 충분히 공감된다.

 

~~~

 

 

신뢰(Trust)는 진정한 팀웍을 이루는 가장 기본 요소다. 때문에 서로를 이해하고 상대방에게 열린 태도를 갖추지 못한다면 팀웍은 기대할 수 없다. 제대로 된 팀이라면 팀원간에 서로 등돌리지 말아야 한다. 자신의 실수와 약점을 솔직하고 두려움없이 이야기하고 인정할 수 있어야 한다. (their mistakes, their weakness, and their concerns without fear of reprisal)

미팅에서 이야기가 오가지만, 이야기는 이야기일 뿐 서로 문제가 되지 않기 위해서 그러는 척 할 뿐 짐심을 담아서 문제를 이해하고 그 문제 혹은 해결 방법에 동의(agreement)는 어렵다.

다른 사람의 소중한 시간을 배려하기 위해 전체 팀원들에게 중요한 이야기만 팀 미팅에서 이야기해야 한다? 완전히 개인적인 이야기나 특정 사람을 비난할 목적의 이야기가 아니라면 팀 미팅에서 함께 다뤄져야하고 미팅에 들어온 사람들은 온전히 그 이야기에 집중하고, 들어야 한다. 미팅 시간에 다른 짓을 하는거야말로 미팅 시간에 모인 다른 사람들의 소중한 시간을 낭비하게 만드는 결과를 초래한다. IT회사라고 해서 원온원(One-on-one)을 주창하지만, 그건 소통의 한 수단일 뿐 결국에는 소통 방식의 한 수단일 뿐이다. 다른 사람들도 알아야 한다면 팀 미팅이 되려 더 좋은 선택이고 합리적인 소통 방법을 찾는 것이 좋은 팀 문화다.

Relationship setting – 신뢰(Trust)는 그냥 만들어지는게 아니라 그 사람에 대해 아는 것부터 시작한다. 사람을 처음부터 알기는 불가능하고, 그 사람의 가족이나 집안 환경 혹은 성장 배경등을 알 때 얼추 짐작을 시작할 수 있다. 일상적인 생활의 일부를 공유하고 있어야지 그 사람의 태도나 성격을 짐작할 수 있다. 일이 본격적으로 힘들어 지더라도 이런 배경 지식이 업무 가운데서 신뢰가 쌓여가는데 도움이 된다.

사람들이 서로 싸운(arguing, heated debating)다고 해서 이걸 인위적으로 중재해서 가식적인 평화 상태를 만드는 건 리더로써 올바른 선택이 아니다. 문제가 있다면 그걸 수면위로 드러내고, 팀 내부에서 자연스럽게 결론이 도출될 수 있도록 가이드해야 한다.

팀원들이 팀 내에서 개인의 현재 위치를 인식하게 만들어야 한다. 팀/회사를 위해 공헌하는 측면에서 딱 1개씩 잘하는 것과 고쳐야 할 것들을 다른 팀원들 앞에서 이야기하는게 필요하다. 이 과정은 개인이 생각하는 면과 다른 팀원들이 생각하는 그 팀워에 대한 시각차가 어떤 것들이 있는지 드러내고, 이 차이를 어떤 방식으로 메꿀지를 명확하게 할 수 있다.

다른 사람들과의 협업을 포기하고, 저 혼자 잘났다고 생각하는 사람이 있다면, 그 사람을 가만히 두면 안된다. 그 사람의 태도로 인해 팀내 협업이 망가질 수 있고, 혼자 잘 났다고 하는 자존심(Ego)를 되려 키워줄 수 있기 때문에 팀에 마이너스로 동작한다. 그런 징후가 보이면 바로 싹을 잘라야한다. 탑신병자처럼 개인 플레이만 고집하고 개인 성과에만 집착하는 경우라면 아무리 능력이 좋더라도 팀원으로써의 가치가 없다. 과감하게 팀에서 배제하고, 필요하다면 내보내는게 맞다.

Inattention to results – the tendency of team members to seek out individual recognition and attention at the expense of results, the goals of the entire team. The key is to make the collective ego greater than the individual ones.

팀이 의미있는 결과를 만들어내는지를 지속적으로 체크해야 한다. 두말할 나위없이 이익을 많이 내는게 확실한 결과겠지만, 이건 말그대로 결과론적이다. 이보다는 지속적인 개선을 통해 궁극적인 Goal을 달성해야 한다. 따라서 단기적인 달성 가능한 목표(Goal) 수립이 필요하고, 이 목표를 위한 진행 단계(Milestone)를 정한다. 그리고 지속적으로 진행 결과 및 과정을 리뷰하고 필요한 수정을 거쳐야 한다. 단기 목표는 구성원 모두가 헷갈림없이 이해하고 동의해야 한다. 그래야 팀의 목표에 모두가 동참할 수 있으며, 이게 안되면 각자 도생(Individual status or ego)의 방법을 찾게 된다. 합의된 목표에 대한 진행 상태는 일별로 체크 가능해야하며, 그 결과 역시 투명하게 공유되어야 한다.

농구팀을 상상해보자. 전반전이 끝난 후 라커룸에서 선수들에게 피드백을 줘야하는 코치가 있다. 코치가 피드백을 준다고, 센터 불러서 코치실에서 원온원(One-on-one)하고, 포인트 가드, SF, PF 다 따로 불러서 이야기한다면? 각자의 피드백이 팀 선수들 사이에서 팀 플레이로 살아날까? 선수 개인은 어떨지 모르겠지만, 팀 플레이는 기대하기 힘들다. (That’s not a team. It’s a collection of individuals.) 마찬가지로 팀에서 각자가 하는 일 역시 팀 플에이어야 한다. 누구는 Frontend를 담당하고 있기 때문에 Backend에 대한 관심이나 책임이 없다고 하거나 그 역도 마찬가지다. 팀으로써 우리는 모두 팀이 하는 어떤 일에 대해서도 책임이 있고, 기능적으로 다른 일을 하고 있기 때문에 나는 책임이 없다고 이야기한다면 그 사람은 팀원으로 불합격이다. 기능이 Fabric으로 엮여서 공동의 목표를 수행한다고 했을 때, 나는 반드시 다른 팀원들이 어떤 일을 하고 있고 협업 플레이를 위해 뭘 해야하지 주의깊게 살펴야 한다.

Fear of conflict – You have tension. But there is almost no constructive conflict. Passive, sarcastic comments are not the kind of conflict I’m talking about.

건설적인 토론은 문제 해결을 위해 서로의 의견을 가감없이 테이블위에 올리고, 그 가운데 무엇이 최선인지를 따지는 것을 의미한다. 단순히 난 말을 했고, 넌 이야기를 들었다로 결론내려지는 토론은 전혀 의미없다. 또한 괜히 다른 사람과의 의견 충돌을 두려워해서 이도 저도 아닌 상태가 되도록 방치하는 것도 되려 팀이 앞으로 나아가는 것을 방해하는 짓이다. 치열하게 논쟁하고 싸워서 의미있는 결론이 내려지는 팀 문화가 가치있다. 되려 이런 걸 두려워해서 빅마우스 앞에서 입도 뻥긋안하는 그게 쳐내야할 적폐다.

p96. Lack of commitment – Even if people generally willing to commit, they aren’t going to do so because they need to weigh in before they can really buy-in.

p98. Avoidance of accountability – Once we achieve clarity and buy-in, it is then that we have to hold each other accountable for what we sign up to do, for high standards of performance and behavior. And as simple as that sounds, most executives hate to do it, especially when it comes to a peer’s behavior because they want to avoid interpersonal discomfort.

당신이 결과를 가장 우선시하는 팀(Your first team)이 당신의 팀이다. 개인이 부서장이나 팀장이더라도 그 팀의 결과를 우선으로 삼는다면 그건 개인/팀 이기주의다. 회사에 소속되어 있다면 회사가 잘 되도록 만드는 것이 조직의 일원으로써 갖는 최선의 미덕이다. 이걸 달성하기 위해 가장 먼저 고려해야하는 것은 내가 일한 결과가 누구를 위한 것이냐하는 문제다. 내가 이끌고 있는 팀이 우선이다면 앞서 이야기한 것처럼 팀 이기주의다. 되려 팀장으로써 소속된 본부의 결과를 위해 최선을 하는 것이 맞다. 마찬가지로 본부장도 회사를 위한 결정을 따르고 그 결정을 결과로 이뤄내기를 기대하기 때문에.

그러나 이렇다고 해서 자신이 이끄는 팀을 고려하지 말라는 것은 아니다. 본부의 팀장들이 나의 First Team이 될 것이고, 개발팀은 Secondary Team이 되면 되니까. 신경쓰지 말라는 이야기가 아니라 노력이 어느 부분에 더 집중되어야 하는지를 이야기한다. (Well, you don’t have to destroy it. But you do have to be willing to make it secondary. And for many of you, that might very well feel like abandonment.)

만일 동료가 나의 전문 영역이라는 부분에 대한 이야기가 내가 느끼기에 간섭(혹은 침범)이라고 느껴질 때, 방어적이된다. 이런 태도는 함께하는 동료에 대한 믿음이 부족함에서 오지 않을까 싶다. 그리고 전문가로써 자신에 기량에 대한 의문이라고 생각하기 때문일 것이다. 자존심(ego)와도 깊은 관련성이 있다.

일을 못한 부분에 대해서는 책임을 져야한다. 그리고 못한 부분이 있다면 그 부분을 동료로써 과감하게 질책해야 한다. 되려 그럴수도 있지… 하게 되면 일을 하지 못한 그 동료는 앞으로도 그래도 되는구나라는 안이한 생각을 한다. 담당하는 일과 관련해서 문제를 일으켰거나 일정을 제대로 못지킨다면 그 부분의 책임을 동료 사이라도 명확하게 지적해야 한다. 그 다음에 그 문제를 해결할 방안을 같이 모색해야한다. 책임이 있는 부분에 대해서는 책임을 인정하고, 또 인정하도록 챌린지(Challenge)해야 한다.

Trust is knowing that when a team member does push you, they’re doing it because they care about the team. But we have to push in a way that doesn’t piss people off. Absolutely. Push with respect, and under the assumption that the other person is probably doing the right thing. But push anyway. And never hold back.

 

Understanding and overcoming 5 dysfunctions

Absence of Trust

It requires team members to make themselves vulnerable to one another and be confident that their respective vulnerabilities will not be used against them. The vulnerabilities I’m referring to include the weakness, skill deficiencies, interpersonal shortcomings, mistakes, and requests for help. It is only when team members are truly comfortable being exposed to one another that they begin to act without concern for protecting themselves. As a result, they can focus their energy and attention completely on the job at hand, rather than on being strategically disingenuous or political with one another.

Team effectiveness exercise – It requires team members to identify the single most important contribution that each of their peers makes the team, as well as the one area that they must either improve upon or eliminate for the good of the team. All members then report their responses, focusing on one person at a time, usually beginning with the team leader.

Role of leaders

Demonstrate vulnerability first. This requires that a leader risk losing face in front of the team so that subordinates will take the same risk themselves. What’s more, team leaders must create an environment that does not punish vulnerability. Even well-intentioned teams can subtly discourage trust by chastising one another for admissions of weakness or failure. Finally, displays of vulnerability on the part of a team leader must be genuine; they cannot be staged. One of the best ways to lose the trust of a team is to feign vulnerability in order to manipulate the emotions of others.

Fear of conflict

It is important to distinguish productive ideological conflict from destructive fighting and interpersonal politics. Ideological conflict is to limit to concepts and ideas and avoids personality-focused, mean-spirited attacks. However, it can have many of the same external qualities of interpersonal conflict – passion, emotion, and frustration – so much so that an outside observer might easily mistake it for unproductive discord.

When team members openly debate and disagree about the important ideas, they often turn to back-channel personal attacks, which are far nastier and more harmful than any heated argument over issues.

Role of leaders

It is key that leaders demonstrate restraint when their people engage in conflict, and allow a resolution to occur naturally, as messy as it can sometimes be. This can be a challenge because many leaders feel that they are somehow failing in their jobs by losing control of their teams during conflicts.

Lack of commitment

In the context of a team, commitment is a function of two things: clarity and buy-in. Great teams make clear and timely decisions and move forward with complete buy-in from every member of the team, even those who voted against the decision.

Consensus – Great teams understand the danger of seeking consensus, and find ways to achieve buy-in even when the complete agreement is impossible. They understand that reasonable human beings do not need to get their way in order to support a decision, but only need to know that their opinions have been heard and considered. And when the agreement is not possible due to an impasse, the leader of the team is allowed to make the call.

Certainty – That’s because they understand the old military axiom that a decision is better than no decision. They also realize that it is better to make a decision boldly and be wrong than it is to waffle. If wrong, change the direction with equal boldness.

Role of leaders

More than any other member of the team, the leader must be comfortable with the prospect of making a decision that ultimately turns out to be wrong. And the leader must be constantly pushing the group for closure around issues, as well as adherence to schedules that the team has set. What the leader cannot do is place too high a premium on certainty or consensus.

Avoidance of accountability

The most effective and efficient means of maintaining high standards of performance on a team is peer pressure. One of the benefits is the reduction of the need for excessive bureaucracy around performance management and corrective action. More than any policy or system, there is nothing like the fear of letting down respected teammates that motivates people to improve their performance.

Simple and regular progress review – Relying on them to do so on their own, with no clear expectations or structure, is inviting the potential for the avoidance of accountability.

Team Rewards – By shifting rewards away from individual performance to team achievement, the team can create a culture of accountability. This occurs because a team is unlikely to stand by quietly and fail because a peer is not pulling his or her weight.

Inattention to results

They do not live and breathe n order to achieve meaningful objectives, nut rather merely to exist or survive. Unfortunately for these groups, no amount of trust, conflict, commitment, or accountability can compensate for a lack of desire to win.

By making results clear and rewarding only those behaviors and actions that contribute to those results.

Teams that say, “We’ll do our best”, are subtly, if not purposefully, preparing themselves for failure.

Role of leaders

Team leaders must be selfless and objective, and reserve rewards and recognition for those who make real contributions to the achievement of group goals.

 

~~~

종종 찾아보면서 읽고, 내용을 새기면 좋을 듯 싶다.

배려있게 Slack 사용하기

다른 글에서 슬랙(Slack)을 업무용으로 괜찮게 사용하기 위한 팁을 몇가지 소개했다. 이번은 슬랙이라는 커뮤니케이션 도구 혹은 커뮤니케이션 공간의 배려에 대해 이야기 해보고 싶다.

슬랙은 업무용 메신저다. 메신저가 다 같은 메신저일 뿐이지, 다른게 뭐냐??? 라고 이야기하는 분이 있다면 일상과 일(업무)을 구분하지 못하는 분이다. 슬랙류를 사용하는 이유는 업무를 위해서지 수다떨기 위함이 아니다.

투명한 커뮤니케이션

슬랙은 기본적으로 일을 위해 쓴다. 그렇기 때문에 이 공간에서 이뤄지는 일상적인 대화는 정보(Information)의 가치를 갖는다. 업무를 진행하는데 있어서 정보의 중요성은 모두가 공감할 것이다. 그리고 정보의 가치는 필요한 사람에게 전달 가능할 때 가장 빛을 발한다.

슬랙의 대화는 공개 채널(Public Channel) / 비공개 채널(Private Channel)  / 1:1 메시지(DM – Direct Message)의 3가지 형태로 이뤄진다. 정보 가치를 갖는 대화가 업무 관련 담당자들에게 도움이 될려면, 대화 기록에 자유롭게 접근할 수 있어야 한다. 특정 이슈 혹은. 주제에 대한 이해 당사자이외에도 관심있는 사람들의 다양한 관점들이 모아지면, 정보의 가치가 더욱 커질 수 있다. 이런 부분을 고려한다면 가장 효과적인 대화 형태는 공개 채널이다.

공개 채널의 투명성은 어떤 면에서는 참가자들이 신중한 대화를 하도록 부작용 혹은 순작용으로 동작한다. 속된 말로 아무말 대잔치를 할 수는 없다. 본인이 던지는 이슈 혹은 질문받은 내용들을 한번 더 생각하게 만든다. 물론 이런 이유로 공개 채널에서 이야기하기를 꺼리고 눈팅만 하기도 한다. 누구는 속된 말로 “자기검열” 아니냐… 이야기할 수 있지만 정제된 대화라면 더욱 더 정보 가치를 지닐 수 있다.

비공개 채널은 채널에 초대받은 사람만 참여가 가능하다. 그리고 1:1 메시지는 당연히 개인간 대화니 초대받지 않은 다른 사람이 메시지 내용을 볼 수 없다.  이 환경에서 주고받는 정보는 고립된다. 제한된 사람만 내용을 알고 있고 모르는 사람은 소외된다. 좀 더 과장하자면 정보를 무기로 사용하게 된다. 이러면 안된다.

채널의 의미와 배려

채널의 물리적인 종류는 앞서 이야기한 바와 같이 공개 / 비공개 유형으로 나뉜다. 이를 실제 운영상 관점에서 살펴보자. 회사 혹은 조직의 특성에 따라 슬랙 채널에 어떤 의미를 두는지는 경우에 따라 다른다. 그렇지만 일반적인 경우 한 팀은 하나의 대표 슬랙 체널을 갖는다. 내가 현재 속한 라이엇 개발팀의 경우 #team-dev 형식의 채널의 이름을 부여한다. 팀 채널의 경우에는 팀에 소속된 사람들이 기본 멤버로 참여하고, 이루어지는 대화들도 대부분 팀에 한정된 이야기들이다.

개발팀에서 이뤄지는 일들에 대해 관련된 사람들이 질문하거나 논의하는 장소는 팀 채널이 아니다. 이런 목적을 위해 #ask-dev 채널이 존재한다. 이 채널의 참여자는 물론 개발팀에 있는 모두가 포함되며, 업무 관여자(Stakeholder)들이 모두 참여한다. 이 채널에서는 개발팀이 아닌 일반 업무 관여자들은 주로 업무 현황이나 이슈에 대한 질문을 던진다. 개발팀은 이 채널을 주요 당사자들이 알아야할 중요 전달 사항이나 공유 사항들을 이야기한다. 두 가지 모두 의미를 가지는 정보가 되고, 관련된 당사자들이 종종 챙겨봐야 할 내용들이다.

이 이외에도 채널의 이름을 통해 의미를 부여하는 방법은 다양하게 있다. 특정한 프로젝트를 위한 채널의 경우에는 #prj-something 이라는 방식으로 이름을 짓는다. 이 채널의 구성원은 프로젝트 실무를 진행하는 주요 담당자들이 기본 멤버가 된다. 주로는 PO 혹은 PM, 개발자, QA 담당자들이 기본 멤버가 되며, 필요에 따라서 이해 당사자들이 참여한다. 프로젝트가 완료되어 하나의 제품이 되었다면, 이제 제품에 대한 질의 응답을 위한 #ask-something 채널로 진화한다. 혹은 완료되어 특정 팀의 운영 범위로 들어간다면 채널을 Archive 시키고, 이후에 관련된 논의들을 개발팀이 운영하는 #ask-dev 채널로 통합히기도 한다. Archive 시킨다고 하더라도 내용이 어디 사라지는 것도 아니고 검색도 당연히 할 수 있기 때문에 문제없다.

채널의 이름을 통한 부여된 의미를 현재 내가 속한 조직의 기준으로 정리하면 대강 아래와 같다. 각 조직의 현황에 맞춘 일관성을 유지할 수 있다면 아래 열거된 내용 이외에도 다른 명명 규칙을 정의한다. 하지만 접두어를 통해 제시하는 용도의 일관성은 지켜져야하기 때문에 가급적 합의된(혹은 정의된) 규칙을 지켜줘야 한다. 목적에 맞는 채널을 찾는 사람의 경우, 아래 열거된 간단한 추론을 통해 팀의 채널을 찾아올 수 있기 때문이다.

  • #team-{team} 특정 팀({team}) 사람들이 논의한다.
  • #ask-{team} 특정 팀과 관련된 질문 사항들 혹은 개발팀 관점에서 본다면 개발팀에서 운영하는 서비스에 대한 질문하고 공유한다.
  • #prj-{product} 진행중인 프로젝트 실행 주체들을 중심으로 프로젝트 진행을 위한 구체적인 내용들이 논의된다.
  • #ask-{product} 특정 제품 혹은 서비스 관련질문이나 담당자(들)가 공유 사항을 전달한다. 성격상 팀 채널과 유사해서, 용도가 불명확하면 혼란을 만들 수도 있다.
  • #nt-{team} 공지 전용이다. 경우에 따라 Read Only로 제한을 걸기도 한다. 개발팀에서는 CI/CD 시스템을 연동해서 배포 혹은 서비스 모니터링 용도로 “nt-” 접두어를 쓰기도 한다.
  • #ot-{issue} 특정 이슈 혹은 사안에 대해서 한번(Off Topic) 웃고 즐기고 토론하는 채널이다. 대체로 업무 용도로 사용하지 않는다.

이제 배려를 이야기해자. 몇몇 채널의 명명 규칙을 이야기하면서 누가 그 채널의 주체가 되어야 하는지도 언급했다. 그리고 앞서서 채널은 정보의 공유를 위해 공개 채널을 유지하는 것이 바람직하다는 이야기도 했다. 그런데 여기서 왜 배려가 튀어 나올까?

각각의 채널에는 각 채널의 주체와 목적이 있다. 특정 팀의 채널은 말그대로 그 팀을 위한 전용 공간이고 또한 되어야 한다. 프로젝트 채널의 경우에도 비슷한 맥락을 따른다. 그럼에도 채널을 공개 채널로 유지해야 하는 이유는 공유되어야 할 내용이 자유롭게 공유되기 위함이다. 그런데 팀과 직접 연관된 사람도 아닌 사람이 팀 혹은 프로젝트 채널에서 불쑥불쑥 튀어 나와 이야기를 한다면?

뭐가 문제인가 싶긴 하지만… 나는 이 부분에서 2가지 문제를 지적하고 싶다. 첫 번째 문제는 팀 채널이나 ask 채널의 구분이 모호해진다. 즉 이야기를 공유할 적절한 공간이 어디인지 헷갈리기 시작한다. 대화가 어디에서 시작되는지가 헷갈리니 나중에는 그 내용을 어디에서 찾아야 할지 헷갈린다. 이 문제는 비슷비슷한 성격의 채널들이 여러개 생겨나면 가장 대표적으로 나타난다.

두 번째 문제는 자연스러운 무관심이다. 특정 팀이나 프로젝트 채널의 경우에는 업무 이야기도 많이 하지만 짤방부터 아재 개그까지 다양한 이야기들이 난무한다. 물론 그 가운데 의미있는 정보도 있다. 그렇지만 다른 도메인의 이야기들은 나에게는 4차원의 이야기인 경우가 다반사 아닌가? 그럼 결국 몇 번 보게 되지만, 그럼에도 Mute한다. 안읽은 내용이 있기 때문에 신경쓰는 것보다는 차라리 해당 채널을 나오는게 개인적으로는 정신 건강에 더 좋다.

이 관점에서 채널의 주인 팀에서 다른 팀을 존중하는 배려는 그럼 뭘까? 가장 먼저 해당 팀에서 다른 팀의 팀원을 초대할 때 먼저 신중해야 한다. 정보를 전달하는데 있어서 본인의 팀 채널에서 정보를 전달하는 것어 과연 올바른지 한번 생각해보자. 맞지 않다고 생각이 들면 팀 채널이 아니라 ask 채널로 가야한다. 혹은 전달해줘야 할 사람이 있는 팀의 ask 채널. 것도 아니면 prj 채널 혹은 nt 채널이 정보가 나타나야할 맞는 곳일 수 있다. 괜히 엄하게 초대해서 초대받은 당사자를 뻘쭘하게 만들 수 있다.

역으로 초대받은 쪽에서도 해당 정보를 공유받았다면, 공유받은 정보만 잘 보관하자. 그 내용을 본인의 팀 채널 혹은 ask 채널에 링크든 공유 형태든 가져오면 된다. 가져왔으면 굳이 초대 받은 채널에 남을 이유가 없다. 바로 Leave를 선택하자. 만약 이후라도 공유해줄 내용이 있다면 아마도 또 부른다. 그 사람이 날 불렀다는 건 그 사람이 궁해서지 내가 궁한건 아니니까.

대화를 의미있는 정보로

정보를 잘 퍼갈 수 있는 방법은 뭘까? 스크린 캡쳐??? ㅋㅋㅋ

어이없는 소리같지만 실제로 이런 경우가 정말 많다. 아마 증거 확보차원이라고 생각한다. 근데 뭘 위한 증거일까? ㅎㅎㅎ 근데 정말 많이 이미지로 캡쳐 후 공유한다.

슬랙은 다른(혹은 같아도) 공개 채널의 대화를 자유롭게 공유할 수 있다. 공유와 링크 복붙으로 메뉴가 나뉘지만, 본질적으로는 같다. 근데 아래와 같은 경우는 어떻게 해야할까?

공유해야할 내용은 3줄인데 각각이 구분된 메시지 있다. 두줄을 어떻게 하면 함께 공유할 수 있을까? 예시지만 함께 공유되어야 하는 경우에는 이미지 캡쳐가 동료들에게 더 효과적일 수 있다. 그중에 어느 하나만 공유하면 나머지는 무시될 수 있으니까.

하지만 원칙적으로 작성하는 사람이 공유 가능한 형태로 작성하지 않은 잘못이 더 크다. 직설적으로 이야기하자면 동료에 대한 배려심 부족? 사실은 단톡방의 습관에서 유발된 것이다. 본인이 아닌 3자가 공유하기위해 분리된 메시지가 아닌 한 메시지로 작성해야했다.

업무를 위한 대화가 시작되었다면 그 내용은 반드시 쓰레드화 되어야 한다. 당연히 업무 공유를 위해서다. 업무 관련 내용들이 쓰레드 형식이 아닌 평면적인 형태로 채널에 올라와서 한 페이지 분량 이상이 되면 캡쳐로도 공유하기 함들어진다. 상식적으로 업무에 관련된 내용들이 어떤 채널에서든 시작이 되었다면 그걸 주제로 새로운 글의 실타래가 시작되어야 한다. 요즘에는 거의 습관적으로 (:use_the_threads:) 라는 이모지를 글을 시작한 사람이 적지 않았더라도 일하는 사람의 기본이라고 생각하자.

그럼에도 불구하고 여전히 많은 분들이 슬랙을 단톡방처럼 사용하신다. 업무를 위한 이성적인 문구가 아닌 단발성 단어로 된 줄들이 이어진다. 제대로 된 정보없이 쓰레드의 라인 수만 길어진다. 과도한 라인수는 난독증을 유발한다. 항상 이럴 필요는 없지만, 정보의 가치를 생각한다면 의미있는 글의 흐름을 고려해야 한다. 물론 여러 사람들이 이런 쓰기를 다 같이 하는 건 무지 어렵다. 시킨다고 되지도 않는다. 이게 될려면 하나의 문화로 정착되어야 가능하다. “될 수 있을까?“는 다소 의문이긴 하지만… ㅎㅎ

슬랙이 만사형통?

슬랙이 업무용 메신저로 써본 사람이라면 정말 편리하다는 걸 알 수 있다. 그러다보니 많은 소통이 슬랙을 통해 이뤄진다. 일견 사람들간의 피드백이 빨리졌고, 업무 처리 속도 역시 향상됐다.

하지만 이 과정에서 좋은 모습만 있는 것은 아니다. 관심있는 채널에 올라오는 메시지에 민감해지고, 알림을 통해 전체(@all, @channel) 혹은 특정 사람을 호출하는 것이 일상화됐다. 사람들은 알림에 신경질적으로 바뀐다. 특히나 한 지역이 아니라 시차가 나뉜 경우에는 이 문제가 좀 더 심각하다. 새벽 2~3시쯤 숙면을 취하고 있는데 슬랙 알림을 받으면 편한 마음이 안된다.

가장 속편한 방법은 어느 글에서나 이야기하는 거지만, 장문의 글보다는 찾아가서 대화하는 거다. 대부분 그게 안되기 때문에 슬랙을 이용하지만, 만약 글이 장문이 되는 경우에는 슬랙보다는 메일이 효과적이다. 정리할 내용이 많다면 메일 보내고, 슬랙으로 살짝 “멜 보셈” 이라고 메시지를 남겨두는게 훨씬 효과적이다. 메일 회신이 안온다면? 슬랙 메시지 한번 더 보내면 된다. 🙂

그리고 슬랙으로 인한 숙면 방해에서 벗어나고 싶다면 다음 설정을 권한다.

정리하자면

대강 아래 그림과 같은 구도로 업무 이야기/토론이 진행되었으면 좋겠다는 바램이다.

slack-discussion

 

 

일단은 나 스스로부터 먼저 습관이 되어야겠다.

– 끝 –

 

 

 

휴면 계정 처리 – 배치에서 온라인 시스템으로

배치(Batch)라는 작업은 주기적으로 실행되는 작업을 말한다. 다루는 데이터가 적은 경우는 별 걱정이 없다. 하지만 다룰 데이터가 많다면 과연 이 작업이 정해진 시간안에 끝날지 걱정하게 된다. 배치 작업은 대량의 데이터에 대한 문제도 있지만, 한 주기안에 그 일이 끝나야한다는 시간적인 제약도 존재하는 문제기도 하다.

서비스와 이를 뒷받침하는 시스템은 계속 진화한다. 그리고 데이터와 시간에 대한 최적화도 진화에 맞춰 지속되어야 한다. 최근의 시스템은 MSA(Microservice Architecture)를 따라 보편적으로 개발한다. 그리고 기존의 시스템들도 MSA화 하기 위한 방향으로 변경을 진행한다. 내가 있는 라이엇게임즈 개발팀에서도 시스템을 개편하거나 신규 시스템들은 모두 MSA를 따라 개발 작업을 진행한다.

MSA 방식으로 구성된 시스템은 Monolithic 방식으로 구성된 시스템보다는 내부 Component간 연동이 느릴 수밖에 없다. 어플리케이션 내부의 함수 콜이 작은 어플리케이션간의 RESTful API 호출로 바뀌었기 때문에 당연히 느릴 수밖에 없다. 각각의 컴포넌트는 독립적이고 자율적인 형태로 바뀌었지만, 이에 대한 반대 급부로 컴포넌트간의 연동 속도 저하가 단점이 될 수 밖에는 없다. 배치 작업 가운데 여러 외부 컴포넌트를 연동하는 경우가 많다면 MSA를 따르면서 전반적인 시스템의 최적화가 난관에 봉착하게 마련이다. 생각보다 한 주기의 시간안에 작업을 끝내는게 심각하게 어려운 작업이라는 것을 새롭게 알 수 있게 될 수 도 있다..

이 글에서는 팀이 MSA 환경에서 어떻게 배치 시스템의 속도 문제를 사고의 전환으로 해결했는지 소개한다.

휴면 계정이란?

휴면 계정은 개인 정보 보호 조치 가운데 하나다. 계정이 1년 동안 아무런 활동도 없으면, 해당 계정과 관련된 개인 정보를 라이브 시스템에서 없애고 이를 라이브 시스템과 분리된 별도의 공간에 저장하는 조치다. 유럽에서 GDPR이 작년(2017)에 실행되면서 개인 정보 보호 조치를 강화했지만, 한국에서는 이미 2015년에 이 조치를 실행했다. 이런 경우들을 두고보면 개인 정보를 다루는 법률적인 측면에서 한국이 되려 다른 나라보다 선두에 있음에 틀림없다. 꼭 이런 조치를 실행하고, 시스템을 만들어야 하는가에 대한 논의가 있긴하다. 하지만 불필요한 개인 정보를 굳이 라이브 시스템에 둘 이유는 없다. 필요없다면 지우는게 맞다. 이런 측면에서 옳은 정책임에는 틀림없다. 다만 이를 어떻게 구현하고 실행할지 그 방법론이 문제가 될 수 있을지도 모르겠다.

휴면 계정 대상자는 휴면 조치를 취하기 전 1개월내에 최소 1회 이상, 계정 소유주에게 휴면 조치가 취해진다는 사실을 알려야 한다. 그럼에도 불구하고 추가적인 활동이 없다면 최종 활동이 있었던 시점으로부터 1년이 되는 날에 계정에 대한 휴면 조치를 실행한다.

일반적으로는 이렇게 합니다.

1년 동안 아무런 활동도 없는 계정들을 추출하기 위해서 어떤 방식을 사용하나? 가장 쉽고 일반적으로 생각할 수 있는 방안은 매일 전체 Active 계정들(ACCOUNT 테이블)을 추출한 다음에 해당 계정들의 최종 Activity Date를 확인하는 방법이다.

안목있는 아키텍트가 있다면, 최종 활동에 대한 정보를 하나의 테이블(RECENT_ACTIVITY)에 모아둘 것이다. 그리고 모든 데이터가 하나의 데이터베이스에 있는 구조라면 이 문제를 가장 손쉽게 해결할 수 있다. SQL 쿼리 한방으로.

SELECT * FROM ACCOUNT, RECENT_ACTIVITY
WHERE ACCOUNT.id = RECENT_ACTIVITY.account_id
AND RECENT_ACTIVITY.activity_datetime < now() - 365;

휴면 계정으로 추출된 계정 정보들을 휴면 계정화하고, 이를 계정 테이블에서 삭제한다. 정말 깔끔하다. 휴면 계정화 1개월 전에 보내야하는 통보 기능도 비슷한 쿼리로 이메일 주소를 추출해서 처리할 수 있다.

SELECT * FROM ACCOUNT, RECENT_ACTIVITY
WHERE ACCOUNT.id = RECENT_ACTIVITY.account_id
AND RECENT_ACTIVITY.activity_datetime < now() - 335;

원래 있던 쿼리의 365를 335로 간단히 바꾸면 통보를 위한 대상자도 손쉽게 추출할 수 있다.

MSA가 대세라구요!?

MSA를 서비스에 적용하면서 이제 역할에 따른 DBMS를 별도로 분리한다. MSA의 기본 원칙 가운데 하나는 하나의 마이크로서비스가 자신의 데이터를 서비스를 위해 정의한 저장소에 관리하고, 해당 데이터가 필요한 다른 서비스들은 API를 통해 참조하는 것을 원칙으로 한다.

이제 최종 활동에 대한 관리 기능이 별개의 서비스(ActivityLogger)로 분리된다. 잦은 로깅으로 전체 서비스에 영향을 주던 민폐를 걷어내고, 온전히 로깅에 대한 역할을 담당하는 마이크로서비스로 거듭난다. 계정의 최종 활동일을 계정 테이블에 담고자하는 노력이 있긴 했지만, 계정 테이블은 많은 서비스들에서  기본적으로 참조하는 데이터 영역이고, 잦은 업데이트는 과도한 IO 부하를 일으키기 때문에 ActivityLogger 서비스의 API를 통해 참조해서 처리하기로 한다.

ElasticSearch를 통해 구현된 최종 활동 API는 빠른 응답 속도를 보장한다. 이를 사용하기로 했기 때문에 어쩔 수 없이 전체 계정들을 모두 로딩 후 API를 호출해서 최종 Activity Date를 구하고, 1년이 경과한 계정을 찾는다. 하지만 백만 건이 넘는 계정에 모두 로딩해서 처리하는 건 많은 소요 시간을 필요로 한다. 계정 아이디에 따라 이를 N개의 그룹으로 나누고, 각 그룹을 로딩해서 동시에 처리하도록 병렬 쓰레딩을 도입한다.

어차피 전체 계정들을 모두 로딩해야했기 때문에 이메일 통보 기능과 휴면화 기능을 2개의 배치 작업에서 1개의 배치 작업으로 합친다.

전체 데이터 셋과 유사한 환경을 구성하고, 테스트를 해보니 6시간이면 배치 작업이 성공적으로 완료됐다. 이제 라이브 서비스에서 실제로 배치를 실행할 시점이다.

이라, 이건 아닌데

테스트 환경과 달리 라이브 환경은 계정 테이블에 대한 다양한 조회와 변경이 존재하기 때문에 6시간 보다 완료 시간이 2시간 더 걸렸다. 하지만 24시간내에 작업이 완료됐기 때문에 ActivityLogger 서비스의 도입은 성공적으로 마무리가 됐다. 굿!!

Clock6h

성공적인 사업의 확장과 MSA의 도입으로 조직과 서비스 시스템들은 수평적인 확장을 통해 안정적인 서비스를 제공한다.

가입자 증가로 휴면 처리 시간이 점차 오래 걸린다. 12시간

Clock12h

과정에서

  • 이메일 발송 기능도 클러스터링 기반의 독자적인 이메일 서비스로 새롭게 탄생한다.  – 13시간
  • 상품을 판매하기 시작했고, 결제 데이터가 개인화 데이터로 분류됐다. 마찬가지로 해당 데이터 역시 휴면화 대상 프로세스에 편입되어야 한다.  – 16시간
  • 가입자가 더 늘었다. – 20시간

Clockmh

하루안에 휴면 처리 배치가 완료되어야 하지만 이대로 두면 하루를 넘길 것이 분명하다. 계정 데이터에 대한 그룹을 세분화하고, 추가 장비들을 도입해서 병렬화를 높이는 것도 방법이긴 하지만 과도한 조회로 인해 계정 테이블에 무리를 주는 건 그닥 올바른 방법으로 보이지 않는다.

문제를 다시 정의해보자.

MSA 적용 전/후의 휴면 계정 처리 시스템이 갖는 가장 큰 차이점은 대상 계정을 추출하는 방식이다. MSA 적용 이전의 Monolithic 시스템 상황에서는 DBMS를 통해 전체 계정을 조회했다. 이후에는 직접 전체 계정을 조회하는 방식이 적용됐다. 방식의 차이가 있긴 하지만 모두 전체 계정을 조회한다는 점에는 차이가 없다. 바꿔 이야기하면 두 방식 모두 가입자가 늘어나는 상황에서는 모두 문제를 가질 수 밖에는 없게 된다.

우리가 해야할 문제를 다시 한번 짚어보자. 계정의 휴면화가 진행되는 과정을 살펴보면 1년이 되기 한 달 전에 계정의 소유주에게 알림을 주고, 1년이 경과한 시점에 휴면화를 처리한다. 즉 시간의 흐름에 따라 발생하는 이벤트다. 그리고 꼭 전체 계정을 대상으로 해야할 작업이 아니라 이 조건은 특정 계정에 관련된 이슈라고 정의할 수 있다.

문제를 이렇게 다시 정리해보니, 계정을 시간 조건에 의한 State Machine으로 간주하면 쉬운 해결 방안을 찾을 수 있다. 즉 위 조건에 따르면 다음과 같은 계정의 상태 전이가 이뤄진다.

AccountActivities

NOTIFIED 혹은 DORMANTED 상태에서 계정에 Activity가 발생했다면, 당연히 ACTIVE 상태로 변경된다. 그리고 각각의 상태 변화가 발생하는 시점에서 필요한 작업들을 해주면 된다. 예를 들어 이메일을 발송하거나 계정의 개인 정보를 분리된 데이터베이스로 옮기는 작업들이 이에 속한다.

이렇게 정리되면 계정의 상태 변화를 발생시킬 수 있는 도구만 있으면 된다. 그리고 생각외로 이런 기능을 지원해주는 Repository들을 손쉽게 찾아볼 수 있다.

그래서 이렇게 바꿨다.

휴면 계정 처리 작업에서 계정을 시간을 조건으로 한 State Machine으로 정의하고 보니, 계정 테이블을 직접 연동하는 것이 아니라 휴면 프로세스를 위한 State Machine용 repository를 만들어버리는데 훨씬 더 깔끔한 접근 방법이다. 이런 식으로 방향을 잡으니 아예 휴면 계정을 위한 별도의 마이크로 서비스를 만드는게 더 효과적이다. 그래서 다음과 같은 방식으로 휴면 계정 처리 서비스의 아키텍쳐와 도구들을 잡았다.

  1. 시간의 흐름에 따른 계정의 상태 변화를 관리하기 위해 별도의 Repository를 AWS DynamoDB를 활용하여 정의한다.
    • AWS DynamoDB의 기능 가운데 TTL 관리 기능이 존재한다.
    • DynamoDB의 TTL 기능은 단일 Entry 수준에서 TTL 값을 모두 다르게 설정할 수 있다.
    • 지정된 시간이 초과한 데이터를 AWS Lambda를 통해 추가적인 필요한 처리를 실행할 수 있다.
  2. 상태의 변화가 일어날 때 처리해야할 작업들이 좀 된다.  람다의 성격상 복잡한 Biz Logic을 구현하는 건 맞지 않다고 보기 때문에 별도 Application에서 따로 처리한다.
  3. AWS Lambda는 Expire된 항목들을 받아 계정의 상태 변화를 관리하는 어플리케이션에 전달한다.
  4. 어플리케이션은 전달된 계정의 지정된 State Transition을 관리하고, 상태 변경시에 취해져야할 기능들을 실행한다.
  5. ActivityLogger 서비스와 연동해서 계정의 활동이 발생하면 해당 계정의 상태가 항상 ACTIVE 상태가 되도록 한다.
  6. 물론 마이크로서비스를 위한 최초 상태 데이터는 계정 데이터베이스와 ActivityLogger 시스템으로부터 생성한다.

Dormant service

이와 같은 구조로 변경되면 앞서 이야기했던 배치 작업이 없어진다. 시간의 흐름에 따른 개별 계정의 상태 변화는 DynamoDB와 ActivityLogger 서비스에 의해 실시간으로 처리된다. 더 이상 배치 작업이 하루 안에 마무리 될지 가지고 조바심내지 않아도 된다.

MSA 환경에 적응할려면.

마이크로서비스 아키텍처는 서비스를 제공하는 환경을 크게 탈바꿈시켰다. 개발된 기능을 배포하는 시간을 크게 줄였을 뿐만 아니라 서비스의 독립성과 빠른 배포 주기를 기본 사상으로 가지고 있기 때문이다. 이를 충실히만 따른다면 빠른 피드백 사이클을 갖는 서비스의 개발과 유지가 가능하다.

하지만 모든 것에는 장단이 있기 마련이다. 때문에 모든 Monolithic 어플리케이션을 악의 축으로 보고, 이를 MSA화 하는 것은 상당한 위험을 내포하고 있다. 실제 변화를 꾀하기 전에 단기적으로 발생하는 손익도 반드시 고려해야한다. 이 글에서 이야기한 것처럼 예기치 않은 부작용이 발생할 수 있으며 관련된 추가 비용이 발생한다.

MSA를 효과적으로 반영하기 위해서는 상황에 맞는 적절한 도구들을 활용해야 한다. 휴면 계정용 서비스를 구현하는데는 AWS DynamoDB라는 도구를 이용해 시간에 따른 이벤트를 활용할 수 있었다. 물론 DynamoDB도 Expiration이라는 것을 정확하게 체크하지는 못한다. 해당 시간이 지난 후 4시간 안에는 지워진다지 정각에 지워지는건 아니다. 하지만 휴면 계정을 실행하는데 10분 ~ 20분의 차이는 서비스의 동작에 영향을 미치지 않는다.

마이크로서비스는 기능에 집중하기 때문에 해당 기능에 최적화된 도구를 사용할 수 있느냐 없느냐에 따라 서비스의 효율성이 좌우된다. MSA 환경에서 아키텍트는 특정 도구만 고집할 것이 아니라 다양한 도구들이 어떤 특성을 가지고 있고, 상황에 따라 유연하게 도구들이 사용될 수 있도록 가이드할 수 있어야 한다. 물론 어느 한 사람이 현재의 도구나 최신 기술들을 모두 알기란 불가능하고 또 그럴 필요도 없다. 개념이면 충분하다. 그럼 활용할 수 있는 시점에 도구들을 찾아내기만 하면 된다.

무엇보다도 중요한 점은 하나의 틀에 얽매이지 않는 자세가 중요하다. MSA가 아무리 좋다고 하지만, 본인이 있는 환경에 어울리지 않는다면 사용하지 않는 용기도 가지고 있어야 한다. 좋은 개발자 혹은 테크 리더가 보여줘야 할 태도중에는 항상 최신이 아닌 최선을 위해 최신 기술을 미룰줄도 알아야 하는게 아닐까 싶다.

– 끝 –

회사 생활을 위한 생존 영어

라이엇게임즈에 입사해서 개발하는 것을 떠나 가장 큰 변화는 뭐였을지를 생각해본다. 아마도 영어가 아닐까 싶다.

사실 입사 과정을 거치면서 미국에 본사를 둔 회사에 일을 하지만 한국에서 개발할 꺼리가 있으니 개발자를 채용하겠지… 라고 생각했다. 개발자가 코드로 이야기를 하면 되지 굳이 영어를, 그것도 내가 쓸 일이 얼마나 될까 싶었다. 입사 직후에 보니 영어를 할 줄 아는 PO(Product Owner)도 있는데 더욱 더 내가 영어를 쓸 일은 거의 없겠구나 생각했다.

그렇게 어느덧 4년이란 시간이 흘러 개발팀에 있는 사람들 가운데 영어로 나름 이야기하는 사람이 됐다. 영어를 써본 경험이라곤 자유여행떄 가이드북에 나온 영어 몇 마디 해봤던게 전부였는데 말이다. 전혀 의도하지 않은 발전이다. 그렇다고 네이티브만큼 영어로 이야기를 할 수 있다는 건 아니니 오해말자. 영어로 대화하는 건 아마 라이엇 한국 개발팀에 있는 모두 피하고 싶은 일들 가운데 하나니까.

왜 영어로 직접 말을?

라이엇 게임즈(RiotGames, Inc)는 다들 알다시피 미국에 본사를 둔 회사다. 한국 오피스 입사 후 역시 기대와 마찬가지로 한국 기능을 개발할려면 본사 개발팀들과 이야기를 해야만 한다. 물론 독립적으로 개발된 기능들도 있었지만, 기능이 정말 제한적이다. 제대로 동작되는 기능을 개발할려면 거의 대부분 필수 불가결적으로 센트럴 팀들과 이야기를 해야한다.

초반에 PO 역할을 담당하는 분들이 네이티브거나 비슷한 경험이 있어서 대부분 이메일과 화상회의(컨퍼런스 콜 or 컨콜)에 도움을 주었지만, 개발자로써는 충분치 않았다. 일반 업무 이야기는 PO 수준의 커뮤니케이션만으로 충분했지만, 기술 이야기는 달랐다. 특히 본사에서는 엔지니어가 나와서 이야기를 하는데, 한국팀은 엔지니어가 한국어로 한국 PO에게 이야기를 하고, 본사 팀에서 응답을 받는 구조가 되어 있었다. 이런 과정이 몇 번 반복었다. 내 성격이 좀 급해 그럴 수도 있겠지만, 이런 핑퐁식의 커뮤니케이션은 효과적이지 않다고 생각했다.

그래서 기존의 방식과는 달리 직접 커뮤니케이션을 시작했다. 기술적인 부분에 대해 직접 커뮤니케이션하고, 필요하면 컨콜도 참여했다. 영어를 통한 Writing & Reading은 별 문제가 없었지만, 듣고 말하는 건 또 다른 차원의 문제였다. 특히나 내가 잘 못 들은건 아닐까 혹은 내가 입으로 내뱉은 말이 이해되지 않거나 말도 안되는 소리를 하는 건 아닐까 싶은 두려움 등등.

처음에는 이메일이나 슬랙/행아웃을 통해 텍스트를 중심으로 소통할려고 했다. 영어 텍스트는 정보의 정확성 측면에서 좋다. 하지만 이 방식은 2개 문제점을 가지고 있었다. 하나는 시차를 극복하지 못한다는 점, 그리고 더 큰 문제는 의사 전달에는 취약하다는 점이다. 본사가 있는 LA와 한국 오피스 사이에 17시간의 시차가 있다. 한국 오전 시간은 본사의 오후 시간이고 조만간 퇴근 시간이 된다. 아침에 이야기가 마무리 안되면 나머지 이야기들은 그 다음날로 넘어간다. 호랑이 담배피던 시절의 전보와 다름없다. 또 상대방에서 응답을 주지 않으면 하염없이 기다려야 한다.

좀 더 심각한 문제는 텍스트다. 텍스트는 해석이 수반된다. 영어는 한국어와 다른 표현 체계다. 우리가 글을 읽을 때 행간을 읽으라고 한다. 행간은 보낸 사람의 의도이고, 자칫 의도와는 다른 방식으로 그 행간을 해석하거나 될 수 있다. 한국어로 쓰여진 경우에도 이런 경우가 빈번한데 영어로 쓰여진 경우에는 어떻겠나? 개발자/엔지니어로써 사실에 입각해서 이야기를 하고 싶긴 하지만 개발자도 인간이다. 잘못된 행간과 고집이 적절치 못한 커뮤니케이션으로 이어지게 만드는 경우가 발생한다. 이런 일이 발생하면 난감하다. 이야기가 길어지는 모습을 보는 것도 고역이다. 수습 또한 쉽지 않다.

그래서 요즘 사용하는 방법은 직접 얼굴보고 이야기한다. 잉?? 한국 오피스에서 본사에 있는 친구들 얼굴을 어떻게 보고 하냐고? ㅎㅎㅎ 간단하다. 본사로 날라간다. 출장!

한국에서 개발해야할 사안이 중요하거나 빠른 처리가 필요한 경우에는 출장가서 본사 엔지니어들과 함께 일을 한다. 하루에 1 ~ 3번 미팅을 한다. 다행스럽게도 라이엇의 모든 회의실에는 화이트보드가 있다. 말로 안되면 그림으로 소통한다. 현장에서 어떤 프로토콜, RESTful endpoint, 혹은 어떤 시스템으로부터 데이터를 얻는게 합리적인지 바로 결정한다. 추가로 궁금한 사항이 있다면 그 친구 책상에 찾아가면 된다. 한국과의 의사 결정이 필요한 부분이 있다면 그 부분만 정책적인 의사 결정을 한국쪽에서 받으면 된다. 가장 빠르고 의사 소통의 혼선도 거의 없을 뿐만 아니라 서로에 대한 신뢰는 덤으로 따라온다.

물론 그렇다고 문제가 있을 때마다 매번  출장갈 수는 없다. 3등석에 앉아 10시간, 12시간 비행을 하는 건 상당한 체력을 필요로 한다. 시차를 극복하는 건 더 힘들다. 음식도 맞지 않아 변비는 일상이다.

그래서 최근에는 종종 컨콜을 이용한다. 출장을 통해 서로 얼굴을 익혀둔 친구들과는 컨콜로도 필요한 의견을 주고 받을 수 있다.  단, 나의 모국어가 영어가 아님을 그 친구들이 이해해 준다면. 그리고 내가 전달하고 싶은 이야기를 말할 수만 있으면 된다. 유창할 필요도 없다. 떠듬더라도 입밖으로 이야기를 꺼내면 듣고자 하는 상대방이 들을 것이다. 이런 면에서 라이엇 게임즈의 문화는 글로벌에 있는 다른 친구들과 이야기할 자세가 되어 있다는 점이 개인적으로 다행이라고 생각한다.

영어 공부는 어떻게?

읽고 쓰기 위해 따로 공부를 했던 적은 크게 없었던 것 같다. 개발과 관련해서 매일 읽고, 쓰는 것들이 영어이기 때문에 ㅎㅎ

다만 쓰는 표현 자체는 Business school을 나온 사람들의 유창한 표현이 될리 없다. 초반에 내가 영어로 쓴 글들을 가끔 마주치게 되는데, 참 어처구니없는 글이나 문장들이 많다. 사실 요즘에도 슬랙에서 이야기하다보면 단복수형이 틀리는 경우도 허다하다. 하지만 미국 친구들표현이 틀린 건 마찬가지로 허다하다. 그 친구들도 Business English를 쓰는 건 아니니까.

코딩과 마찬가지로 영어도 가장 중요한 건 많이 쓰는 것이다. 그리고 되도록 짧게. 글이나 이야기가 길어지다보면 맥락을 놓친다. 것보다는 짧게 여러 문장으로 나눠 전달하는 것이 효과적이다. 이 방식의 글쓰기는 꼭 영어로 이야기하는 경우만 국한되지 않는다. 한국어로 이야기할 때도 마찬가지고 코딩을 짤때도 마찬가지다. 짧게. 자주 쓰는 것이 큰 도움이 된다.

영어로 글쓰기를 할 때 도움이 되는 것은 온라인 사전이다. 상황에 적절한 단어가 떠오르지 않으면 네어버 영어 사전에 가서 검색해보면 추천이 될만한 단어를 추천해준다. 거기에 추천된 단어를 활용해보자. 가장 피해야할 건 구글이나 네이버의 번역기를 돌리는 짓이다. 물론 그 순간에는 도움이 될지 모르지만 영어를 써야하는게 일상이라면 되려 그 방식은 전혀 도움이 되지 않는다. 문법이나 오탈자가 걱정된다면 Grammarly라는 툴을 사용해볼 걸 추천한다. 광고하는 건 아니지만 영어 문장쓰는데 Grammarly만한 도구를 아직 만나보지 못했다.

듣고 말하기와 관련해서는 1:1 토론 수업을 추천한다. 글쓰기와 마찬가지로 듣거나 말하기 역시 자주 하는 것 이외에 다른 왕도는 없다. 개인적으로 1:1 토론 수업을 해본적은 없지만 만약 단기간에 실력을 끌어올리고 싶다면 이 방법이 최선이지 않을까 한다. 일주일에 1~2번 원어민이 원어민 발음으로 하는 이야기를 듣고, 내가 하는 발음을 교정해주기 때문에 단기간에 가장 효과적으로 발전을 이룰 수 있는 방법이지 않을까 앂다.

영화로 연습하기

회사 일을 하며 사용한 방법은 1:1 토론 수업 방법은 아니다. 나는 특정 영화 1개를 정해놓고, 그 영화의 대사를 반복적으로 듣고, 주요 대사를 가장 흡사하게 따라서 발음하는 방법이다.

라이엇에 다니기 시작하면서 이 방식에 활용한 영화는 아이언맨 1편이다. 아이언맨의 팬이기도 했지만, 그 전부터 아이언맨은 열심히 봐둔 편이었고, 1편의 토니 스타크와 아이언맨의 광팬이었기도 했기 때문에. 2015년부터 2016년까지는 주로 아이언맨을 지하철 출퇴근하면서 봤다. 출퇴근길 집과 지하철역을 오가면서 주요 대사를 따라해보기도 하며, 주인공 토니의 발음을 비슷하게 흉내낼려고 했다.

개인적으로 가장 인상적인 장면은 동굴에서 탈출 후 MARK-II 만들기 시작할 때 예전 MARK-I의 도면을 홀로그램으로 올리는 장면이었다. 영화가 나올 당시에 개발하는 제품에 대한 영감을 원했는데, 이 부분이 깊은 인상을 주긴 했었다. (그래서 당시에 이걸 몇 번이나 개발팀에 틀어줬던 것 같기도 하고…)  영상보다 이어지는 부분에서 가장 기억에 남는 명 대사가 나온다. 개발을 마치고, 비행 시험을 하면서 자비스에게 하는 말.

Jarvis, sometimes you gotta run before you can walk

정해진 규칙에 얽매이기보다는 어느 때에는 틀을 깨고 도전을 할 필요가 있다는 말이 너무 인상적이다. 주변에서 보면 틀이라는 프레임에 안주하는 친구가 있다면 꼭 들려주고 싶은 말이다.

그 다음 영화로는 마션이었다. 아마 마션도 100번 넘게 Playback 하면서 나오는 문장을 듣고 맘에 드는 문장을 따라했다.

두 영화의 공통점은 우선 내가 좋아하는 SF 장르의 영화라는 점이다. 토니 스타크와 마크 와트니가 보여준 역경을 기술을 사용해서 극복하는 모습이 무엇보다도 좋았다.

특히 이 두 영화가 듣기와 말하기기 연습에 좋았던 이유는 주인공의 독백 형식의 대사가 많다는 점이다. 독백 형식이기 때문에 말 속도도 빠르지 않고, 사용되는 단어도 어렵지 않았다. 온전히 한 캐릭터의 감정과 말투에 몰입해서 따라하기가 다른 영화들보다 쉬웠다고나 할까? 다른 영화 몇 편을 가지고도 비슷한 시도를 해봤지만, 주고 받는 이야기가 너무 많아서 따라하는 용도로는 그닥이었다.

그럼에도 실전이다.

1:1 토론 수업이 좋다고 이야기했다. 아무리 영화에 나오는 케릭터를 따라한다고 하더라도 그건 이미 정해진 내용/대사/단어를 반복할 뿐이다. 어찌보면 죽은 영어다. 1:1 토론 수업이 좋은 이유는 살아있는 지금의 영어로 말하고, 이에 대한 피드백을 받을 수 있기 때문이다. 듣는 역량을 키우는 건 여러 방식을 가능하지만 말하기는 다른다. 피드백을 해줄 수 있는 사람이 있다면 좀 더 빠른 발전을 이룰 수 있다.

그런 면에서 보면 나는 개인적으로 좀 무식했다. 일단 출장가서 일을 해야했기 때문에 되던 안되던 필요한 내용을 내 입을 통해 말해야 했다. 와중에 지금까지 출장 가운데 1/4 정도는 혼자 출장을 갔으니까. 제대로 전달이 될 턱이 없다. 같은 문장을 몇 번을 이야기하고, 그래도 안되면 다른 문장으로 바꿔서, 것도 안되면 화이트 보드나 연습장에 적어서 이해를 시켰다. 그럼 그 친구들이 그들의 말로 다시 이야기를 해준다.

Yes, That’s it.

이러면서 하나씩 그 친구들의 말을 배웠다. 제대로 나와 대화하는 사람들과 이야기를 할려면 코드가 맞아야 한다. 사실 전까지는 “Fuck” 이나 “Shit” 같은 단어는 욕이고 절대로 쓰면 안되는 단어로 알고 있었다. 하지만 그들은 일상에 흔히 이야기하는 감정 표현 단어다. 물론 함부로 쓰면 안되지만, 적절한 교감이 있다면 이 단어들은 “이건 말도 안되는데…” 정도를 표현하는 가장 효과적인 “문장”일 뿐이다.

영역(Domain)이 틀려지면 사용하는 영어도 틀려진다. 그러면에서 내가 가장 싫어하는 건 식당에서 주문할 때다. 음식이나 재료의 이름을 내가제대로  알턱이 있나? 더구나 굳이 이걸 힘들여서 공부하고 싶은 생각도 별로 없다. 그래서 가급적 식당은 메뉴가 그림으로 설명된 곳이 가장 반갑다. 관광지에서 해외 여행 몇 번 해보지 않은 와이프가 나보다 더 잘 주문하는 경우를 보면 신기하기까지 하다.

영어도 마찬가지로 의지의 문제다. 익숙해지기 위해서는 많이 써야하고, 많이 접해보는 것 이외에 왕도는 없다. 요즘에도 본사에 있는 친구들이나 다른 지역에 있는 친구들과 컨콜이 잡히면 앞서 이야기한 영화들을 보면서 귀를 풀어주고, 입을 풀어준다. 그리고 해야할 말이 있다고 생각되면 다른 사람의 입을 빌리기보다는 내가 들은 이야기를 바탕으로 내 입으로 이야기한다. 쪽 팔릴 수 있다고 생각할 수 있다. 하지만 상대방 입장에서 같이 일을 해야하는 파트너가 제대로 된 컨텍스트를 얻었는지가 중요하다. 그래서 그 사람을 위해서라도 내가 제대로 이해하고 있는지를 내 입을 통해 확인해 줄 필요가 있다.

처음 라이엇에 입사했던 그때보다는 해외 친구들과 커뮤니케이션하는 능력이 좀 늘어난 건 맞다. 하지만 여전히 부족하고, 그래서 지금도 영어로 읽고 쓰고 듣고 말할려고 한다. 가끔 많이 피곤하면 영어로 잠꼬대를 한다고 가족들한테 욕을 먹긴 하지만,… 뭐 이런 부작용은 눈감아주지 않을까??? ㅎㅎㅎ

– 끝 –

Thoughts after reading Start with WHY and Leaders eat last

책(Leaders eat last)에서 인간의 기본 심리를 4가지 호르몬의 동작으로 정의한다.

  • 엔돌핀(Endorphins) – 생존에 대한 두려움 혹은 살아야 한다고 느꼈을 때. 어느 글에서는 이게 좋은 호르몬이라고 이야기를 했던 것 같지만 결국 두려움이 이 호르몬을 만들어낸다.
  • 도파민(Dorphamins) – 성취. 혹은 다른 사람을 이겼다고 느꼈을 때.
  • 새로토닌(Serotonins) – 다른 사람을 돌보고, 이들을 위해 희생하고자 함. 대표적으로 리더 성격을 가진 사람들이 많이 이런 호르몬을 가진다.
  • 옥시토신(Oxytocins) – 타인에 대해 사랑을 베풀고, 그 자체에서 기쁨을 얻는다.

인간이라는 동물로써 차별성을 가지는 가장 큰 부분은 사회성이다. 조직안에 있을 때 편안함을 느끼는 이유는 생존에 대한 두려움을 덜 수 있기 때문이다. 하지만 경쟁 사회에서 우리는 개인주의를 강요받는다. 특히 이런 개인주의의 팽배는 한국의 경우, 1990년대 말 IMF 시대를 거치면서 보편화됐다. 미국도 원래는 이렇지 않았다. 레이건 정부를 거치면서 이런 정책이 일반화됐다. 자본주의의 팽배는 결국 사람을 사람으로 보는 것이 아니라 물건으로 보기 시작하고 하나의 숫자로써 개인을 취급하면서 보편화되었다.

선사시대 이후로 작던 크던 조직내에 소속된 개인을 돌보는 문화가 하나의 숫자로써 여기지는 시대로 변화되었다. 사회의 가치보다는 개인의 성취 혹은 성공이 더 우선시하는 개인주의가 기본 패러다임이 되었다. 이런 변화는 단순히 자본주의 때문이라기보다는 풍요로움이 기여한 바가 크다고 할 수 있다. 1,2차 세계 대전이라는 고난의 시대를 거치면서 세상은 급속하게 풍요의 시대를 맞았다. 한국의 경우에도 한국 전쟁을 거치면서 소위 베이비부머라 불리는 세대가 나타났으며 70년대 베트남 전쟁과 한강의 기적이라는 산업적 변혁을 맞이하면서 더 이상 배고프지 않는 시대에 진입했다. 80년대 90년대를 초반을 거치면서 “개천에서 용나는” 시대를 관통했다. 당시의 부모 세대는 평생 직장이라는 울타리에서 자신의 자녀 세대들은 자신에게는 없었던 풍요로움을 줄 수 있었으며, 또한 “”이 되어주었으면 하는 희망으로 자녀의 교육에 최선을 다했다. 다른 사람보다는 내가 우선이었으며, 이런 사회 풍토는 미국과 마찬가지로 경쟁 사회로의 진입을 부추켰다. 90년대 IMF 이후로 이런 기조는 더욱 더 남을 돌아보기보다는 자신의 앞을 보고 더욱 더 채찍질 하는 사회를 고착화시켰다.

이런 사회적인 풍토에 제대로 일격을 가한 대표적인 사건이 바로 엔론(Elon)사태고, 2008년 모기지 사태로 촉발된 미국 금융 위기다. 자신들만의 세상(Inner Circle)의 이익을 최우선하기 위해 장부를 조작했으며, 단일 회사 수준이 아니라 누구도 예측못했던 글로벌 위기를 촉발했으며 미국뿐만 아니라 전세계의 많은 사람들이 직업을 잃고 거리를 헤매게 만들었다. 이들 Inner Circle 그룹은 조직의 다른 사람들은 단순히 숫자에 불과했으며, 사람 혹은 동료가 아니라 수단에 불과했던 것이다. 이에 대한 반성이 미국 사회에서는 조금씩 생겨나기 시작했고, 주주우선주의가 아닌 구성원 우선주의가 유행처럼 번지고 있다. Google, Facebook을 포함한 IT 기업들(Amazon은 글쎄???)이 이런 흐름에 동참하고 있고, 가장 대표적인 기업으로 Southwest Airline과 국내에도 있는 Costco가 칭송받고 있다.

한국의 경우에는 어떨까? 기업들의 Moral Hazard로부터 촉발되어 베이비부머 세대 수십만을 길거리로 내몬 IMF 사태 이후, 과도한 인력 유동성을 확보한 기업들은 더욱 더 자기반성없이 성과지상주의로 개인들을 초 경쟁사회로 내몰고 있다. 이제 도구로써의 역할을 다하지 못한 기업의 40, 50대들은 언제 해고될지 모르는 두려움이 떨고 있다. 생존을 위해서는 어찌되었든 옆자리의 동료을 이겨야하고, 더 좋은 라인을 타기 위해서 사내정치에 몰두한다. 그리고 승리의 보답으로 인센티브라는 단물을 챙긴다. 부모 세대들의 10, 20, 30대 자녀들은 어떨까? 그들은 풍요와 경쟁의 한 가운데에 있다. 언제까지나 피말리는 경쟁을 버텨내는 것은 싫지만 지금까지 누려왔던 풍요로움을 포기하기는 싫다. 결국 그들의 선택지는 공무원 혹은 대기업이다. 부모 세대의 경쟁은 점진적이었다. 이에 반해 이들의 경쟁은 바늘구멍같은 좁은 자리를 향해 경쟁해야한다. 결국 경쟁은 일상회되고, 사회는 피로사회로 전환된다. 스카이캐슬은 그들만의 문제가 아니라 사회의 문제이자 시스템의 문제다.

현재의 자본주의 시대는 인센티브의 시대다. 엔돌핀과 도파민의 시대다. 회사라는 조직에 있는 사람들은 언제 책상이 사라질지 모르는 두려움(엔돌핀)을 안고 있다. 그리고 옆자리에 앉은 동료, 팀, 혹은 다른 회사와 경쟁한다. 그들의 경쟁은 몇 개의 숫자로 정의된다. 그 숫자가 맞춰지면 인센티브(도파민)가 지급된다. 이 숫자를 달성하지 못한 사람은 언제 닥칠지 모르는 상실의 두려움에 떤다. 한국의 대표적인 리더들은 항상 숫자를 우선에 둔다. 회사는 매출 목표, 수익 목표. 그 숫자들은 출근길에 쓰러지거나 스트레스에 지쳐 떠난 직원들을 패배자로 규정시킬 수 있도록 리더에게 당위성을 부여한다. Inner Circle에 충성해야하고, Inner Circle이 지정한 숫자에 충성해야 한다. 고객은 숫자이며, 직원은 숫자를 채우는 수단에 불과하다.

우리는 왜 일하는가? 회사와 같은 인위적인 공동체에 개인은 소속의 이유를 갖는다. 공동체의 리더는 조직의 존재 이유를 대변하고, 이 가치를 실현하기 위해 노력해야 한다. 공통체의 일원인 우리는 그 가치에 동의하고 그 가치를 실현하는데 동참하기 원한다. 하지만 이상은 이상이고, 현실은 현실이다. 그리고 공동체의 가치를 설파하고, 실현시키기 위해 노력한 리더도 대부분 이상론으로 존재할 뿐 현실에서는 찾아보기 정말 어렵다. 세상을 혁신하겠다는 스티브 잡스나 세상사람들에게 컴퓨터라는 동일한 권력을 쥐어주겠다는 빌 게이츠가 이런 대표적인 리더이다. 이들이 현업에 있던 당시에 함께 일했던 애플과 마이크로소프트의 직원들은 아마도 이런 생각에 깊이 동감했을 것 같다. 하지만 지금의 이들이 떠나버린 애플이나 마이크로소스프트에 이런 생각에 동의하면서 일하는 구성원들이 얼마나 될까?

하지만 새롭게 생겨나는 대부분의 회사들이 단순히 돈을 벌기위해서 만들어지는 건 아니다. 나름대로 그들이 정의하는 공동체를 위한 목적을 가지고 있으며, 회사의 구성원들 모두 그 목적을 실현하기 위해 다들 노력하고 있다. (혹은 노력하고 있다고 믿고 싶다.) 현실론으로 단순히 돈을 벌기 위해 그 안에 있다는 것 자체는 너무 슬프지 않은가?

이 관점에서 나는 어떤 목표와 목적을 가지고 왜 아침에 일터로 향하는가? 내가 현재의 회사에 조인한 이유는 명확하다.

고객들에게 의미있는 서비스를 만들고 싶고, 그 서비스를 통해 고객들과 공감하고 싶다. 

나는 여전히 이 목표에 충실한가 라는 질문에 지금까지는 “예” 라고 답할 수 있다. 그리고 최근에 이야기된 회사의 가치 역시 이에 부합한다고 생각한다.

 

Player experience first
Dare to dream
Thrive together
Execute with excellence
Stay humble; stay hungry

 

숫자 혹은 성과를 위해 하는 것이 아닌 것은 분명하다.

개발을 포함해 모든 일이 마찬가지지만 개인 혼자서 할 수 있는 일이 아니고 팀 혹은 구성원들과 함께 하는 공동의 작업이다. 따라서 리더로써 해야할 일은 명확하다. 조직의 목표와 비전을 명확하게 함께하는 동료들에게 인지시켜야 한다. 함께 일을 하는데 있어서 동감은 반드시 전제되어야 한다. 동감되지 않는 리더의 비전은 그 사람의 “꿈”에 지나지 않는다. 꿈이 아니라 현실에서 이를 실현하기 위해서는 구성원들의 지원이 필요하며, 이를 위해 동감은 반드시 필요하다.

동감을 통해 우리가 추구해야할 가치는 구성원간의 신뢰다. 공통체가 공동체로써 존재할 수 있는 가장 큰 근간은 신뢰 혹은 상대방에 대한 이해다. 신뢰가 무너지면 조직의 근간이 흔들린다. 사람의 진실성(Integrity)에 대해 의문을 품는 순간, 신뢰에는 금이 간다. 신뢰를 만들어나가는 과정은 쉽지않다. 하지만 무너지는 것은 일순간이다. 특히 요즘과 같이 카톡이나 페이스북 메신저가 일상화된 현실에서 온라인 상에서만 이뤄지는 대화는 우리가 생각하는 신뢰라는 것에 대한 착각을 불러일으키기 십상이다. 텍스트보다는 얼굴을 보고 이야기할 때 오히려 사람사이의 진실함을 파악할 수 있다. 특히나 같은 공간을 함께하는 동료라면 그 사람의 책상으로 가서 이야기를 하는 것이 훨씬 더 효과적이다. 밀레니엄 세대는 이런 오프라인 대화를 부담스러워한다고 이야기를 하지만 자연스럽게 얼굴보고 이야기할 수 있는 기회를 만들고 활용할 수 있도록 하는 것이 필요하다.

비전을 통해 나아가야 할 방향이 정해지고, 이에 대한 공감과 같이 하는 동료간의 신뢰가 바탕된다면 이제 여정을 떠날 수 있다. 이 여정에는 가장 필요한 요소는 헌신(옥시토신)과 배려(세로토닌)이다. 여정을 통해 실현해나가는 과정에서 팀과 개인은 그에 따르는 성취를 이뤄야 한다. 물론 성취의 기본 개념은 일을 완성해나가고 있다는 것이다. 하지만 다른 관점에서 성취의 또 다른 면으로 중요시하는 것은 배움이다. 과정이 항상 성공적일 수만은 없다. 여정에서 잘못된 길로 들어설 수 있으며, 이 과정에서 왔던 길을 되돌아 나와야 하는 경우도 있다. 이 과정에서 배움이 있었다면 이 또한 성취의 또 다른 면이라고 할 수 있다. 이런 경험들이 모두 모여 우리가 이루고자하는 비전을 달성할 수 있다.

성취는 개인의 성장을 위해 반드시 필요하고 이럴 때 느끼는 도파민은 값지다. 어려움을 극복하거나 새로운 배움을 통한 성취는 개인의 성장에 큰 도움이 된다. 그리고 이런 도움이 팀의 작업에 도움이 되고, 다른 사람들의 성장을 자극한다면 더할 나위없다. 하지만 성취가 성과라는 단어와 만났을 때, 그리고 그것을 개인 혼자만의 것으로 만들려고 하는 경우에는 큰 문제가 된다. 이런 사람이 많아지면 많아질 수록 “우리” 보다는 “나” 가 우선한다. 특히나 이런 분위기를 리더가 조장하거나 방임하게 되면 구성원들은 단기 성과에만 매달린다. 시작은 도파민이었지만, 이후에는 엔돌핀이 구성원들을 지배한다.

회사 혹은 조직의 문화에 따라 이런 도파민과 엔돌핀을 무기로 구성원들을 다루는 경우가 있다. 소위 당근과 채찍이라는 이름으로 성과를 강요한다. 이러면 구성원들은 위기감을 느끼고 뭔가를 보여줘야만 스스로의 생존을 담보할 수 있다고 느끼게 된다. 이건 사람이라는 동물적인 유전자를 가지고 있기 때문이지 개개인이 잘못이 아니다. 하지만 이런 병폐가 지속되면 자본의 논리가 조직의 논리가 된다. 사람을 물질화시키고, 투입 대비 효율이라는 가치 명제가 횡횡하게 된다. 구성원이 더 이상 그 효용이 다하면 버려도 되는 재화(Commodity)가 되어 버린다.

조직의 리더가 어떤 방식으로 조직을 이끌고, 이에 구성원들이 동의하는가에 따라 그 조직의 문화가 결정된다. 자본의 논리에 따른 “각자도생“의 문화를 가질 것인지 서로 뒤를 봐주는 공생의 문화를 가질 것인지가 결정된다. 조직장의 스타일이 큰 역할을 담당하겠지만, 이를 실행하기 위한 구성원들이 이에 동감하고 함께하는지도 중요한 역할이다. 결론적으로 내가 추구하는 조직은서로가 서로를 보살피는 문화를 가진 조직이다. 우리는 우리가 해야할 목표가 있고, 이걸 달성하고 성장하면서 오래 함께 할 수 있는 조직을 이루고 싶기 때문이다.

중간 리더는 상당히 어정쩡한 존재다. 모든 것을 결정할 수 있는 충분한 권한(Authority)가 있는 것이 아니고 책임을 지고 싶어도 질 수 있는 위치가 아닌 경우가 태반이다. 결정을 해야할 때 빠른 결정을 내리는 것도 좋은 자질 가운데 하나일 수 있다. 하지만 한국 조직은 수직, 계층 조직이다. 각 조직별로 나름의 사일로(Silo)를 가지고 있으며, 이 영역을 영역밖의 사람이 들어갈려고 했을 때 받는 이물감은 상당하다. 인위적으로 이런 형국을 초래하기보다는 중간 지점에서 접점을 만들고 서로 얼굴 맞대고 최선의 결론이 도출될 수 있도록 기회를 만드는 것이 중간계의 리더가 해야 할 일이 아닐까 싶다. 그럼에도 결론이 안된다면 사일로의 최상단이 서로 논의할 수 있는 관련된 정보를 제공하는 수준에서 마무리하는 것이 최선이 아닐까?

궁극적으로 구성원들 개개인이 심리적으로 Circle of Safey 안에 존재한다는 믿음이 생기도록 만들어야 한다. 준다고 해서 덥석 생겨나는 것도 아니다. 한두달 열심히 한다고 해서 생겨나는 것도 아니다.일상을 통해 체감될 수 있도록 리더와 구성원들이 서로 노력해야한다. 그 결과가 문화로 정착되야 한다. 결론은 문화다.

– 끝 –

 

개발자에게 좋은 직장 혹은 좋은 환경

직업이 뭐냐고 물어보면 “개발자”라고 서슴없이 이야기한다. 개발하는 직장인으로써 “행복하십니까?” 라고 질문한다면 나의 답은 “행복합니다.” 이다. 하지만 “행복”이라는 단어에 고민이 있다. 나는 직장인으로써 행복한 것인지 아니면 개발자로써 행복한 것인지. 혹은 둘다에서 모두 만족과 행복을 얻고 있는 것인지.

전 직장인 네이버에서 일할때도 초반에는 이런 행복이라는 단어를 이야기했다. 그때도 개발자로 시작을 했지만, 성과를 인정받고 일을 리딩하는 팀장이 됐다. 리더는 이럴 것이다라는 나의 자화상과 팀원들의 기대를 섞은 형상이 되기 위해 최선을 다했다. 결론적으로 행복하지 못했다. 팀장, 리더라는 직책을 가진 직장인으로 행복하지 못했고, 개발할 시간을 1도 갖지 못한 개발자로써 행복하지 못했다. 더욱 심각한 문제는 나만 행복하지 못한게 아니라 함께 일하는 팀원들도 행복하지 못했다. 그리고 3년 반전에 현재의 라이엇 게임즈로 이직했다.

이직 후 현재까지 개발자로써 행복하다. 물론 라이엇 게임즈에서도 개발 리더의 역할을 하고 있다. 그리고 앞서 이야기한 것처럼 행복하다. 이 나이에 실제 코딩을 하고 있고, 과정에서 미처 몰랐던 것들, 새로운 것들을 계속 배우는 즐거움이 있다. 그리고 나이먹었다고 굳이 봐주지 않는 까칠한 동료들이 있다. 나는 내 기술과 경험과 코딩으로 사용자들이 실제 사용하는 시스템을 만들고 있고, 좋은 동료들의 도움으로 발전하고 있다. 더구나 새로움에 도전한다고 뭐라하는 말도안되는 거버넌스 같은 것들이 없다. 개발자에게 이만큰 좋은 환경이 있을까? 이렇든 저렇든 코딩을 직접하는 회사에서 이만큼의 행복한 조건을 찾을 수 있을까? 내가 보기에 감히 개발자에게 최고의 환경이다.

라이엇이 좋다고는 했지만 그럼 개발자에게 행복한 직장 환경은 뭘까?

개발이라는 직군(Discipline) 관점에서 살펴보자.

  • 능력이 아닌 실제 코딩 – 어떤 직책(Role)에 있던 개발 직군에 있는 사람은 코딩을 해야한다. 중요한 점은 할줄 아는 능력이 아니다. 실제로 코딩을 해야한다. 자, 먼저 여러분들의 주변을 둘러보자. 코딩보다는 문서를 만들고 있거나, 회의를 하고 있는건 아닌가? 혹은 의미없는 이메일 놀이? 본인의 시간을 코딩하는데 쓸 수 있어야 코딩을 할 수 있다. 그리고 코드를 통해 본인의 존재감을 인정해줄 수 있는 조직 문화가 있어야 한다. 회의에서 한마디 말을 하거나 문서/이메일 쓰레드에 자신의 필력을 발휘해야만 존재감을 인정받는다는 생각을 하는 소위 관료주의적 문화가 있다. 이런 문화는 개발자가 코드로 기여하기보다는 정치질하기 쉽다. 코드짤 시간도 인정하지 않고, 회의에서 말 한마디 못한다고 갈구는 분위기이고, 본인은 개발자로 남길 원한다면 그 조직에 오래 남을 이유는 없을 것 같다.
  • 피드백 줄 수 있는 동료 – 코딩은 글쓰기랑 비슷하다. 글쓰기 능력을 높이는 좋은 방법은 2가지 다. 첫번째는 많이 써보는 것이고, 두번째는 쓴 글을 다른 사람들과 돌려 읽는 것이다. 다른 관점에서 살펴본 피드백은 좋든 안좋든 본인이 작성한 내용을 뒤돌아보게 만든다. 코딩도 마찬가지로 피드백이 있을 때 더 빠르게 발전할 수 있다. 코딩의 피드백을 가장 잘 줄 수 있는 사람은 같은 일을 하는 개발자다. 같은 일을 하고 있기 때문에 코드의 목적을 공유하고 있고, 다른 관점에서 제대로 코드의 문맥이 제대로 읽히는지 봐줄 수 있다. 개발 시간은 3개월이더라도 완성된 코드는 6개월, 1년 혹은 몇년 동안 운영된다. 과정에서 변경은 필수이고, 변경할려면 제대로 읽혀야 한다. 제대로 읽히는지 피드백을 주는 동료가 있다면 그만큼 읽힐 수 있는 코드, 품질 높은 코드를 작성할 기회가 더 많아진다. 자신보다 잘하든 못하든 상관없이 진심으로 읽고 주는 피드백은 충분히 값어치를 한다.
  • 도전 – IT 환경만큼 빠르게 변화하는 세상이 없다. 개발 환경 뿐만 아니라 사용자 환경도 마찬가지다. A 기능이면 만족하던 사용자들이 AAAAA 기능이 아니면 안된다고 하루 아침에 돌변한다. 바뀐 환경에 대응할려면, 본인이 가지고 있던 밥그릇을 버려야 할 때도 있다. 어떻게 해야할까? 개발자라면 도전해야한다. 밥그릇이 아닌 접시를 원한다면 이제 접시를 만들러 가자. 새로움에 도전하는 사람이 있다면 응원과 관심이 필요하다. 성공하든 깨지든 과정의 경험은 도전한 사람에게 값진 경험으로 남는다. 도전이 일상이 될 때 혁신이 이뤄지고, 조직은 더욱 발전한다. 개인의 도전을 장려하고, 그 과정 혹은 결과를 조직 발전의 밑거름으로 삼으려는 조직에 있어야 개발자로써 더 나아갈 수 있다.
  • 성장 – 개발이라는 분야만큼 빠르게 변하는 동네는 없다. 자고나면 새로운 기술과 언어 그리고 프레임웍이 등장한다. 한국에서는 자바(Java)랑 스프링만 해도 충분해… 라는 생각을 가진 개발자들이 현실적으로 많다. 사실 그닥 틀린 말은 아니다. 하지만 이런 마인드가 ActiveX를 욕하면서 아직도 걷어내지 못하고 있는 것이다. Frontend와 Backend의 분리, Microservice Architecture, Cloud 등등이 최신 기술이라고 이야기하던 때는 이미 1~2년 전에 지났다. 프로토콜 관점에서도 세상은 이미 HTTP/2를 향해 나아가고 있고, Serverless와 AI, Machine Learning을 현실과 어떻게 접목할 것인가를 고민하는 시점이다. Java 언어 자체도 Imperative feature보다는 lambda를 필두로 functional language 관점이 주요 발전 뱡향으로 자리잡았다. 또 9 버전 이후 module 방식의 packaging 방식의 변화를 가져오고 있다. 이것들을 종합해보면 Concurrent 환경의 안전한 코딩과 빠른 실행을 담보하기 위한 체계로 전환해야 한다고 이야기하고 있다. 그럼에도 불구하고 당신의 조직이 “이걸로도 충분해” 라고 안주하고 있나? 성장하는 조직이라면 새로움에 대한 탐색과 이를 도전적으로 적용해보고 그 값어치에 대해 논의할 수 있는 것을 장려해야 한다. 이런 논의가 없다면 고인물이 되고, 시간이 흐르면 썩어버린다. 썩은 물은 두말할 필요없이 주변을 오염시킨다.

팀장이나 아키텍트라는 타이틀을 가진 사람들이 흔히 “내가 옛날에 해봤는데 말이야…” 라는 말을 내뱉는다. 옛날 언제? CPU가 1 Core였던 호랑이 담배피던 시절에? 혹은 “오라클 DB면 다 되네..” 라고 감탄하던 시절에? 바뀐 환경과 바뀐 프로그래밍 패러다임을 이해하지 못하는 사람과 함께 일하는 건 불행이다. 넓은 식견과 경험을 변화된 환경에 최적화된 코딩으로 녹여내기 위해 노력해야한다.

우리는 이런 노력이 결실을 맺기 위해 피드백을 주고, 시간을 줄 수 있어야 한다. 이 과정을 통해 성장한 사람이 결국 조직 구성원들에 영감을 준다. 이런 한 사람 한 사람의 노력이 모여 개발자 조직의 성장을 이끈다.

개발자라는 직장인 관점에서 행복한 직장 환경은 뭘까?

Good

  • 실행 가능한 비전과 목표 – 뜬구름잡는 듯한 구호는 의미가 없다. 비전과 목표는 실행 가능한 아젠다를 도출할 수 있어야 하거나, 무엇을 실행할지에 대한 지침이 되어야 한다.
  • 공유 – 무언가를 실행할 때 기술적이든 업무적이든 정보가 필요하다. 이런 정보가 투명하게 공개되고, 원할 때 찾아볼 수 있는 형태로 존재해야 한다. 그리고 다른 사람들에게도 이런 정보가 있음을 알려줘야 한다. 그래야 혼선을 최소화할 수 있고, 빠른 업무 진행이 가능하다.
  • 성취와 축하(Celebration) – 어떤 일이 됐든 방점이 없는 일은 사람을 지치게 만든다. 반드시 우리가 이 목표를 달성했다라는 성취감을 느낄 수 있어야 한다. 이 성취감을 통해 다음번 고지를 향해 나아갈 수 있는 자신감을 얻을 수 있다. 그리고 그 성취를 모두가 함께 축해해줘야 한다. 개인의 성취보다는 우리 혹은 팀의 성취가 되어야 하고, 그 성취를 조직의 모든 사람들이 다 같이 축하해줄 수 있어야 한다. 이런 시점에 회식은 꿀맛이다.
  • 실패 – 2보 전진을 위한 1보 후퇴다. 누구나 실패할 수 있다. 하지만 실패를 통해 배운 교훈이 있고, 이 교훈이 다음번 일을 하는 발판이 되어야 한다. 가장 어이없는 건 실패를 실패 자체로 비난하는 것이다. 이러면 누구도 두려워 일을 못한다. 아마도 성공할만한 일만 골라서 하지 않을까? 실패에서 팀이 어떤 Lesson and Learn이 있었는지, 그리고 같은 실수를 두번 되풀이하지 않기 위해 어떤 방식으로 다음번 일을 준비하는지를 봐야한다.

Bad

  • 경쟁 혹은 사내 정치 – 누구를 위한 경쟁인가? 아이러니컬하게도 사람은 3명 이상만 모이면 정치질이라는 걸 하게 되있다. 성과 중심적인 직장내에서는 특히나 이런 정치질이 심각하다. 내가 남보다 잘해서 성과를 받기 보다는 남을 깍아내려 상대적인 우위를 점하기 위해 이런 짓을 하는 경우가 심심치않다. 일에 대한 값어치는 “고객, 동료”들에게 평가받아야 하지만, 의도적인 깍아내림은 조직을 좀먹는다.
  • 단기성 금전적 인센티브 – 성과에 대한 보상으로 인센티를 왕창주면 어떻게 될까? 그리고 다음번 평가시에 이만큼의 인센티브를 받지 못하게 되면 어떻게 생각할까? 그리고 왕창 인센티브를 받지 못한 다른 팀 혹은 구성원은 어떤 생각을 가지게 될까? 결국 인센티브를 받을만한 일들만 골라서 구성원들이 할려고 든다. 왕창받지 못한 사람은 저성과자라는 인식을 스스로 하게 되거나, 혹은 성과 불평등에 대해 이야기하기 시작한다. 그리고 자신만이 높은 성과를 받기 위해 인위적으로 정보를 숨기거나 협업을 말 그대로 방해한다.
  • 회의(Meeting) – 회의는 말 그대로 여러 사람이 같이 머리를 맞대고 논의하기 위한 자리이다. 그리고 그 자리에선 대부분 결론이 도출되야 한다. 하지만 본인의 존재감을 드러내기 위해 회의에 들어온다. 하지만 그닥 참여한 의미는 없다. 되려 회의 시간만 길어진다. 최악의 회의 결론은 다음번 회의를 하자라는 것이다. 이런 회의가 비일비재하다. 회의가 소집되었다면 회의록이 있어야 하고 결론이 도출되어야 한다. 그리고 관심있는 사람들에게 회의록이 공유되면 된다. 본인의 소중한 1시간을 허비할 필요는 없이, 관심있다면 회의록을 살펴보면 된다.

불행한 직장인은 누구일까? 아마도 매일 매순간을 성과에 얽매여 두려움에 사는게 아닐까 싶다. 성과라는 것을 아예 고민하지 않을 수는 없다. 개인적인 주관이지만 개발직군에 있는 직장인으로써 이런 성과에 직접적으로 연연하기보다는 본인의 성장이 성과와 자연스럽게 연결될 수 있는게 최선이 아닐까 싶다.

본인 성장을 이룬 다는건 주변의 협조없이는 불가능하다. 그리고 조직내에서 이런 협조는 조직의 문화와 직접적으로 관련되어 있다. 그리고 궁긍적으로 이런 문화가 유지되고 발전되는건 그만큼 조직장 혹은 리더의 역할이 중요하다. 다행스럽게도 라이엇게임즈 코리아에는 이를 강력하게 지원해주는 리더십이 있다. 물론 이런 리더십을 뒷배경으로 각 개인이 열심히 하는 부분들이 있기 때문에 문화가 유지 발전되는게 아닐까 싶다.

새로운 2019년이 시작되었고, 앞으로도 기술 및 개발 조직 문화가 발전될 수 있는 환경이 되도록 일조해야겠다.

Kafka broker memory leak in 0.10.x version

Kafka 클러스터를 한국 개발팀에서 운영한지도 한 2년 넘은 것 같다. 메시징 시스템이라고 하면 뭔가 대단한 것 같았는데, 실제로 시스템을 디자인하고 운영하다보니 별거 없더라는… 라고 뭉개고 싶지만 사실 숨기고 싶은 진실이 하나 있었다.

개발 과정에서는 이 문제를 찾을 수 없었는데, 운영을 하면서 나타난 문제점이 있었다. 카프카라는 메시지 큐가 실제로 Business Logic이라는 걸 처리하는게 없다. 또 저장하는 데이터의 보관 주기 역시 그닥 크지 않다. 그런데… 왜… 아무리 Heap Memory의 크기를 늘려줘도 Full GC 이후에 카프카의 Java 어플리케이션의 메모리가 반환되지 않고 CPU는 미친듯이 날뛸까? 최초에는 넘 메모리를 적게 줘서 발생한 문제점인가 싶어서 메모리를 2G부터 9G까지 꾸준히 늘려갔다. 재시작 후 OOM을 외치는 시점이 좀 늘어나긴 했지만, 변함없이 찾아온다. 별수 없이 매번  Kafka Broker Application을 내렸다가 올려야 한다. 올리면 잘 돌아간다. 하지만 매번 해야한다. 시스템은 돌아가야 하니까…

해외 Conference에서 들었던 사례들은 무시무시한 수량의 노드들을 운영하고, 아무것도 거칠것이 없었다. 하지만 내가 만든 이 간단한 물건은 뭐가 문제길래 매번 CPU 혹은 Health Check Alarm을 걸어두고 알람이 올때마다 매번 내렸다가 올리는 작업들을 해야만 하는걸까? Kafka에 데이터를 보내고 받아오는 코드들을 이리저리 살펴봤지만 그닥 큰 문제점들이 보이지는 않는다. 그럼에도 OOM의 Root Cause를 확인할 방법이 뾰족히 보이지 않는다. 결국 취할 수 있는 방법은???

별 수 있나. 하루에 한번씩 내렸다가 올리는게 가장 안전한 방법이다! 쪽팔리지만 내가 작성한 코드 수준에서 해결할 수 없는 방법이지만 운영은 되어야 하기 때문에 이 방법을 취했다.

그리고 2주 전까지는 잘 살아왔다. 2주 전쯤에 보안 강화를 위해 기존에 데이터를 받던 방법을 개선했다. 개선하면서 제대로 데이터가 들어오게 됐다. 와우~~

그런데 데이터가 많이 들어와도 너무 많이 들어온다. 정말 너무 많이 들어왔다. ㅠㅠ 하루에 한번씩 OOM을 내던 놈이 이제는 24시간을 버티질 못하네. 이런… 이제 12시간마다 한번씩 내렸다 올려야 하나?? 완전 닭짓인데, 이게 운영인가? 근데 데이터가 너무 많이 들어는데 이 데이터를 살펴봐야하지 않을까? 따지고 보니 데이터를 수집하는 시스템을 잘못 이해하는 바람에 데이터가 비정상적으로 많이 들어왔다. 이 부분을 정리하고 났더니 전반적인 데이터 처리는 줄었지만 여전히 이전에 비해서 처리하는 데이터는 확연히 많다. 여전히 24시간을 버티지는 못한다. 역시 같은 질문! 이게 운영인가?

Kafka broker application이 돌아가는 EC2 Instance를 높은 사양으로 변경도 해보고, broker configuration도 이리저리 바꿔봐도 약간의 차이는 있지만 OOM은 매번 발생한다. 이럴리가 없는데…

그래 이건 Kafka Bug다. 사용하던 버전이 0.10.1.0 이었다. 0.10.X 버전에서 발생하는 메모리 관련된 내용들을 구글링해보니 이것저것 많이 나온다. 이런 제길…  그래 버그가 있긴하구나.

(PP-1841) Kafka memory leak

Kafka 버전을 살펴보니 어느새 2.1.0 버전이다. 내가 시스템을 셋업할때는 최신 버전이 0.10.X 버전이었는데 2년 사이에 엄청 발전한 모양이다. 사이에 한번 나도 버전업을 하긴 했지만 운영에 신경을 많이 쓰지 못한 건 맞는 사실이다. 뭐 하루에 한번씩 재시작시키는 걸로 운영을 퉁쳤으니까 할말 다했다. 🙁

기존 버전으로 최신 버전인 2.1.0 버전으로 올렸다. 재시작시키는 Cron Job을 없애고 몇일 두고 봤다. 이쁜 그림을 그리면서 잘 돌아간다. 관리를 제대로 안하고 툴을 욕했던 내가 한참을 잘못했다.

이제 자잘한 말 그래도의 운영 이슈가 남아있지만, 이제는 비겁한 재시작은 없다. 2년이라는 시간이 걸리긴 했지만, 그럼에도 불구하고 제대로 시스템이 운영되기 시작하니까 마음이 한결 놓인다.

Spring 5 reactive programming ground zero

Spring framework에서도 5.X 버전부터 Reactive 방식의 프로그래밍이 가능하다. 이게 한 1년 이상 전 이야기인 것 같다. 내 입장에서 좋기는 한데 이게 그림의 떡이었다. 대부분의 Java Backend 개발을 Springboot framework을 가지고 하고 있는데, 여기에 Spring framework만 5.X 버전으로 덜렁 넣을 수 없기 때문이다. Spring 5.X 버전을 지원하기 위해 2.X 버전이 개발중에 있었지만, Milestone 버전이었고, 옆에서 Early Adopter 기질을 가진 친구가 고생하는 모습을 보니 아직은 때가 아닌 것 같았다. 아직은 스프링부트 1.X 버전이나 잘 쓰자 생각했다.

한 반년 사이에 Springboot 2.X 정식 버전이 릴리즈됐다. 하지만 바쁘다는, 2.X 버전으로 부트 버전만 변경하는게 별 의미없다는 핑계들을 가져다대며 미적댔다. 그 사이에 다른 친구들은 프로젝트에 Spring Reactive를 Streaming 방식으로 적용해서 성공적으로 마무리를 시켰다. 사람들에게 이제 Reactive의 시대니까 백엔드도 이 방식으로 개발을 해야한다고 떠들고 다녔다. 어이없게도 정작 내가 개념을 입으로만 떠들고 아는체하고 있는게 아닌가? 가장 싫어하는 짓을 내가 하고 있으면서 그걸 모른체하고 있다는게 어이없긴 하다.

출장중에 잠못드는 밤이 많고, 그 시간에 꾸역꾸역 억지로 잠을 청하느니 차라리 밀린 숙제나 해야겠다라는 심정으로 자료를 찾아봤다. 간단한 몇번의 구글링만으로도 뭔가를 시작해볼 수 있는 자료가 화면을 가득 채운다. 나만의 썰 몇 가지를 주절여보고, 검색 결과로 나온 아주 괜찮은 몇가지를 추려봤다.

Reactive Programming의 개념부터 정리해보자.

단어적인 의미를 직역하면 “반응형” 프로그래밍이다. 반응한다라는 것으로 어떤 의미일까? 주체적으로 동작하는 것이 아니라 외부 요인에 의해서 동작이 실행된다라는 것을 의미한다. Frontend 환경에서는 이런 반응형 프로그램이 ReactJS와 같은 Javascript framework의 발전과 더불어 보편적으로 채택되고 있다. UI 환경의 동작을 실행시키는 주된 요인은 사용자의 키보드 입력 혹은 마우스 클릭과 같은 이벤트와 Backend 서버에서 보낸 데이터의 “도착” 같은 것들이 대표적이다. 이런 요인들을 Browser 혹은 Framework이 Event/Promise와 같은 형태로 Trigger 시키고, 이를 Listening하는 개발자의 코드가 실행되는 모델이다.

Backend 환경은 User event와 같은 것들이 없다. 다만 IO를 중심으로 데이터가 “반응”을 촉발시키는 매체가 된다. Synchronous 환경은 직렬화된 데이터 처리를 강요한다. 반응형이 될려면 당연히 Asynchronous IO 기반의 데이터 처리가 기본이 되어야 한다. 사실 컴퓨터라는 것 자체가 Asynchronous하게 동작되는 물건인데, 개발자가 편하라고 Synchrnonous progrocessing을 지원했던 건데 세월이 지나보니 다시 과거의 개념으로 회귀한 것이다. 뭐 물론 다른 차원의 이야기이지만.

스프링의 언어 기반인 자바는 최초에 Synchronous IO만 지원했다. 그러다 Java 1.4 시점에 NIO라는 컨셉으로 Asynchronous IO를 지원하기 시작했다. 내가 Open Manager 2.0 버전을 설계하던 즈음에 이거 나온거보고 이거다 싶어서 작업을 했던 기억이 아련하다. Async IO의 장점은 데이터를 읽어들이는데 있다. Sync IO의 경우 자신이 지정한 데이터가 도착할때까지 무작정 기다려야한다. 반면에 Async는 데이터가 도착했는지를 확인하고, 도착하지 않았다면 그 사이에 다른 작업을 수행하면 된다. 원래 IO 처리 대상이 되는 socket은 Duplex Channel 방식을 지원한다. 즉 한 socket의 FD값을 알면, 두 개 이상의 쓰레드에서 읽고, 쓰기를 동시에 할 수 있다. 그리고 Async IO는 이와 같은 duplex channel 방식의 통신이 지원되는 기반에서 동작한다.

통상 이런 일련의 흐름을 개발자가 모두 코딩하기 어렵다. 그래서 필요한 것이 일종의 엔진이다. 엔진은 데이터가 도착했는지 확인하고, 도착한 데이터를 요청한 대상(Subscriber or Listener)에게 데이터를 넘겨 처리한다. 간단히 설명했지만, 이를 실제로 동작하게 하기 위해 Thread Pool, Queue, Publish and Subscribe 등이 기본적으로 갖춰져야한다.(2004년에 이걸 다 처음부터 끝까지 구현했다라는게 놀랍기는 하다.)

Spring framework 5.0 / Springboot 2.0 버전은 위와 같은 실행 모델을 지원하기 위한 전체 틀을 지원한다. 여기에는 실제 작업을 수행하는 Web Controller 수준의 작업 정의(Get/Post/Put… Mapper)와 내부의 데이터 수행을 위한 Mono/Flux와 같은 이벤트 방식의 실행 모델 및 이를 지원하기 위한 API 집합등을 포함한다. 또한 기반 웹서버와의 통합도 아주 중요하다.

Servlet이 특정 쓰레드에 의해 단독으로 처리되는 방식이 아닌 이벤트 방식으로 동작되어야 최적의 성능을 낼 수 있기 때문이다. 때문에 Netty를 쓰라고 권고하는 것이고, 톰캣은 너무 덩치가 커서 이 방식으로 전환하기에는 적절하지 않아서 동작되도록만 맞추고 최적화는 Give Up 한 것으로 보인다.

Googling…

기초 개념을 알면 이제 뭘 해야할까? 개발자라면 가끔씩 무턱대고 코드를 짜보는 것도 나쁘지는 않다. 그래도 무작정하는 것보다는 잘 파악한다음에 하는 성격을 가진 분들도 있다. 그런 분들에게 도움이 될지 모르겠지만 찾아본 자료들 가운데 Wow 했던 자료들 몇개를 링크한다.

  • Servlet or Reactive Stacks: The Choice is Yours. Oh No… The Choice is Mine! – Rossen Stoyanchev – 2017년 SpringOne 발표된 자료인데, 시류가 Reactive라고 무조건 MVC(Sync)를 버리고 Reactive 방식을 따르지 말라고 충고한다. Pivotal에서 Reactive를 밀고 있는 사람인데 약파는 말이 아니라 쓰는 사람 관점에서 이야기를 한다. Awesome!! 물론 더 다양한 팁과 사례도 소개된다. 1시간 10분 남짓 분량인데, 조곤조곤 이야기를 설명을 참 잘한다.
  • Web on Reactive Stack – 스프링쪽에서 만든 WebFlux의 How-To 문서이다. Spring MVC 모델에 대한 개념이 있다면, 이 문서를 읽어보는 것만으로도 뛰어다닐 수 있을 것이다.
  • Mono and/or Flux – Spring Reactive Programming을 하기 위해 제공되는 객체 모델은 Mono와 Flux로 나뉜다. 이름이 의미하듯이 Mono는 1회성 데이터의 Reactive model을 위해 사용되고, Flux는 Streaming 방식의 Reactive Model을 위해 사용된다. Javadoc 문서인줄 알았는데, 비동기적 데이터의 흐름이 어떻게 되는지 제공되는 method 별로 그림을 곁들여 아주 잘 설명하고 있다. 읽어보는 것만으로도 개념을 이해하는데 많은 도움이 된다.
  • Which operator – Mono/Flux를 사용해서 개발을 하더라도 Fully Reactive하게 코드를 작성하는게 만만하지는 않다. 이미 Synchronous 환경에 생각이 굳어져있어서, 특정 상황에 어떤 method를 쓰는게 좋을지 까리까리한 경우가 종종 발생한다.  이럴 때 이 문서를 참고하면 상황별로 어떻게 Reactive task를 시작하는게 좋을지 혹은 병렬로 처리된 2개의 Reactive Task를 Merge 시킬지에 대한 아이디어를 얻을 수 있다.

 

Springboot 2.X에서 Reactive project setup

오래 해보지는 않았지만, 가장 좋은 설정 조합은 WebFlux만을 사용해서 프로젝트를 셋팅하는 것이다. 이렇게 결심했다면 다음의 설정으로 처음 시작을 해보는게 좋다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

물론 원한다면 spring-boot-starter-web artifact를 추가할 수 있다. 이려면 MVC 방식과 Reactive 방식 모두를 사용해서 코드 작업을 해볼 수 있다. 단점은 MVC 방식이 훨 쓉기 때문에 원래 할려고 했던 Reactive를 금새 포기하게 될거라는거. 이왕 할려면 맘을 독하게 먹는게 좋지 않을까? 한가지 덧붙힌다면 starter-web artifact를 사용하는 경우, 기반 어플리케이션 서버가 Tomcat 이라는 점. 위의 구글링한 결과 가운데 첫번째 동영상 링크를 봤다면 알겠지만, 톰캣은 Servlet 요청을 Synchronous 방식으로 구현했다. 돌려 말하면 요청의 시작부터 끝까지를 완전 Reactive 방식으로 처리가 안된다. WebFlux 단독으로 사용하면 Servlet 스펙을 Asynchronous하게 구현한 Netty가 기반 어플리케이션 서버가 된다. Full Async 혹은 Reactive 방식으로 구현이 가능하다. 물론 starter-web artifact를 사용하더라도 설정을 추가로 잡으면 Netty를 쓸 수 있다. 하지만 할거면 제대로 해보라는게 충고 아닌 충고다. 영역한 동물은 쉬운 길이 있으면 굳이 어려운 길을 고집하지 않고 쉬운 곳으로 방향을 잡기 마련이다.

Spring reactive programming에서 아래와 같은 2가지 실행 모델을 지원한다.

  • Mono – 단일 값에 대한 처리
  • Flux – 서로 독립적인 복수 값들을 처리. 스트리밍 방식으로 떼이터를 처리할 때 주로 사용한다. 스트리밍 방식의 개념은 대강 할지만 여기에서 썰을 풀정도는 아니라서 스킵!

설명을 위해 이제부터는 Mono를 가지고 계속 이야기를 하겠다. 그림과 같이 Mono는 Flow를 가진다. Reactive programming이란 우리는 흐르는 과정에서 실행되길 원하는 코드를 lambda를 거치게 만드는거다. That’s it. 간단히 보자면 간단하다. 약간 어려운 부분들이 있다면, 대강 아래 같은 것들이지 않을까 싶다.

  • Mono가 가지는 Value object의 immutable refernece를 갖는다. 중요한 점은 reference가 immutable이라는거지 value object의 state(or value)는 변경 가능하다라는 점이다.
  • map() 혹은 비슷한 함수를 사용해 다른 타입의 Value object를 만들었다면, 그 객체에 대한 새로운 Mono가 만들어지고, 새로운 흐름이 만들어진다. 대부분의 경우에는 하나의 흐름을 상대하기 때문에 굳이 이런저런 걱정을 할 필요는 없다. 그리고 다른 흐름이 생겼다고, 원래 있던 흐름을 어케 정리하거나 할 필요는 없다. 이게 쓰레드나 파일과 같이 시스템의 리소스를 잡아먹는 그런 어마무시한 놈들은 아니라서.
  • 여러 Mono들의 값들을 한꺼번에 다뤄야하는 경우가 있다. Mono는 흐름이지 직접적인 실행은 쓰레드 풀에 있는 언놈일지도 모르는 쓰레드가 담당한다. 기대 결과를 기다리는 Mono들이 준비가 된 상태인지를 확인하고, 그 상황에서 우리가 지정한 function이 실행되기 위해 zip() 혹은 when()과 비슷한 기능들을 활용할 수 있다.
  • Java 개발자들이 lambda를 좋아한다고 생각안해서 그런지 모르겠지만, BiFunction() 혹은 TriFunction() 같은걸 쓰는 경우가 종종 발생한다. 자바 언어가 Strong typed language이기 때문이라고 추측을 해보지만, 무식하게 생겼다라는 느낌을 지울 수 없다. Mono/Flux 클래스에서 지원하는 메소드들이 이런 것들을 많이 활용하기 때문에 어떤 방식으로 Lambda 함수를 받아서 처리하는지 사전에 알아두는게 좋다. 안그럼 좀 많이 헷갈린다.

가장 많이 등장하는 그림이 아래 그림과 같다. 흐름이 종료되지 않으면 계속 그 흐름 과정에서 객체는 살아있다. 그리고 다른 객체 타입으로 변환되어 만들어지면, 그 순간 새로운 모노가 만들어진다.

괜히 궁금해지는 부분! Mono 객체가 종료되지 않은 상태로 있다면 이 객체는 Garbage collection 대상이 되나? 이전의 테스트 상황에서 GC에 대한 부분을 구체적으로 살펴보지 않았지만 30분정도 부하 테스트에서 성능상 큰 문제가 없었던 것으로 봐서 GC 처리되는 것 같기는 하다. 최근에 하도 GC 문제 때문에 고생을 좀 해서 그런가 그래도 안전하게 할려면 안전한 종료 처리를 하는게 맞지 않을까 싶다.

이런 이해를 바탕으로 간단히 작업해본 셧다운(Shutdown) 로직 가운데 일부다.

  @GetMapping("/{game}/{id}")
  @ResponseStatus(HttpStatus.OK)
  public Mono<PlayPermission> canPlayTheGame(@PathVariable String game, @PathVariable String id) {
      return playerService.identifyPlayer(id)
                          .doOnSuccess(playerInfo -> ifFalse(gameService.isPlayableAge(game, playerInfo.calculateAge()), NotAvailableAgeResponseException.class))
                          .map((playerInfo) -> new PlayPermission(game, id, Permission.ALLOWED));
  }

PlayerService에서 구현한 identifyPlayer는 WebClient를 통해 RESTful response를 받는다.

  public Mono<PlayerAccount> identifyPlayer(String id) {
    return webClient.get().uri("/api/v1/account/" + id)
                    .retrieve()
                    .onStatus(Predicate.isEqual(HttpStatus.NOT_FOUND), response -> Mono.error(new NotExistingPlayerResponseException(puuid)))
                    .bodyToMono(KasPlayerDto.class)
                    .map(kasInfo -> buildPlayerAccount(kasInfo));
  }

So how about?

요 설정을 기반으로 했을 때 성능 테스트 결과를 얻었다.

1000 Concurrent User를 m4.xlarge(4 Core, 8G Mem) 장비를 대상으로 실행했을 때, 466 TPS를 보였다. 재미있는 건 평균 응답 시간이 1초(1044 ms)다.
딱 곧이 곧대로 보자면, 4개의 Core로 처리할 수 있는 작업이 4개라는 이야기다. 음?

물론 곧이 곧대로 세상을 보지는 않을 것이다. 해당 Thread가 IO처리를 하면, 당연히 OS는 그 쓰레드를 Context Switching시키고, 다른 쓰레드를 CPU에 올려서 일을 시킬 것이다. 즉 처리량을 늘릴려면 쓰레드를 정량적으로 늘리면 된다. 하지만 일정 개수를 초과하는 쓰레드는 Context Switching 비용만을 증가실킬 뿐 효율성의 향상을 초래하지는 못한다.

여기에서 설명한 예제는 2개의 외부 연동 포인트를 가지고 있다. 첫번째는 회원 정보 연동을 위해 External Service를 RESTful endpoint로 요청하는 구간이고, 해당 계정 사용자가 해당 시간에 시스템에 접근하는 것을 허용할지 말지를 조회하기 위한 Repository 조회다. 각 단위 연동 시간이 아래와 같다고 하자.

  • External RESTful query – 60ms
  • Repository query – 20ms

이외 부차적인 JSON Serialization/Deserialization 등등을 위해 소모되는 시간까지 고려했을때, 총 소요 시간은 계산하기 쉽게 100ms라고 가정하자. Synchronous한 방법으로 Transaction이 처리된다고 가정하면 1개 Core에서 초당 수행 가능한 건수는 10건이다. 4개 Core라고 하면 단순 계산으로 40건을 처리할 수 있다.

Business people standing with question mark on boards
헐… 근데 근데 부하 테스트 결과가 466 TPS라고? 사기아님?

사기라고 생각할 수 있지만, 위의 그림을 보면 납득이 될 것이다. 실제 어플리케이션의 쓰레드를 통해 실행되는 Code의 총 실행 시간은 20ms 밖에는 되질 않는다. 나머지 시간은 외부 시스템들(여기에서는 External Service와 Respository)에게 정보를 요청하고, 그 결과를 받는걸 기다리는 시간이다. 따라서 전체 CPU의 시간을 온전히 어플리케이션의 수행을 위해 사용한다면 50(1 core당 처리 가능한 Transaction 수) x 4 = 200개를 처리할 수 있다!

쉬운 이해를 위해 어플리케이션 자체 처리 시간을 20ms로 산정했지만, 실제 작성된 코드는 아름다운 최적화 알고리즘과 데이터 구조로 내부 처리 시간은 10ms안쪽에서 실행되기 때문에 466TPS 라는 숫자가 나올 수 있었다. 🙂

근데 MVC 방식으로 하든 Reactive 방식이든 정말 성능에 영향을 미치나? 당연히 미친다. 왜? 어떤 사람은 IO가 발생하면 쓰레드는 Context Switching되고, 다른 쓰레드가 CPU에 의해 실행되기 때문에 성능은 비슷해야하는거 아니냐고 반론은 제기할 수 있다. 틀린 말이기도 하고 올바르게 문제를 지적하기도 했다. 성능에 영향일 미치는 요인은 바로 Context Switching에 있다. Context Switching 자체도 결국 처리는 CPU에 의해 발생된다. 시스템이 CPU를 많이 잡아드시면 드실수록 사용자 프로세스(어플리케이션)이 CPU를 실제 일을 위해서 사용할 시간이 줄어든다. 바꿔말하면 열일할 수 없다.

Reactive 방식으로 열일 시킬려고 할 때 항상 주의해야할 부분이 있다. 바로 IO에 대한 처리다. IO 처리를 User code 관점에서 처리하면 안된다. 이러면 비용대비 효율성이 떨어진다. 이유는 IO에 대한 관리를 Reactive Framework에서 관리해줄 때 최고의 효과를 볼 수 있기 때문이다. User code 수준에서 IO에 대한 주도권을 가지면 IO 처리가 완료됐을 때 User code의 쓰레드가 이를 직접 제어해야한다.

이 말은 쓰레드가 위의 그림에서처럼 기다려야한다는 의미이고, Context switching을 이용해야한다는 의미다. 그렇기 때문에 Reactive Framework에서 제공하는 WebClient 혹은 Reactive한 Repository들을 사용해야 한다.

상상도이긴 하지만 실제 Reactive의 경우, 쓰레드의 Context에 의존하는게 아니라 각각의 Queue를 통해 User code의 control을 제어한다. 마찬가지로 IO에 대한 요청 역시 User code에서 이를 직접 제어하는 것이 아니라 Reactive Repository를 통해 필요한 데이터 Request를 위임한다. 그러면 IO Dispatcher 같은 놈이 데이터를 Connection Pool을 통해 실제 데이터 저장소 혹은 External Service에 전달한다. 이 과정에서 Async IO를 하기 때문에 굳이 Connection을 물고 기다리는 것이 아니라 계속 다른 요청을 전달한다. 물론 Connection Polling을 통해 특정 Connection에 응답이 도착하면 이를 관련된 Request에 Mapping하고 궁극적으로는 Mono/Flux 객체를 Reactive Queue에 넣어서 쓰레드가 어플리케이션 수준에서 다음 작업을 이어가게 한다. 이러한 이유로 RESTful Request를 처리하기 위해서는 Reactive에서는 RestTemplate이 아닌 WebClient를 사용해야 한다.

이 과정을 통해 시스템에 의한 개입을 최소화하여 어플리케이션 수준의 Performance를 극대화하는 것이 Reactive의 핵심이다.

Reactive programming에서 주의할 점들

일반적인 Sync 코딩 방법과는 달리 몇가지 부분들을 주의해야한다. 이 섹션은 앞으로 나도 작업을 하면서 보완해나갈 예정이다. 얼마나 잘 오류를 만들지에 따라 내용이 풍부해질지 아니면 빈약한 껍데기로 남을지는 모르겠다.

  • 코딩 관점에서 반드시 생각할 점은 실행되는 쓰레드를 기다리게 만들면 안된다는 것이다. 로직을 실행하기 위해 필요한 데이터를 받아야 한다. RESTful API, NoSQL, MySQL이든 이건 IO를 통해 받기 마련이다. 이 과정에 Synchronous 한 부분이 들어간다면, 연산을 해야할 쓰레드가 불필요하게 대기해야한다. 이러면 Async 방식의 효율성이 크게 저하된다.
  • 비슷한 맥락으로 Mono/Flux에서 지원하는 block() 함수도 실제 런타임 코드에서는 사용하지 말아야 한다. block() 함수는 연관된 Async 동작이 모두 완료될 때까지 현재 쓰레드를 마찬가지로 대기하게 만든다. 디버깅을 위해 한시적으로 사용하는 용도로는 가끔씩 사용할 수 있다.

Software developer vs Software engineer vs Full stack developer

O’Reilly에서 보내주는 뉴스레터 메일에 가끔 재미있는 글이 있다. 오늘자 메일에 Software 분야에 일하는 사람들의 직군 호칭에 대한 레딧 이야기가 있다. 용어만 보면 나도 가끔 뭐가 뭔지 헷갈리는데 확실히 정확한 정의는 없는 것 같다. 각 호칭들에 대한 주관적인 생각들이 댓글로 달렸다. 주관적이지만 분류도 있고, 개인 경험을 대비한 솔직한 이야기들이 솔솔하다.

여러 댓글들 가운데 맘에 드는 글은 요글이 아닐까 싶다.

Here’s are my very simplistic definitions:

Software Developer

You know one, maybe two, programming languages well enough to implement a somebody else’s design.

Software Engineer

You know how to learn any language, how to choose the right one for the problem you need to solve and can create new designs.

Full Stack Developer

You’re a Software Developer that can work on both front-end and back-end software.

In my experience, a lot of people who consider themselves software engineers lack the adaptability and competency to make good tool and design choices.

개인적으로 제일 맘에 드는 일은 developer 역할을 수행할 때다. 가장 편하게 코딩을 즐길 수 있으니까.

분류를 나누는게 과연 의미가 있을까? 종종 이런 질문이 생각든다. 우리가 평사원/대리/과장/부장 계층을 나누듯 개발 분야에서도 주니어/걍/시니어/아키텍트… 다양한 색깔의 완장을 둔다. 그리고 이걸 내세워서 경쟁하게 만든다. 치열한 경쟁의 결과물인 완장은 조직 안에 자신의 권력을 보장한다. 혹은 “보장한다” 라는 환상을 심어준다. 지내고 보면 쓸데없는 짓이고, 괜한 맘고생을 뒤짚어 쓰는 일이다.

개발자는 개발에 집중하고, 좋은 코드를 작성하고, 그 일과 과정이 즐거우면 된다. 이 일에 충성하는 사람을 잘 관찰하고 성장의 과정이 조직의 발전과 괘를 맞춰줄 수 있도록 충고를 해주는 관리자가 있다면 그 조직은 흥할 것이다.

개발자는 관리자가 되어서는 안된다. 이유는 뻔하다. 그 관리자와 다른 개발자들이 어떻게 PR 리뷰를 편하게 주고받을 수 있겠나? 동등한 관계에서 주고받는 리뷰와 피드백은 개발자 성장의 가장 큰 밑거름이다. 지식과 경험의 많고 적은 차이가 있을 뿐이지 코드를 작성하고 하나의 어플리케이션을 만든다는 점에서 같은 위치에 있어야 한다.

이렇든 저렇든 작은 조직에서 많은 걸 바랄 수는 없다. 하지만 스스로 조심 또 조심해야할 일이다.

About the react-redux and keeping the global states in the persistent manner

I’m a pretty new one in the developing the frontend app in the web. Making a user interface in the web with HTML, CSS, and JS was a very tedious work and its code writing was so much ugly because of my short knowledge. If I had tried to learn the core nature of JS in the early days, it could be one of my best languages in the development.  Unfortunately, there was no such poor language than JS when I saw its beginning and its characteristics as a programming language. It has become the dominant programming language in the development world.

In recent days, I’ve fallen in love with the ReactJS app in the web. I’d hated the UI programming with any sorts of language, from VC++ to WEB(Mostly with HTML). But the ReactJS helps me to build a humble web app with full functionalities we need. Its code looks very good by adopting the functional and asynchronous style.  Its functional coding style helps the asynchronous event handling in a straightforward way.

Redux and the management of the state

But one of the headaches we should care is the separation of the logic from the UI. The frontend and backend system development approach has helped us define a clear role and responsibility and most of the critical business logic is on the backend side. However, some fraction of codes is related with the none UIs, just like the interactions with the backend. The values returned by the backend defines what actions or interaction should happen between the frontend app and a user. We call the set of values who controls the interaction as “states“. According to the characteristics of the data, some states are meaningful only a specific page. On the other hand, some states are valuable in the overall app and need to be shared by all pages or workflows in the app. In ReactJS, we call it the first as the “local states“, the other as the “global states“.

As far as I know, one of the many reasons why the redux feature has been introduced is to cover the management of the global states. The global states are very important resources and it should be managed in a controlled way. It means we should not make it be modified by anyone because it can do. To achieve this goal, it has adopted the value modification with the state machine along with the asynchronously executing observers. Anyone can reference values of the global states via the component’s properties, which are read-only and you cannot modify it directly.

You can see the technical details in the following resources.

  • https://redux-observable.js.org/
  • https://redux-observable.js.org/docs/basics/Epics.html
  • https://redux.js.org/basics/reducers
  • Simple online code writing and running tool: http://jsbin.com/jexomi/edit?js,output
  • https://redux-observable.js.org/docs/Troubleshooting.html#rxjs-operators-are-missing-eg-typeerror-actionoftypeswitchmap-is-not-a-function

It is a pretty awesome framework for many reasons.

  • It allows you to manage the global values in the structured and controlled way via the state transitions.
  • Its architectural guide enforces the separation of the logical data manipulation from the UIs who trigger the action by a user.
  • It provides the simple global value reference via ReactJS component’s properties in a safe way.

But how about the local states?

Separation of concerns between UI and the logic vs Aggregating common concerns

Persistence

Well, the redux-observable is a good solution to handle the global state management but it doesn’t keep the last global state in the browser. If you hit the current URL in the browser’s address bar, booms!! All the states kept in your web app are reset by the default values. To keep it, we should make one of the utilities such as the local storage, cookies, and sessions. The redux-persist supports the feature, not hurting the existing code. It requires a minimal routing change and offers a way to share the information among separate pages.

  • https://www.npmjs.com/package/redux-persist#nested-persists

The “Transform” should be your consideration to keep the local data safe. The data deletion is the simplest way to achieve this goal and the following guides can offer the way.

  • https://github.com/gabceb/redux-persist-transform-expire
  • https://github.com/maxdeviant/redux-persist-transform-encrypt

It is the first summary of my javascript/react programming and I will try to keep posting articles related to it sooner or later.