스토리 홈

인터뷰

피드

뉴스

개발에 관심있다면 꼭 읽어야하는 글
조회수 1978

Tips for building fast portrait segmentation network with TensorFlow Lite

PrefaceDeep learning has led to a series of breakthroughs in many areas. However, successful deep learning models often require significant amounts of computational resources, memory and power. Deploying efficient deep learning models on mobile devices became the main technological challenge for many mobile tech companies.Hyperconnect developed a mobile app named Azar which has a large fan base all over the world. Recently, Machine Learning Team has been focusing on developing mobile deep learning technologies which can boost user experience in Azar app. Below, you can see a demo video of our image segmentation technology (HyperCut) running on Samsung Galaxy J7. Our benchmark target is a real-time (>= 30 fps) inference on Galaxy J7 (Exynos 7580 CPU, 1.5 GHz) using only a single core.Figure 1. Our network runs fast on mobile devices, achieving 6 ms of single inference on Pixel 1 and 28 ms on Samsung Galaxy J7. Full length video. There are several approaches to achieve fast inference speed on mobile device. 8-bit quantization is one of the popular approaches that meet our speed-accuracy requirement. We use TensorFlow Lite as our main framework for mobile inference. TensorFlow Lite supports SIMD optimized operations for 8-bit quantized weights and activations. However, TensorFlow Lite is still in pre-alpha (developer preview) stage and lacks many features. In order to achive our goal, we had to do the following:Understand details of TensorFlow and Tensorflow Lite implementation.Design our own neural network that can fully utilize optimized kernels of TensorFlow Lite. (Refer to 1, 2 and 3)Modify TOCO: TensorFlow Lite Optimizing Converter to correctly convert unsupported layers. (Refer to 4)Accelerate several quantized-layers with SIMD optimized code. (Refer to 5 and 6)We are willing to share our trials and errors in this post and we hope that readers will enjoy mobile deep learning world :)1) Why is depthwise separable layer fast in Tensorflow Lite ?Implementing low-level programs requires a bit different ideas and approaches than usual. We should be aware that especially on mobile devices using cache memory is important for fast inference.Figure 2. Memory access requires much more energy (640 pJ) than addition or multiplication.Accessing cache memory (8 pJ) is much cheaper than using the main memory (table courtesy of Ardavan Pedram) In order to get insight into how intrinsics instructions are implemented in Tensorflow Lite, we had to analyze some implementations including depthwise separable convolution with 3x3 kernelsBelow we describe some of the main optimization techniques that are used for building lightweight and faster programs.Loop UnrollingCan you spot the difference between the following two code fragments?for (int i = 0; i < 32; i++) { x[i] = 1; if (i%4 == 3) x[i] = 3; } for (int i = 0; i < 8; i++) { x[4*i ] = 1; x[4*i+1] = 1; x[4*i+2] = 1; x[4*i+3] = 3; } The former way is what we usually write, and the latter is loop-unrolled version of former one. Even though unrolling loops are discouraged from the perspective of software design and development due to severe redundancy, with low-level architecture this kind of unrolling has non-negligible benefits. In the example described above, the unrolled version avoids examining 24 conditional statements in for loop, along with neglecting 32 conditional statements of if.Furthermore, with careful implementation, these advantages can be magnified with the aid of SIMD architecture. Nowadays some compilers have options which automatically unroll some repetitive statements, yet they are unable to deal with complex loops.Separate implementation for each caseConvolution layer can take several parameters. For example, in depthwise separable layer, we can have many combinations with different parameters (depth_multiplier x stride x rate x kernel_size). Rather than writing single program available to deal with every case, in low-level architectures, writing number of case-specific implementations is preferred. The main rationale is that we need to fully utilize the special properties for each case. For convolution operation, naive implementation with several for loops can deal with arbitrary kernel size and strides, however this kind of implementation might be slow. Instead, one can concentrate on small set of actually used cases (e.g. 1x1 convolution with stride 1, 3x3 convolution with stride 2 and others) and fully consider the structure of every subproblem.For example, in TensorFlow Lite there is a kernel-optimized implementation of depthwise convolution, targeted at 3x3 kernel size:template <int kFixedOutputY, int kFixedOutputX, int kFixedStrideWidth, int kFixedStrideHeight> struct ConvKernel3x3FilterDepth8 {}; Tensorflow Lite further specifies following 16 cases with different strides, width and height of outputs for its internal implementation:template <> struct ConvKernel3x3FilterDepth8<8, 8, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 2, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 1, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 2, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 1, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 4, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 1, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 4, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 1, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 4, 2, 2> { ... } Smart Memory Access PatternSince low-level programs are executed many times in repetitive fashion, minimizing duplicated memory access for both input and output is necessary. If we use SIMD architecture, we can load nearby elements together at once (Data Parallelism) and in order to reduce duplicated read memory access, we can traverse the input array by means of a snake-path.Figure 3. Memory access pattern for 8x8 output and unit stride, implemented in Tensorflow Lite's depthwise 3x3 convolution. The next example which is targeted to be used in much smaller 4x1 output block also demonstrates how to reuse preloaded variables efficiently. Note that the stored location does not change for variables which are loaded in previous stage (in the following figure, bold variables are reused):Figure 4. Memory access pattern for 4x1 output and stride 2, implemented in Tensorflow Lite's depthwise 3x3 convolution. 2) Be aware of using atrous depthwise convolutionAtrous (dilated) convolution is known to be useful to achieve better result for image segmentation[1]. We also decided to use atrous depthwise convolution in our network. One day, we tried to set stride for atrous depthwise convolution to make it accelerate computation, however we failed, because the layer usage in TensorFlow (≤ 1.8) is constrained.In Tensorflow documentation of tf.nn.depthwise_conv2d (slim.depthwise_conv2d also wraps this function), you can find this explanation of rate parameter.rate: 1-D of size 2. The dilation rate in which we sample input values across the height and width dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1.Even though TensorFlow doesn’t support strided atrous function, it doesn’t raise any error if you set rate > 1 and stride > 1. <!-- The output of layer doesn't seem to be wrong. -->>>> import tensorflow as tf >>> tf.enable_eager_execution() >>> input_tensor = tf.constant(list(range(64)), shape=[1, 8, 8, 1], dtype=tf.float32) >>> filter_tensor = tf.constant(list(range(1, 10)), shape=[3, 3, 1, 1], dtype=tf.float32) >>> print(tf.nn.depthwise_conv2d(input_tensor, filter_tensor, strides=[1, 2, 2, 1], padding="VALID", rate=[2, 2])) tf.Tensor( [[[[ 302.] [ 330.] [ 548.] [ 587.]] [[ 526.] [ 554.] [ 860.] [ 899.]] [[1284.] [1317.] [1920.] [1965.]] [[1548.] [1581.] [2280.] [2325.]]]], shape=(1, 4, 4, 1), dtype=float32) >>> 0 * 5 + 2 * 6 + 16 * 8 + 9 * 18 # The value in (0, 0) is correct 302 >>> 0 * 4 + 2 * 5 + 4 * 6 + 16 * 7 + 18 * 8 + 20 * 9 # But, the value in (0, 1) is wrong! 470 Let’s find the reason why this difference happened. If we just put tf.space_to_batch and tf.batch_to_space before and after convolution layer, then we can use convolution operation for atrous convolution (profit!). On the other hand, it isn’t straightforward how to handle stride and dilation together. In TensorFlow, we need to be aware of this problem in depthwise convolution.3) Architecture design principles for efficient segmentation networkUsually segmentation takes more time than classification since it has to upsample high spatial resolution map. Therefore, it is important to reduce inference time as much as possible to make the application run in real-time.It is important to focus on spatial resolution when designing real-time application. One of the easiest ways is to reduce the size of input images without losing accuracy. Assuming that the network is fully convolutional, you can accelerate the model about four times faster if the size of input is halved. In literature[2], it is known that small size of input images doesn’t hurt accuracy a lot.Another simple strategy to adopt is early downsampling when stacking convolution layers. Even with the same number of convolution layers, you can reduce the time with strided convolution or pooling within early layers.There is also a tip for selecting the size of input image when you use Tensorflow Lite quantized model. The optimized implementations of convolution run best when the width and height of image is multiple of 8. Tensorflow Lite first loads multiples of 8, then multiples of 4, 2 and 1 respectively. Therefore, it is the best to keep the size of every input of layer as a multiple of 8.Substituting multiple operations into single operation can improve speed a bit. For example, convolution followed by max pooling can be usually replaced by strided convolution. Transpose convolution can also be replaced by resizing followed by convolution. In general, these operations are substitutable because they perform the same role in the network. There are no big empirical differences among these operations. <!-- substitutable -->Tips described above help to accelerate inference speed but they can also hurt accuracy. Therefore, we adopted some state-of-the-art blocks rather than using naive convolution blocks.Figure 5.  Atrous spatial pyramid pooling (figure courtesy of Liang-Chieh Chen) Atrous spatial pyramid pooling[1] is a block which mimics the pyramid pooling operation with atrous convolution. DeepLab uses this block in the last layer.We also substitute most of the convolution layers with efficient depthwise separable convolution layers. They are basic building blocks for MobileNetV1[3] and MobileNetV2[4] which are well optimized in Tensorflow Lite.4) Folding batchnorm into atrous depthwise convolutionWhen quantizing convolution operation followed by batchnorm, batchnorm layer must be folded into the convolution layers to reduce computation cost. After folding, the batchnorm is reduced to folded weights and folded biases and the batchnorm-folded convolution will be computed in one convolution layer in TensorFlow Lite[5]. Batchnorm gets automatically folded using tf.contrib.quantize if the batchnorm layer comes right after the convolution layer. However, folding batchnorm into atrous depthwise convolution is not easy.In TensorFlow’s slim.separable_convolution2d, atrous depthwise convolution is implemented by adding SpaceToBatchND and BatchToSpaceND operations to normal depthwise convolution as mentioned previously. If you add a batchnorm to this operation by including argument normalizer_fn=slim.batch_norm, batchnorm does not get attached directly to the convolution layer. Instead, the graph will look like the diagram below: SpaceToBatchND → DepthwiseConv2dNative → BatchToSpaceND → BatchNorm The first thing we tried was to modify TensorFlow quantization to fold batchnorm bypassing BatchToSpaceND without changing the order of operations. With this approach, the folded bias term remained after BatchToSpaceND, away from the convolution layer. Then, it became separate BroadcastAdd operation in TensorFlow Lite model rather than fused into convolution. Surprisingly, it turned out that BroadcastAdd was much slower than the corresponding convolution operation in our experiment:Timing log from the experiment on Galaxy S8 ... [DepthwiseConv] elapsed time: 34us [BroadcastAdd] elapsed time: 107us ... To remove BroadcastAdd layer, we decided to change the network itself instead of fixing TensorFlow quantization. Within slim.separable_convolution2d layer, we swapped positions of BatchNorm and BatchToSpaceND. SpaceToBatchND → DepthwiseConv2dNative → BatchNorm → BatchToSpaceND Even though batchnorm relocation could lead to different outputs values compared to the original, we did not notice any degradation in segmentation quality.5) SIMD-optimized implementation for quantized resize bilinear layerAt the time of accelerating TensorFlow Lite framework, conv2d_transpose layer was not supported. However, we could use ResizeBilinear layer for upsampling as well. The only problem of this layer is that there is no quantized implementation, therefore we implemented our own SIMD quantized version of 2x2 upsampling ResizeBilinear layer.Figure 6. 2x2 bilinear upsampling without corner alignnment. To upsample image, original image pixels (red circles) are interlayed with new interpolated image pixels (grey circles). In order to simplify implementation we do not compute pixel values for the bottommost and rightmost pixels, denoted as green circles.for (int b = 0; b < batches; b++) { for (int y0 = 0, y = 0; y <= output_height - 2; y += 2, y0++) { for (int x0 = 0, x = 0; x <= output_width - 2; x += 2, x0++) { int32 x1 = std::min(x0 + 1, input_width - 1); int32 y1 = std::min(y0 + 1, input_height - 1); ResizeBilinearKernel2x2(x0, x1, y0, y1, x, y, depth, b, input_data, input_dims, output_data, output_dims); } } } Every new pixel value is computed for each batch separately. Our core function ResizeBilinearKernel2x2 computes 4 pixel values across multiple channels at once.Figure 7. Example of 2x2 bilinear upsampling of top left corner of image. (a) Original pixel values are simply reused and (b) – (d) used to interpolate new pixel values. Red circles represent original pixel values. Blue circles are new interpolated pixel values computed from pixel values denoted as circles with black circumference. NEON (Advanced SIMD) intrinsics enable us to process multiple data at once with a single instruction, in other words processing multiple data at once. Since we deal with uint8 input values we can store our data in one of the following formats uint8x16_t, uint8x8_t and uint8_t, that can hold 16, 8 and 1 uint8 values respectively. This representation allows to interpolate pixel values across multiple channels at once. Network architecture is highly rewarded when channels of feature maps are multiples of 16 or 8:// Handle 16 input channels at once int step = 16; for (int ic16 = ic; ic16 <= depth - step; ic16 += step) { ... ic += step; } // Handle 8 input channels at a once step = 8; for (int ic8 = ic; ic8 <= depth - step; ic8 += step) { ... ic += step; } // Handle one input channel at once for (int ic1 = ic; ic1 < depth; ic1++) { ... } SIMD implementation of quantized bilinear upsampling is straightforward. Top left pixel value is reused (Fig. 7a). Bottom left (Fig. 7b) and top right (Fig. 7c) pixel values are mean of two adjacent original pixel values. Finally, botom right pixel (Fig. 7d) is mean of 4 diagonally adjacent original pixel values.The only issue that we have to take care of is 8-bit integer overflow. Without a solid knowledge of NEON intrinsics we could go down the rabbit hole of taking care of overflowing by ourself. Fortunately, the range of NEON intrinsics is broad and we can utilize those intrinsics that fit our needs. The snippet of code below (using vrhaddq_u8) shows an interpolation (Fig. 7d) of 16 pixel values at once for bottom right pixel value:// Bottom right output_ptr += output_x_offset; uint8x16_t left_interpolation = vrhaddq_u8(x0y0, x0y1); uint8x16_t right_interpolation = vrhaddq_u8(x1y0, x1y1); uint8x16_t bottom_right_interpolation = vrhaddq_u8(left_interpolation, right_interpolation); vst1q_u8(output_ptr, bottom_right_interpolation); 6) Pitfalls in softmax layer and demo codeThe first impression of inference in TensorFlow Lite was very slow. It took 85 ms in Galaxy J7 at that time. We tested the first prototype of TensorFlow Lite demo app by just changing the output size from 1,001 to 51,200 (= 160x160x2)After profiling, we found out that there were two unbelievable bottlenecks in implementation. Out of 85 ms of inference time, tensors[idx].copyTo(outputs.get(idx)); line in Tensor.java took up to 11 ms (13 %) and softmax layer 23 ms (27 %). If we would be able to accelerate those operations, we could reduce almost 40 % of the total inference time!First, we looked at the demo code and identified tensors[idx].copyTo(outputs.get(idx)); as a source of problem. It seemed that the slowdown was caused by copyTo operation, but to our surprise it came from int[] dstShape = NativeInterpreterWrapper.shapeOf(dst); because it checks every element (in our case, 51,200) of array to fill the shape. After fixing the output size, we gained 13 % speedup in inference time.<T> T copyTo(T dst) { ... // This is just example, of course, hardcoding output shape here is a bad practice // In our actual app, we build our own JNI interface with just using c++ code // int[] dstShape = NativeInterpreterWrapper.shapeOf(dst); int[] dstShape = {1, width*height*channel}; ... } The softmax layer was our next problem. TensorFlow Lite’s optimized softmax implementation assumes that depth (= channel) is bigger than outer_size (= height x width). In classification task, the usual output looks like [1, 1(height), 1(width), 1001(depth)], but in our segmentation task, depth is 2 and outer_size is multiple of height and width (outer_size » depth). Implementation of softmax layer in Tensorflow Lite is optimized for classification task and therefore loops over depth instead of outer_size. This leads to unacceptably slow inference time of softmax layer when used in segmentation network.We can solve this problem in many different ways. First, we can just use sigmoid layer instead of softmax in 2-class portrait segmentation. TensorFlow Lite has very well optimized sigmoid layer.Secondly, we could write SIMD optimized code and loop over depth instead of outer_size. You can see similar implementation at Tencent’s ncnn softmax layer, however, this approach has still its shortcomings. Unlike ncnn, TensorFlow Lite uses NHWC as a default tensor format:Figure 8. NHWC vs NCHW In other words, for NHWC, near elements of tensor hold channel-wise information and not spatial-wise. It is not simple to write optimized code for any channel size, unless you include transpose operation before and after softmax layer. In our case, we tried to implement softmax layer assumming 2-channel output.Thirdly, we can implement softmax layer using pre-calculated lookup table. Because we use 8-bit quantization and 2-class output (foreground and background) there are only 65,536 (= 256x256) different combinations of quantized input values that can be stored in lookup table:for (int fg = 0; fg < 256; fg++) { for (int bg = 0; bg < 256; bg++) { // Dequantize float fg_real = input->params.scale * (fg - input->params.zero_point); float bg_real = input->params.scale * (bg - input->params.zero_point); // Pre-calculating Softmax Values ... // Quantize precalculated_softmax[x][y] = static_cast<uint8_t>(clamped); } } ConclusionIn this post, we described the main challenges we had to solve in order to run portrait segmentation network on mobile devices. Our main focus was to keep high segmentation accuracy while being able to support even old devices, such as Samsung Galaxy J7. We wish our tips and tricks can give a better understanding of how to overcome common challenges when designing neural networks and inference engines for mobile devices.At the top of this post you can see portrait segmentation effect that is now available in Azar app. If you have any questions or want to discuss anything related to segmentation task, contact us at [email protected]. Enjoy Azar and mobile deep learning world!References[1] L. Chen, G. Papandreou, F. Schroff, H. Adam. Rethinking Atrous Convolution for Semantic Image Segmentation. June 17, 2017, https://arxiv.org/abs/1706.05587[2] C. Szegedy, V. Vanhoucke, S. Ioffe, J. Shlens, Z. Wojna. Rethinking the Inception Architecture for Computer Vision. December 11, 2015, https://arxiv.org/abs/1512.00567[3] A. Howard, M. Zhu, B. Chen, D. Kalenichenko, W. Wang, T. Weyand, M. Andreetto, H. Adam. MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications, April 17, 2017, https://arxiv.org/abs/1704.04861[4] M. Sandler, A. Howard, M. Zhu, A. Zhmoginov, L. Chen. MobileNetV2: Inverted Residuals and Linear Bottlenecks. January 18, 2018, https://arxiv.org/abs/1801.04381[5] B. Jacob, S. Kligys, B. Chen, M. Zhu, M. Tang, A. Howard, H. Adam, D. Kalenichenko. Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference. December 15, 2017, https://arxiv.org/abs/1712.05877
조회수 12470

개발자가 이직에 대해 생각할 때...

‘이직’이라는 화두는 샐러리맨에게는 매우 무섭게 다가온다. 평생직장이라는 의미가 사라진 현대 시대에 있어서 직장생활 중에 많이 만나게 되는 단어이다. 더군다나, 소프트웨어 개발자들에게는 매우 일상적으로 발생한다. 그러니, 이직을 너무  두려워하지 말자. 오히려 평소에 이직에 필요한 스킬과 준비를 매우 당연하게 해야 한다.개인적으로 소프트웨어 관련 학과에서는 '이직'과 관련된 커리큘럼을 하나 만들어 두거나. 아니면, 교양과목이라도 있어야 하나 가르쳐야 한다고 생각한다.필자도 여러 기업에 입사하고 이직을 고민하는 과정을 똑같이 경험했다. 더 큰 경험으로는 기업을 창업하고 직원을 채용하고, 퇴사하는 과정도 같이 경험했다. 돌이켜 생각해 보면 직원의 입장과 중간관리자의 입장, 경영진과 최고 경영진의 입장에서의 ‘이직’을 바라보는 관점이 정말 매우 다르다.이번 이야기에서는 이런 ‘이직’의 관점을 ‘소프트웨어 개발자’의 입장에서  이야기해보자.이직이란 단어는 언제 만나게 될까? 이직이라는 단어를 머릿속에 떠올리게 되면서 당연하게 고민할 것이다. 더 좋은 조건을 제시하는 회사로 자리를 옮기거나, 또는. 현재 있는 직장에서 하는 일이 마음에 들지 않거나, 특정한 사람이나 환경 때문에도 이직이라는 단어는 언제나 떠올릴 수 있다.소프트웨어 개발자들이 이직을 고민하고  생각할 때에 어떤 부분들을 고민하고 생각해야 하는지 알아보자. 물론, 이번 이야가의 내용은 전적으로 필자의 주관적인 경험을 바탕으로 만들어진 내용들이기 때문에 매우 주관적이라는 것을 먼저 이야기해야겠다.다만, 작지 않은 경험을 적은 기업의 신입직원이었을 때부터, 벤처기업의 CEO, 중견기업의 CIO의 역할을 해보고 느낀 점 들을 몇 가지 정리하여 본 것이다.자, 이 글을 읽는 독자들은 ‘이직’을 고민하는가?혹은 이직이라는 단어에 대해서 어떻게 생각하는가?일단, 직장은 너무 쉽게 바꾸거나, 특정한 이유에 너무 집착하여, 너무 쉽게 결정하지 않기를 바란다. 일반적으로 한 회사에서 정년퇴직한다는 전설을 만난다는 것은 이제 거의 불가능에 가깝다. ( 필자역시, 딱 한사람 만났다. )소프트웨어 개발자들은 한 회사에 오래 근무할 수 있는 여건이 되는 사람들은 매우 극소수에 해당된다고 생각해야 한다. 대부분은 프로젝트가 종료되거나 의미가 없어지면서 이직에 대해서 고민을 시작 하게 된다.너무도 자주 만나게 되는 이 단어에 대해서 사람들마다 각자의 의미와 나름대로의 기준점을 잡아두는 것이 매우 좋다고 설명하겠다. 각자 자신이 걸어가야 할 로드맵이나 기본적인 원칙을 한, 두 가지쯤은 정해 두는 것이 좋다. 이 기준은 정말, 개인적인 기준들이다. 이 기준을 각자 가져야 한다.필자의 경우에는 초보때에 세웠던 원칙이 몇 가지 있었고, 나름 경험이 많아지면서  이러한 원칙들은 조금씩 그 기준을  추가하게 된다. 필자의 사례를 들어보자필자는 가장 먼저 사회생활 초년병의 시작을 병역특례로 시작하였다. 그래도. 나름 기준은 있었다. 그것은 앞으로 소프트웨어 개발일을 계속하기 위해서 내가 어떤 기준으로 회사를 찾아야 하는가에 대한 것이었다. 내가 세운 대원칙은 딱 하나였다. 하드웨어 작업을 병행하는 일을 한다는 것을 원칙으로 했다.그래서, 선택한 기업에서 처음 내게 할당된 일은 Z80으로 음성보드를 만들고, 적외선 센서로 터치스크린을 만드는 파트에서 Z80과 i8051의 크로스 어셈블리로 프로그래밍하는 일이었다. 내가 세운 큰 대원칙에는 맞는 일이었고, 일 자체에 대해서도 매우 큰 매력을 가졌다.하지만, 그 업체에서 병역특례 일을 하다가 부당한 노동현장(?)의 부조리를 맞이 하게 되면서 회사를 그만두게 됐다. 그 당시 얻은 경험 중의 하나는 ‘부조리한 노동현장’은 빨리 떠나라는 개인적인 원칙도 세웠다. 그 기준은 나중에 기업을 운영하면서도 가장 부끄러워할 경영진의 몫이라는 것을 인지하게 된 것도 가장 큰 경험이었다고 하겠다. ( 이런 경험은 차라리 초보나 신입 때에 경험하는 것이 그나마 불행 중 다행이며, 사회의 쓴맛을 제대로 보았다고 하겠다. 무료 법률상 담도 해보았고, 노무담당 문의도 해봤다. )그 후에 경력직 프로그래머로써 제대로 된 취업을 할 때에도 나름대로 원칙을 세웠다.병역특례 일을 하다가 그만두고 군대를 다녀왔을 당시에는 윈도우즈 애플리케이션의 개발이 매우 어렵던 시절이었기 때문에 나름 몸값을 요구할 수 있었다. 그래서, 프로그래밍을 하는 데 있어서 조금은 특이한 솔루션을 활용하는 경험을 하고 싶었고 그것을 중요한 원칙으로 삼았다.당시에 선택할 수 있는 기업은 3곳이었다. 하나는 용산 근처의 게임 개발사. 건대 부근의 한국전력에 소프트웨어를 개발해서 판매하던 회사, 그리고. 하나는 건축 소프트웨어 개발을 하는데 Auto-Cad의 ARX아키텍처 기반의 프로그래밍과 윈도즈 개발을 하는 일이었다.3군데의 회사에 면접이 다 통과된 이후에 고민하였다. 가장 적극적이었던 회사는 게임회사였다. 지금 기억으로도 90년대 중반에 팔레트 애니메이션을 능숙하게 조작하는 내 스킬을 보고 매우 탐을 내었던 게임업체의 사장이 기억난다. 그 먼 거리에서 인천의 집까지 나를 태워다 주면서, 같이 일하자고 차를 타고 오는 도중에 많은 이야기를 나누면서, 같이 일하자고 설득했다.하지만, 결정적으로 그 당시에 결혼을 한 필자의 입장에서는 ‘급여’가 가장 큰 걸림돌이었다. 전혀 엉뚱하게도 ‘급여’를 가장 많이 준다는 ‘회사’를 선택했다. 바로, 건축 소프트웨어 개발회사였다. ( 당연하지만, ‘급여’는 언제나 샐러리맨에게는 최고의 선택 기준이 될 것이다. )아마도, 필자가 그 당시에 급여는 매우 적지만, 그 게임업체에 들어갔다면 운명이 매우 많이 바뀌었을 것으로 생각한다. 당시, 병역특례를 하다가 군대를 다녀오면서 네트워크 프로그래밍에 대한 스킬까지 겸비한 필자가 게임업계로 들어갔으면 나름 재미있는 미래가  진행되지 않았을까 한다.하지만, 그래도 그 당시에 급여도 나름 가장 많았지만. 최고의 선택 기준은 ‘독특한 기술’에 대한 호기심이었다. 더군다나, 윈도즈 개발자로서 나름 이름을 알리기 시작하던 시기였기 때문에 필자의 선택은 옳았다고 생각한다.이때 중요한 화두는 ‘급여’와 ‘윈도즈 개발환경’, ‘독특한 콘셉트’이었다. 당시, 그 회사는 AutoCad에서 동작되는 한글 소프트웨어와 설계용 지원 유틸리티를 개발하는 업체였기 때문에, 선배 개발자들과의 경험이 매우 좋았다. 선배 개발자와 개발실장으로 계시는 분들이 20대 중반이었던 필자를 매우 아껴주었던 기억이 난다.최소한 그 계통에서 5년 이상 일을 했던 선배들이 몇 분 계셨고,  그분들에게 생각보다 많은 것을 얻을 수 있었다. 정말, 훌륭한 선배들은 언제나 초보와 신입들에게는 큰 도움이 된다.필자가 신입시절에 크게 결정한 것은 ‘장래성’도 아니고, 오히려 찾은 것은 ‘독특한 개발’을 경험할 수 있는 환경을 찾았고. 그것은 또 하나, 새로운 개발환경을 초기서부터 세팅하는 것도 포함되어 있었다.‘개발자가 이직을 결정해야 할 때’는 언제 인가하고 후배들이 가끔 질문을 해오거나 자문을 구해올 때가 있다. 그렇다면, 소프트웨어 개발자가 이직을 생각하는 때에 대해서 어떤 것을 고민해야 하고, 이직을 결정하기 위하여 중요한 사항은 어떤 것이 있을까?물론, 이직은 모든 분야에서 공통적으로 발생하는 요소이기 때문에 전부를 이야기할 수 없겠지만, 가장 좋은 이직이란 무엇인지 필자의 경험을 중심으로 이야기해보자. 다음에 나열하는 요소들은 ‘이직’을 고민하게 될 가장 큰 가능성을 가지고 있다.첫째. 자신의 전문성에 대해서 고민하기 시작할 때...보통은 자기계발에 충실한 사람의 경우에 자신이 제대로 된 전문성을 확보하고 있는지에 대해서 의문점이 생기는 시점에 '이직'을 고민하게 된다. 이 일을  계속하는 것이 미래에 ‘전문성’을 가질 수 있느냐에 대해서 의문을 가지기 시작할  때부터이다.둘째. 조직원들 간에 트러블이 발생하거나, 말도 안 되는 상사의 권위에 질렸을 때이 부분은 일반 직장과 동일하다. 아무리 전문성이 보장되고, 일이 괜찮다고 하더라도. 동료들과의 문제가 발생되는 부분은 어느 직종이나 동일하다.필자는 소프트웨어 개발일을 하면서도 벤처기업의 경영진 역할과, 중견병원그룹의 CIO생활을 하면서 다양한 직종의 사람들과 일을 해보고 인사권을 가지고 있었지만. 모두 동일하게 문제가 발생하는 것은 ‘직원들’ 간의 문제나, 중간 관리자의 전횡 등이 가장 큰 이유가 되었다.셋째. 프로젝트가 종료되었을 때에생각보다 하나의 프로젝트가 종료되면서, 소프트웨어 품질이나 개발에 대한 연속성이 제대로 이어지지 않는 경우에는 이직을 생각하게 된다.재미있고 즐거운 개발을 필자가 주창하는 이유 중의 하나가 이러한 ‘프로젝트 종료’ 시의 이직에 대한 고민을 하지 않기 위해서이다. 하지만, 대부분의 프로젝트들은 실패하거나 어려움을 겪는 경우가 다반사이기 때문에, 프로젝트가 종료될 때에 이런 충동을 느끼게 된다.이상 3가지의 기본적인 이슈들은 직장생활을 하면서 매번 만나게 되는 고민이고. 3가지의 고민이 모두 발생한다면, 당연하게 ‘이직’을 오히려 권해야 할 사항이 될 것이다.자, 이직에 대해서 고민하고, ‘이직’을 결정하였다면, ‘미련’없이 ‘이직’을 준비하자.‘이직’을 준비하는 것에 있어서 가장 중요한 것은 옮겨갈 회사를 잘 고르는 것이 가장 큰 것이다. 그리고. 퇴사를 하는 회사의 경우에는 최소한 1개월 정도의 업무 인수인계 작업은 당연하게 고려하자. 물론, 제대로 된 체계가 있는 회사는 당연하지만, 직원들의 이직 프로세스가 잘 잡혀있기 때문에 너무 걱정할 필요 없다.대부분의 조직은 누구 한 사람이 나간다고 하더라도, 그 프로젝트가 잘못되는 경우는 거의 없다. 그냥, 본인의 마음이 떠난다면 ‘이직’을 진행하는 것이 맞을 것이다.너무 걱정하지 말고 이직을 결심하고 진행하라고 조언하고 싶다.다만, 필자는 ‘이직 시에 적합한 회사’를 찾기보다는, ‘이직 시에 안 좋은 회사’를 피하는 방법을 먼저 터득하라고 조언하고 싶다.이직 시에 안 좋은 회사를 피하는 방법개발자들이 이직을 고려하고, 이직을 결심하게 되었을 때에는 신입의 입장과는 매우 다르다. 어느 정도 경력도 생겼고, 일에 대한 경험도 풍부해지고, 나이도 한두 살 더 먹었으며, 사람들과의 스킨십이나 커뮤니케이션 능력도 좋아지기 시작하는 시기가 된다.또한, 과거에는 ‘취업’과 ‘작은 목표’가 중요하였지만, 이제는 같이 일할 동료들에 대해서도 생각하게 되고, 일하는 회사의 비전이나 다른 부분들도 같이 고님할 것이다. 이런 어느 정도의 경험과 시야가 생겼을 때에 ‘이직 시에 좋지 않은 회사’를 골라내는 방법은 어떤 것들이 있을까?필자의 경험으로는  다음의 사항들을 고려하여 ‘이직’하려는 회사들을 평가했다.하나. 고급 개발자가 있는가?회사의 CTO나 개발실장이 고급 개발자이며, 그 분야의 '구루'급에 해당되는 사람인가? 존재한다면,  그분들이 회사 내부에서 '존경'받으며, '대우'를 받고 있는지 확인해보라. 그 회사에서 꾸준하게 엔지니어로 성장한다면..  그분들과 같은 대우를 받을 수 있는 인사 환경을 갖추고 있는지 확인하면 된다.대부분 허접한 회사이거나 일반 기업체에서 전산실의 역할이 부실한 경우라면 IT기술을 최고로 습득해도 계장 이상 올라갈 수 없는 곳이라면, IT기술을 중요시하는 기업이 아니라는 것이다. 이런 경우에는 '직장인'으로써의 비전만을 따지면 된다. ( 정치적인 것이 아니면, 급여, 복지일 것이다. )'개발자'로써의 삶이나 목표, 비전과는 전혀 상관없는 기업이기 때문에 일반적인 '직장생활'에 충실한 것이 좋을 것이다. 이와 관련된 처세술이나 비교자료는 인터넷에 많으니 검색해서 참조하자.둘. 개발자들이 오랫동안 근무한 사람들이 있는가?회사가 성장하고 발전하는 과정에서 사람들이 들어오고 나가는 것을 반복한다. 이런 경우에 회사에 오랫동안 근무한 개발자나 엔지니어가 존재하는지 확인해보는 것이 좋다. 대부분 경력이 올라가면 '급여'가 오르게 되고, 이렇게 경험이 풍부한 사람들이 많이 존재하는 개발 조직이나 회사가 발전 가능성이나 시장을 가지고 있는 경우가 많다.하지만, 회사는 충분하게 돈을 벌고 있지만, 회사 경력에 비해서 적은 경력의 개발자들이 2~3년 차들로 대부분 도배되어 있다면, 특정 시점에 직원들이 물갈이가 되거나, 개발자들이 죄다 못 버티고 나간 경우라는 뜻이다.'소프트웨어 개발자'들도 대부분 '직장인'에 가깝다. 이 회사가 정말 좋은 곳이고, 계속 다닐만한 가치가 있는 회사라면. 오래된 개발자들이 많이 있을 것이다. 이런 오래된 개발자가 없는 곳이라면 분명, 인사 문제나 처우에 문제가 있는 회사이다.셋. 사무실의 환경을 살펴라.큰 사무실이건 작은 사무실이건 '실제 일하는 사람들'이 사용하는 '책상'이라면 사용하는 흔적들이 있다. 공간은 있지만, 빈 책상에 사용되지 않는 물품들만 있다면. 인력파견업체가 대부분일 것이고, 처우나 사무실의 환경은 그다지 좋지 않을 것이다.대부분 팀장이고 이사이고 아웃소싱 일을 대부분 하고 있는 사람들일 것이고, 당연하지만, 근로환경도 최악이고, 월급이 때인다던 지, 프로젝트 진행이 개판이 되는 경우가 많다.넷. 신입직원 연수나 트레이닝 프로그램이 있는지 확인하라대부분, 이직 시에 이러한 것들을 고려하지 않는다. 하지만, 대부분의 중소기업이나 대기업들의 경우에 자체적인 솔루션이 있거나 나름 시장 지배력이 있는 회사의 경우에는 ‘사전에 교육’ 해야 할 내용들이 많아진다.당연하지만, 신입직원들에게 짧으면 2주, 길면 4주 이상의 트레이닝 코스가 존재하게 된다. 나름 시장 지배력이 있는 회사라면 이러한 코스가 당연하게 있다. 만일 이러한 코스가 없다면, 해당 기업은 의미 있는 솔루션을 만들거나, 의미 있는 서비스를 하고 있는 회사라고 보기 어렵다.그것은 중소기업들은 대부분 적당한 인력을 구인해서 적당하게 사용한다고 보면 된다.이처럼 소프트웨어 개발자가 이직을 생각할 때에 이러한 조건들도 있지만, 오히려 개발 경력이 3~4년 차를 넘기는 개발자에게 필자가 가장 중요하게 질문하는 것이 있다. ‘소프트웨어 개발이 적성에 맞는가?’라고 묻는다.굳이, 소프트웨어 개발이 아니더라도 자신의 자아실현이나 사회생활이 충분하게 실현되는 경우도 많다. 억지로, 소프트웨어 개발자의 길을  걸어가면서 주변을 괴롭히거나, 오히려. 안 좋은 중간 관리자가 되면서 IT업계의 원흉이 되는 것도 이 시기에 잘못 결정한 선배들이나 후배들도 많다.필자가 만난 여러 후배 개발자 중에는 소프트웨어를 설계하고 만드는 일이 그다지 적성에 맞지 않는 경우도 상당수 있었다. 또는, 저 사람은 아예 소프트웨어 개발을 하지 않았으며 좋겠다는 생각을 하게 된 사람도 있었다. 그래서, 오히려 조언을 하거나 유도를 해서 다른 일을 선택하고 그 길을 잘 걸어가는 후배들도 여럿 있다.하지만, 대한민국의 SI개발에만 있었다면 다른 직종도 가능할까?라는 질문에는 사실, 정답이 없다고  이야기한다. 갑을병정 이무기라고 불리는 먹이사슬의 과정 속에서 SI현장에서 다른 분야로 진출하는 것은 정말 어려운 일이다.대기업이나 중소기업의 SI에 입사해서, 프로젝트 관리자의 길을 걸어가는 사람이 아니라면, 매우 어려운 자리가 될 것이다. 하지만, SI나 SM의 이직 시에도 제대로 된 선택을 하면 매우 수월하고 편안한 자리로 이직을 할 수 있다.실제 후배들 중에는 많은 급여보다는 안정적인 자리를 원하는 도메인이 특화된 SM자리를 잘 차지하고 편안하게 일하는 개발자들도 간혹 발견할 수 있다. 하지만, 그런 환경이 아니라면 필사적으로 이직 시의 조건을 따져봐야 한다.최소한 ‘피해야 할 회사의 조건’을 따져봤다면, 이제는 가장 현실적인 ‘조건’을 나열하여 회사와 조직의 환경을 살펴보자. 다음의 조건들을 살펴봐라.야근수당을 받는가?2015년을 기준으로 나이 30세 초반에 연봉 3000~4000이라면 소프트웨어 개발자로서 만족하는 삶을 살 수 있을까? 하지만, 사회생활에 있어서 야근수당을 받거나 주말에 근무하면 추가 페이를 계산받는가? 냉정하게 계산하고 매일 야근과 주말근무를 하고 있다면, 실질적인 연봉은 무려 5~6000만 원을 받아야 정상이다.필자가 중견그룹의 CIO 역할을 하던 시절에 인사팀에서 가장 많은 경고와 안내를 받았던 것 중의 하나가 '야근'근무를 가능한 하지 않도록 유도하는 안내였다. 야근을 하게 되면 자연스럽게 지출되는 야근을 위한 식사와 연장근로수당, 그리고. 주말까지 일하게 되면 2배를 넘어가는 수당의 지급은 상당히 부담스러웠던 것이기 때문에, 인사팀에서는 이러한 근무를 하지 않도록 유도하는 것이 최선의 방법이었을 것이다.대부분 괜찮은 기업들은 '야근'근무를 유도하지 않는다.단지, 근무조건이 탐나는가?냉정하게 SI는 전문성이 매우 높은 분야인데, 대한민국에서는 그러하지 않고, 거의 막장에 가까운 환경을 가지고 있다. 매우 슬픈 일이다. 일본이나 미국과 같은 선진국에서 근무하는 SI 개발자들의 처우나 근무조건은 매우 좋은 조건들이고, 연봉 또한 매우 높다.제대로 된 SI분야의 경우에는 대체인원이 그렇게 많지 않고, 어느 정도 경력을 가진 개발자로 성장하기 매우 어려운 분야이기 때문에 경력자와 경험자를 매우 우대한다. 하지만, 대한민국의 SI현장은 정말 열악한 환경으로 변화하였고, 그 현장은 매우 절망스러운 곳들도 많다.대한민국의 SI가 이렇게 된 이유에 대해서는 여러 가지 이유와 근거와 설이 존재하는데, 필자가 생각하는 몇 가지 이유는 다음과 같다.하나. 대기업의 전산실에서 분리된 IT조직의 태생적 한계둘. 전산/IT를 제대로 전공으로 한 '선배'들이 실제 부재하다.셋. 대정부의 SI 관련 프로젝트가 갑을병정 프로세스만으로 진행되면서 만들어진 흑역사넷. 소프트웨어 품질을 모르는 PM/PL들이 아직 수두룩하다. ( 이론만 아는 방법론자들 투성이다. )다섯. 책임지는 소프트웨어 개발 조직과 개발인력이 그다지 SI현장에 없다.여섯. 소프트웨어 개발은 '자격증'과 아무 상관없고, 개발 경력과도 그다지 연관성이 없다.그래서, 대한민국의 SI현장은 주변에 잘 수소문하여 ‘괜찮은 곳’을 찾아가는 센스를 발휘하지 못하면, 암흙의 이직을 경험할 수 있다.물론, 이렇게 이야기하는 ‘이직’의 대부분은 ‘스타트업’이나 ‘도전적인’ 기업을 선택하는 것과는 다른 기준들이다. 대부분은 ‘조직’이라는 틀에서 움직이는 ‘작업자’들을 구인하고 그 공간이 나에게 맞는지에 대해서 잘 따져야 하는 것이다.결국, '조직'의 틀로 생각한다면, 일반 샐러리맨의 회사 선택의 기준과 그다지 차이가 없을 것이다.하지만, 소프트웨어 개발자의 세계에서 '이직'을 제대로 할 수 있는 방법은 말 그대로 '스카우트'을 받고 이동하는 것이다. 그런 대우를 받으려면, 제대로 평가된 ‘나의 인식’과 ‘나의 브랜드’가 있어야 가능하다는 것을 염두에 두자.결론적으로 '이직'을 제대로 하려면, 자신의 '브랜드'를 만들 수 있어야 한다. 그것이 핵심이다.그렇다면, 성공적인 이직을 하려면 무엇을 갖추어야 하는가? 그것은 다음의 6가지로 정리할 수 있다.하나. 자기만의 장점을 가져야 한다.둘. 자신만의 전문성을 가져야 한다.셋. 절대다수는 하지 못하는 희소성을 가져야 한다.넷. 내 경력과 전문성을 증명할 프로젝트를 가져야 한다.다섯. 포트폴리오를 구성하라여섯. 외부활동과 내 브랜드를 만들어라이 6가지 중에 2~3가지만 충족한다고 하여도, 소프트웨어 개발자는 제대로 된 대우나 평가를 받으면서 즐거운 이직을 경험할 것이다. 말 그대로 헤드헌팅이나 개발자 커뮤니티에서 당신에 대한 평가가 좋을 것이다.매우 당연한 것이지만, 준비된 사람에게는 언제나 기회가 만들어진다. 기회가 만들어지지  않는다는 것은 ‘준비가 부족하기 때문이다’라고 이야기할 수 있다.직업 이직을 권유받았는가? 아니면. 이직을 꿈꾸는가?그렇지만, 그렇게 브랜드나 명성을 얻기 전에 권유를 받았건, 상사가 괴롭혀서 떠나건, 이직에 대해서 고민하고 결심했다면 다음의 몇 가지를 고민하자.후배들에게 이야기하는 몇 가지 충고의 이야기가 있다. 이것은 정말 최소한의 기준이다.최소, 이 기준에 대해서는 고민하고 '이직'을 결심했으면 좋겠다.하나. 소프트웨어 개발자들이거나 SI현장에 있는 개발자라면 최소한 하나의 도메인이나 전문분야를 택했다면 최소 5년은 버텨야 한다.둘. 프로젝트나 포트폴리오는 5년 이하 경력은 세상이 제대로 인지하거나 인식하지 않는다.셋. 직장이 중요한 것이 아니라, 직업과 도메인이 중요하다.넷. 경력과 브랜드는 ㅇㅇ회사의 누구가 아니라. 누가 다니는 ㅇㅇ회사가 더 좋다는 평가를 받아야 한다.SI현장에 있건, SM현장에 있건, 대기업이나 중견기업은 파견 나온 개발자를 좋아한다. 어떤 분야이건 어떤 특수하거나 일반적인 분야이건 대부분은 교육이 필요하고, 경험이 필요하다. 그리고, 그 조직과 회사에 적응하는 기간이 필수적이다. 대부분 이러한 '비용'은 기업을 운영하는 입장에서는 어떻게든 최소화하기를 원한다.대부분 이런 신입 비용을 어떻게 줄이느냐가 관건이기 때문에, 대부분의 회사들은 가능한 '경험'자와 '경력자'를 선호하는 것이 매우 당연하다. 특히나, 관련된 일과 조직에 익숙한 사람이라면 회사 입장에서는 신입의 교육비용이 들어가지 않는 파견된 개발자들을 선호하게 된다.바로 업무에 투입하고 결과물을 얻을 수 있기 때문에, 이러한 파견된 개발자들을 선호할  수밖에 없다. 그래서, 보통 갑, 을의 조직들은 자신의 일을 위해서 파견 나온 SI, SM개발자들을 참 매력적으로 인식한다.특히나, 이렇게 일하는 SI, SM 개발자들은 함께 일하고, 같은 조직에서 일하는 사람들이기 때문에 눈으로 확인한 이러한 사람들을 좋아할  수밖에 없다. 당연한 것이지만, '면접'을 통해서 사람을 뽑는 것보다 직접 함께 일한 사람을 뽑는 것이기 때문에 해당 기회비용과 교육을 위한 시간 비용들이 모두 절약된다.그래서, 대부분은 고객 회사에서 이런 개발자들에게 먼저 이직을 권유하게 된다. 고객의 입장에서는 바로 실전에 투입할 수 있는 개발자를 얻을 수 있고, 권유를 받은 개발자 역시 중소기업이나 파견직에서 일하다가 더 높은 연봉과 복지제도를 제공하는 기업으로 옮겨갈 수 있는 기회를 얻는다.다만, 이러한 권유를 받는 것은 '인력파견'업체를 통해서 SI현장에 나가서 일하는 경우에는 이러한 '기회'를 얻기 어렵다. 실제, 이러한 '제의'를 받는 경우는 '고객'의 기업에 직접 나가서 일하는 경우를 의미한다고 봐야 한다.물론, 이러한 것을 중소기업 입장에서는 인력 빼가기?라고 볼 수 있다. 필자도 중소기업을 운영해봤지만, 중소기업에서 4~5년 이상 일을 하고 있는 직원이 아니라면, 이러한 이야기도 하기 힘들것이고, 실제, 중소기업의 일이라는 것이 '일을 배우고 가르치는 이유가 어느 정도 업무에 필요한 수준'까지만 가르치기 때문에, 이를 중소기업의 인력 빼가기라고 이야기하기 어렵다. 가르친 것도 없이 일만 시켰는데 무슨 ‘인력 빼가기’인가?다만, 가장 최악의 이직 회사를 피하는 방법은 정말 고려하다. 하지만, 이직을 할 때에 순간적인 선택에 의해서 정말 좋지 않은 선택을 하는 경우가 종종 있다. 하지만, 아래와 같은 회사로 이직을 하였다면, 재빠르게 '사표'를 내는 것이 가장 현명하다. 필자의 경험을 기반으로 이런 회사는 빨리 떠나야 한다고 생각한다.하나. 회사의 사무실의 인테리어가 영 허접하다현재의 소프트웨어 개발자들의 인테리어는 대부분 훌륭하다. 특히, 이제 막 시작한 스타트업의 경우라면 직원이 아니라, '동료'의 입장으로 참여하는 것이기 때문에 이 조건은 해당이 안될 것이다. 하지만, '직원'의 입장에서 그 회사에서 일을 하는 경우라면 '회사 인테리어'는 매우 중요하다.그것은 초라한 사무실에 초라한 책상에 기본적으로 제공되는 도구도 깔끔하지 않다면, 정말 간단하다. 그 회사에서 직원들에 대한 처우나 근로환경은 최악이라고 보면 된다. 아마도, 입사를 한지 한 달 후에 바로 급여나 근로형태에 대해서 불만이 생길 것이다.대부분 이런 회사의 특징은 인력파견 회사일 확률이 높다. 당연한 것이지만, 내부에 축적된 지식도, 솔루션도 없는 조직이다. 그냥, 싼 개발자를 구하고, 파견을 보낼 개발자를 구했을 것이고, 그것에 당신이 걸려들은  것뿐이다. 빨리 탈출하는 것이 현명하다.둘. 직원들의 얼굴 표정이 매일 야근한 것 같다.근무조건과 처우에 대해서는 그 회사에서 근무하는 직원들의 모습을 보면 된다, 깔끔한 복장에 자유롭고, 자신에 찬 얼굴을 하고 있는 경우라면 상관없다. 하지만, 세탁한지 며칠 된 복장에 연일 야근에 찌든 듯한 얼굴, 사무실에 난로도 제대로 안 때워서 매번 감기에 걸려있는 상태인듯한 모습이라면, 그 회사도 빨리 탈출하는 것이 현명하다.필자는 개인적으로 소프트웨어 개발자들을 제대로 처우하는 곳이라면 키보드와 마우스, 그리고. 의자는 최대한 자신이 원하는 도구를 구해주는 곳이라고 생각한다. 그리고, 최소한의 근무환경을 구성해줄 수 있어야 한다. 다만, 같이 고생하고 같이 나눌 동료가 아니라면 이런 회사는 빨리 탈출하다.셋. 오래된 선배 개발자의 경력이 얼마나 되는가?좋은 조직과 좋은 회사. 그런 곳은 좋은 회사다. 고로, 당연하게 좋은 회사는 계속 다닐만한 가치가 있기 때문에 오래된 개발자들이 존재한다. 회사 업력이 10년이 넘었다면, 10년을 다닌 개발자가 있을 것이고, 5~6년 차 개발자들이 여러 명 존재해야 한다.하지만, 회사 경력이 10년을 넘었는데도 그 회사 경력 2년 차가 팀장이고, 병특들로 모두 구성되어 있는 회사라면, '결코 좋은 회사는 아니다'.분명하게 회사의 사장에게 문제가 있거나, 똘아이 같은 개발이사가 있거나, 막 나가는 팀장이 있을 수 있다. 또는, 처우나 급여문제 등등 문제가 분명 존재할 것이다.넷. 가족과 같다는 이야기를 반복하는 사장의 이야기회사는 '이익'을 위하여 존재하는 곳이고, '돈'을 벌어야 급여가 나오는 회사이다. 회사는 '가족'이 아니다. 그리고, '사장'처럼 일하라고 반복하는 '사장'들이 가끔 있다. 그럼, 이렇게 반문해보자, '사장'같이 일하면, '그 회사'를 물려줄 것인가?아니다. 처우는 '노예'처럼 하면서 일은 '사장'처럼 하기를 원하는 것이다. 이런 회사도 떠나라. 또 이런 회사의 특징은 이렇다.'회사 사정이 어려워서...', '요즘 경기가 안 좋아서...', '다음에...', '이거 끝나면 뭔가 있을 거야...'부끄럽지만 필자도 이런 이야기들을 20대 후반 사장 시절에 반복했었다. 결론적으로 '지키지 못할 약속'을 그냥 반복할 뿐이다. 이런 이야기의 99%는 뻥이고, 그냥.  '립서비스'일뿐이다. 포상은 합리적이어야 하는 것이다. 또한, 엄청난 투자를 받는다고 해서  밀어붙인 일일 경우도 많다. 하지만, 언제나 '과실'중에 '이익'은 경영진만이 가지고 간다는 것을 잊지 말자.다섯. 인건비는 무조건 싼 개발자만 찾는 회사.간단하다. 경력 10년 차 개발, 고급 개발자가 할 수 있는 일을 하거나, 품질이 높은 일이 필요 없는 일이 대부분이다. 임금이 비싸고 경력이 풍부한 사람이 비싼 이유는 당연하게 있다. 하지만, 단지 급여가 싼 사람을 찾는 이유는 간단하다.'일'에 대한 가치를 알지도 못하고, '개발자'에게만 탓을 돌리는 사장이나 경영진일 경우에 대부분 이렇다. 경력 1년 차가 할 수 있는 일이라고만 생각하기 때문에, 경력 4~5년 차도 그에 합당한 급여를 줄 수 없는 것이다.당연한 것이지만, 실제 일은 단순 SM이기 때문에 그런 경력을 가진 개발자가 필요 없다고 인지하기 때문이다. 이런 회사들이야말로 정말 비전이 없다.여섯. 급하게 뽑는데 면접도 제대로 안보는 회사정말 엉터리 같은 인력파견업체의 경우가 이렇다. 자신들이 면접을 보는 것이 아니라, 고객사로 보내서 면접을 본다.만일 위에 언급한 6가지 내용 중에 한 개 이상으로 해당되는 회사나 조직에 있다면, ‘이직’을 고려하는 것이 정말 당연하다 하겠다. 하지만, 자신의 능력과 이직에 대한 준비가 되어 있지 않다면, 어쩔 수 없다. ‘샐러리맨’의 기본자세로 돌아가서, 내 능력에 합당한 현재의 자리에 만족하는 법을 배워야 할 것이고, 처세술이나 그 조직에서 버티기 위한 정치력을 발휘해야 할 것이다. 이러한 글들은 주변 서점에 널려있으니, 그런 책 한두권 읽어보기를 권장한다.‘이직’은 소프트웨어 개발자 생활을 하면서 계속 유혹과 한계를 경험하게 할 때마다 머릿속에 떠오를 것이다. 그때에 실수하지 않고, 좋은 판단을 하기 바라며. 가장 중요한 것은 ‘후회’ 하지 않고, 이미 결정한 것은 잊어버리는 것이 속 시원하다는 것이다.마지막으로 좋은 스타트업을 골라달라고 조언하는 경우에는 다음과 같이 답한다.스타트업은 좋은 동료가 될 생각이 있을 때에 들어가라는 것이다. 스타트업은 초기 멤버로서 합류하면서 고생도 같이 하고, 이익도 같이 나누는 동업자가 되는 것이다. 샐러리맨으로써 직장을 택하는 것과는 정말 다른 것이다.물론, 스타트업이 투자를 받고, 초기 멤버가 아닌 경우에는 위에서 언급한 내용과 별로 차이가 없다고 설명할 수 있다. 어느 규모나 별로 차이가 없었다.'이직'은 소프트웨어 개발자들에게는 매번 경험하게 된다. 그리고, 그 경험을 좋은 결과로 얻기를 바란다. 그리고, 언제나 좋은 선택이  필수이며, 인생 선배나 동료에게 좋은 조언을 구해보자.
조회수 1397

개발팀의 유행어 제조기, Mark를 만나다

 * 2015년에 작성된 글입니다편집자 주: 잔디에는 현재 40명 가까운 구성원들이 일본, 대만, 한국 오피스에서 일하고 있습니다. 국적, 학력, 경험이 모두 다른 멤버들. 이들이 어떤 스토리를 갖고 잔디에 합류했는지, 잔디에서 무슨 일을 하고 있는지 궁금해하시는 분들이 많았습니다.  이에 잔디 블로그에서는 매 주 1회 ‘맛있는 인터뷰’라는 인터뷰 시리즈로 기업용 사내 메신저 ‘잔디’를 만드는 사람들의 이야기를 다루고자 합니다. 인터뷰는 매 주 선정된 인터뷰어와 인터뷰이가 1시간 동안 점심을 함께 하며 다양한 이야기를 나누며 진행됩니다. 인터뷰이에 대해 궁금한 점은 댓글 혹은 이메일([email protected])을 통해 문의 부탁드립니다.인터뷰 시작에 앞서 편집자 스스로 잔디의 개발팀에 궁금한 점이 있었다. 매 주 수요일 아침 8시, 오피스 근처 카페에서 스터디를 하는 그들의 문화가 바로 그 것이다. 회사의 강요가 아닌 공부를 하겠다는 자발적인 이유로 모인다는 그들. 그들 중 한 명인 Mark를 이번 주 맛있는 인터뷰에 어렵게 모시게 되었다.세렝게티의 한 마리 표범과 같은 그의 눈빛이 향한 곳은 가.츠.나.베반갑습니다. 우리 좀 걷지 않았나요? 회사에서 꽤나 멀리 떨어진 ‘오무라안’을 온 특별한 이유가 있다면?회사 바로 앞에 있는 ‘탄’보다는 조금 고급스러운 일식 레스토랑이에요. 우연히 알게 된 곳인데 맛이 딱 제 취향이라 즐겨 찾습니다. 항상 가츠나베를 먹는데요. 그 맛은.. 말로 형용하기 어렵네요.가츠나베성애자이시군요. 얼마나 있는지 모르겠으나 ‘맛있는 인터뷰’ 독자들을 위해 인사 부탁드립니다.안녕하세요, 부산 남자 Mark입니다. 잔디에 합류한 지 약 두 달 정도 되었어요. 잔디에서는 Front-end 개발 업무를 맡고 있습니다.주로 어떤 일을 하시나요?쉽게 말하자면 사용자들이 접하는 부분을 책임지는 역할이에요. 지금은 Jihoon, Young과 함께 일하고 있는데 궁합이 잘 맞는 것 같아요. 사람이 적으면 할 수 있는 일이 한정되어 있고 반면 사람이 많으면 커뮤니케이션이 힘든데 저희 세 명은 예외인 것 같습니다.왔노라, 보았노라, 달렸노라Mark님만의 유행어가 있더라고요?‘가자!’ 를 말씀하시는 것 같은데요. 맞나요? (웃음) 비글로벌 서울 2015 우승 후, 뒷풀이 회식에서 흥에 겨워 술과 함께 외친 ‘가자!’가 다른 분들에게 인상적으로 각인되었던 것 같아요.네, 저도 그 자리에 있었는데요. 굉장히 인상적이었어요. 술이 센 편이신 것 같은데요?아니에요. 사실 술을 잘 하는 편도, 자주 마시는 편도 아니에요. 주량이라면 소주 두 병 정도? 그 날은 저희 회사가 좋은 일도 있고 해서 평소보다 많이 마시긴 했지만 기분이 좋았던 게 그런 사태를 만든 주된 이유인 듯 합니다.잔디 비글로벌 서울 2015 우승!잔디의 개발자 채용 과정이 다른 곳에 비해 까다롭다고 들었어요. 직접 경험하신 분으로서 어땠는지 여쭤볼 수 있을까요?정말 까다로워요. 다른 곳도 코딩시험을 보기는 하는데 잔디는 인사부에서 1차 코딩 시험을 보고 2차 면접에서는 왜 그렇게 코딩을 했는지 설명을 해야 합니다. 그리고 나서 인성 면접을 봤습니다. (잔디에서는 이 면접을 Behavior Interview 라고 부르며, 여러 부서의 인원들이 참여해 해당 인터뷰이가 함께 일할 사람으로서 적합한지 판단하게 된다 – 편집자 주)마치 수험생 같다는 느낌이 들었어요. 면접 과정 중에는 ‘뭐 이리 깐깐하게 굴어?’ 라는 생각을 했었는데, 지금 돌이켜 보면 이런 과정을 거쳐 합류한 인재들이 모여 있어 잔디가 빠르게 성장할 수 있지 않을까 추측해 봅니다.잔디에서의 생활은 어떤가요?신기한 점이 참 많은 것 같아요. 좋은 점은 출중한 능력을 가진 분들이 많다는 점이에요. 그분들을 통해 배울 점도 많고, 개인적으로는 분발해야겠다는 생각을 하게 해요. 많은 자극을 받고 있어요.신기한 점이라면 어떤 부분일까요?예를 들면 아침에 출근하면 Dan(CEO)이 제게 다가와 영어로 말을 건네는 것이 가장 신기한 것 같아요. 당황스러우면서도 한편으로는 신기해요.이건 개인적으로 궁금한 건데요. 개발팀의 아침 스터디에 대해 어떻게 생각하시나요?사실 아직 참여해 보진 못했어요. 잔디 개발팀에서는 매주 아침 8시까지 나와서 자발적으로 스터디를 하고 있는데요. 강요가 아닌 자발적으로 업무 외에 스터디를 한다는 점이 참 인상 깊어요.그렇군요. 질문을 좀 바꿔볼게요. 쉬는 날엔 뭐 하시나요? 부산 사람이니 야구?보통 쉬는 날엔 서울에 있는 친구들을 만나거나 게임을 해요. 야구는 부산 사람이다 보니.. 삶의 일부 같은 느낌이죠. 우리가 공기를 좋아하거나 싫어할 수 없듯, 야구 역시 좋아하거나 싫어할 수 있는 대상이 아니에요.보통 ‘부산 사람=야구’라고 생각하는데 Mark도 여기에 해당하는 분이었군요. 게임은 어떤 걸 즐겨 하시나요?WOW(Wolrd of Warcraft)라고 아세요? 저는 게임에 있어서 저만의 철학이 있어요. 게임에도 레벨이 존재한다고 생각하는데요. 모바일 게임을 아주 안 하는 것은 아니지만 모바일 게임에 투자하는 시간은 아깝다고 느껴져요. 물론 개인적인 생각입니다.록타르.. 피바람을 몰고올 Mark여..그러면 Mark가 생각하기에 게임으로서 ‘와우’는 어느 정도 레벨인가요?제가 알고 있는 게임들 중 와우는 Top3에 듭니다. 물론 생각을 깊게 해 본 적은 없어서 나머지 2개에 뭘 넣어야 할지 고민해야겠지만 와우는 정말 잘 만든 수작이에요.이제 곧 휴가철이잖아요. 부산 여행 추천 장소 좀 해주세요.외지 사람들은 보통 해운대 많이 가는데, 사실 부산 사람들은 해운대를 잘 안가요. 사람이 너무 많잖아요? 부산 여행 장소를 찾으신다면 개인적으로 을숙도를 추천하고 싶어요. 여긴 가족 단위 여행객이 많은 곳인데요. 서울 사람들이 한강을 찾듯 부산 사람들은 을숙도를 찾아요.이번 여름 휴가는 을숙도로!을숙도? 섬인가요?네, 섬이긴 한데 엄청 큰 다리로 육지와 연결되어 있어서 차를 타고 들어갈 수 있는 곳이에요. 공원이 잘 조성되어 있어요. 자전거도 빌려 탈 수 있고 까페도 있어서 여행 장소로는 딱이에요.축구장도 엄청 많아서 축구 동호회 분들이 자주 찾으시는데요. 사람으로 북적거리지 않는 부산 여행지를 찾는다면 이번 여름 여행은 을숙도로 가보세요. 참고로 을숙도에는 음식점이 많지 않아요. 저 같은 경우, 을숙도 갈 때마다 도시락을 챙겨가곤 합니다.다음은 맛있는 인터뷰의 고정 코너 ‘어서 말을 해’입니다. Jinho가 남긴 질문 ‘잔디를 한문장으로 표현한다면?’에 대해 답을 주신다면?잔디란 ‘기회’ 입니다. IT 업에서 제가 어디까지 능력을 발휘할 수 있을지 확인해볼 수 있는 좋은 기회이기 때문이죠. 좀 진부한가요?전~혀 진부하지 않아요. 멋진 답변을 주셨으니 다음 인터뷰이를 위해 질문 하나 남겨주시겠어요?저는 이걸 꼭 물어보고 싶어요. ‘최근 3년 동안 당신에게 가장 행복했던 일은?’Mark와 개인적으로 얘기를 나눠보고 싶었는데 이렇게 소원이 이뤄졌네요. 개인적으로 뿌듯한 인터뷰였습니다.감사해요. 잘 좀 편집해 주세요.#토스랩 #잔디 #JANDI #개발팀 #개발자 #개발 #팀원소개 #팀원인터뷰 #팀원자랑 #기업문화 #사내문화 #조직문화
조회수 4805

Elasticsearch로 느린 쿼리 분석하기

응당 인덱스가 있으리라 생각한 칼럼에 인덱스가 없고 인덱스를 걸자마자 응답속도가 평균 10배 가까이 좋아지는 모습을 지켜보니 여러 생각이 들더라. 통계와 지표가 제공되는 곳은 주기적으로 검토하고 문제가 커지기 전에 손을 쓰는데 그렇지 않은 곳이 문제이다. 주기적으로 Slow query 로그를 훑어볼 수는 있다. 하지만 특정 시점에 일부 로그만 훑어봐서는 엉뚱한 문제를 해결하기 일쑤다. 예를 들어 1초짜리 쿼리보다 10초짜리 쿼리가 문제라고 생각하기 쉽지만 이 1초짜리 쿼리를 10초짜리 쿼리보다 1000배 많이 실행한다면 이야기가 달라진다. 요는 느린 쿼리를 지속적으로 수집하고 통계를 낼 필요가 있다는 것이다.이러한 모니터링 도구를 어떻게 구현할까? 우리 손에 있는 도구를 검토하는 일부터 시작했다.통계분석은 MySQL 또는 Elasticsearch 를 쓰면 된다.Elasticsearch를 쓴다면 Kibana를 이용해 시각화하기 편하다.느린 쿼리 로그를 Elasticsearch에 보내는 일은 Fluentd를 쓰면 된다.그러니까 Fluentd, Elasticsearch, Kibana 조합이라면 데이터를 눈으로 보고 문제를 해결하기 좋을 것이다. 그렇다면 어떻게 구현할 것인가?우선 RDS에서 느린 쿼리를 뽑아서 Fluentd에 보내는 방법을 찾아야 한다.Fluentd를 이용해 Elasticsearch에 데이터를 보내는 건 쉬우니 대시보드만 잘 구성하면 끝!문제는 RDS에서 느린 쿼리를 뽑아서 Fluentd에 보내는 것인데 크게 두 가지 방법이 있다. RDS 설정에 따라 느린 쿼리 로그를 테이블 또는 파일에 저장할 수 있는데 이에 따라 구체적인 구현방법이 달라진다. 하지만 기본적으로는 동일한 과정을 거치는데 대충 이런 식이다.느린 쿼리 로그를 읽는다.같은 쿼리라도 매개변수 값이 다를 수 있으므로 mysql_slow_log_parser 또는 pt-query-digest 같은 도구를 사용해 쿼리를 일반화한다.Fluentd를 통해 해당 로그를 ES로 보낸다.새로 추가된 로그만 읽어서 다시 ES로 보낸다.이와 관련해서는 AWS RDS Mysql SlowQuery monitoring on Kibana using Logstash 등의 글이 잘 설명한다.다행히 테이블에 저장한 로그를 읽어들이는 Fluentd 플러그인을 구하기는 쉽다. 변형체가 많은데 대부분은 kenjiskywalker/fluent-plugin-rds-slowlog에서 파생됐다. 파일에 저장한 로그의 경우는 in_rds_mysqlslowlog_stream.rb를 써서 처리하면 된다. 우리는 테이블에 저장하기 때문에 전자를 선택했다.이쯤 조사를 마치고 나니 진행방향은 매우 명확하다. 적당히 잘 만든 Fluentd 플러그인을 골라서 적용한 후에 ES에 대시보드를 만들면 된다. 물론 우리는 Kubernetes 위에 모니터링 도구를 띄워야 하니 Dockerize할 필요도 있다. 이쯤에서 또다시 구글링을 하니 무시무시한 게 나온다. inokappa/rds-slowquery-log-demo는 방금 설명한 모든 과정을 하나로 정리해서 제공한다. Docker로 만든 Fluentd와 ES 대시보드 설정을 한데 묶어놓았다. 거기에 파일 로그, 테이블 로그 둘 다 예제로 제공한다. 덕분에 일이 쉽게 끝날 줄 알았다. 하지만!개발한지 꽤 시간이 지난 지라 최신 버전의 Fluentd와 ES에서 계속 문제를 일으켰다. 문제점에 대해 구구절절 설명할 생각은 없고 DailyHotel/rds-slowquery-log-demo를 참고해서 적용하면 된다는 점만 이야기하고자 한다. 일어로 된 README 파일은 구글 번역기를 돌리면 적당히 읽을만해진다.삽질을 약간만 하면 아래와 같이 간지!나는 대시보드를 얻을 수 있으니 해볼만 할 것이다.참! DailyHotel/rds-slowquery-log-demo는 테이블 로그인 경우만 테스트했으니 파일 로그를 사용하는 경우라면 이 점을 주의해야 한다.더 읽을거리Collecting and Analying Slow Query Logs for MySQLRDS(MySQL) のスロークエリを EFK スタック + Docker で出来るだけ手軽に可視化する考察(2)〜 log_output: FILE の場合 〜#데일리 #데일리호텔 #개발 #개발자 #개발팀 #Elasticsearch #엘라스틱서치 #꿀팁 #도입후기 #일지

기업문화 엿볼 때, 더팀스

로그인

/