-
Notifications
You must be signed in to change notification settings - Fork 0
/
rss.xml
1393 lines (1393 loc) · 340 KB
/
rss.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>허원철의 개발 블로그 Blog</title>
<link>https://heowc.dev/</link>
<description>허원철의 개발 블로그 Blog</description>
<lastBuildDate>Fri, 07 Apr 2023 20:00:00 GMT</lastBuildDate>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<generator>https://github.com/jpmonette/feed</generator>
<language>ko</language>
<item>
<title><![CDATA[SpringBoot 테스트 개선해보기 (feat. mock)]]></title>
<link>https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock</link>
<guid>https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock</guid>
<pubDate>Fri, 07 Apr 2023 20:00:00 GMT</pubDate>
<description><![CDATA[지금하고 있는 프로젝트에서 초기에는 테스트가 거의 없는 수준이였지만, 지금은 어느덧 약 800 여개의 테스트 케이스가 CI를 통해 검증되고 있는데요. 이렇게 테스트 케이스가 나날이 많아지면서 여기서 생긴 문제점과 이를 어떻게 해결했는지 공유해보고자 합니다.]]></description>
<content:encoded><![CDATA[<p>지금하고 있는 프로젝트에서 초기에는 테스트가 거의 없는 수준이였지만, 지금은 어느덧 약 800 여개의 테스트 케이스가 CI를 통해 검증되고 있는데요. 이렇게 테스트 케이스가 나날이 많아지면서 여기서 생긴 문제점과 이를 어떻게 해결했는지 공유해보고자 합니다.</p>
<p>(아직 Line Coverage가 16% 정도 밖에 되지 않아 갈 길이 머네요... 😵💫)</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="짧디짧은-서비스-구조-설명">짧디짧은 서비스 구조 설명<a class="hash-link" aria-label="짧디짧은 서비스 구조 설명에 대한 직접 링크" title="짧디짧은 서비스 구조 설명에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EC%A7%A7%EB%94%94%EC%A7%A7%EC%9D%80-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%A1%B0-%EC%84%A4%EB%AA%85"></a></h3>
<p>서비스는 그레이들 멀티 모듈을 활용한 여러 서비스를 모듈화해두었습니다.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">gradle-multi-module</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ㄴㅡ a-service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ㄴㅡ b-service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ㄴㅡ c-service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ㄴㅡ d-service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ㄴㅡ ...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="시작은-작은-구멍에-불과했다">시작은 작은 구멍에 불과했다.<a class="hash-link" aria-label="시작은 작은 구멍에 불과했다.에 대한 직접 링크" title="시작은 작은 구멍에 불과했다.에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EC%8B%9C%EC%9E%91%EC%9D%80-%EC%9E%91%EC%9D%80-%EA%B5%AC%EB%A9%8D%EC%97%90-%EB%B6%88%EA%B3%BC%ED%96%88%EB%8B%A4"></a></h3>
<p>시작은 테스트 3~400개가 되었을 무렵부터 였습니다. 평소와 다름 없이 기능 개발을 위해 별도의 feature 브랜치를 만들고 비즈니스 로직과 테스트 코드를 추가했는데 개발 장비에서 잘 돌던 테스트가 CI가 실패하기 시작했습니다. 로그를 보니 OOM이 발생하는 것이였는데요. 여기서 테스트 케이스가 많아져서 그런가 보다... 라는 짧은 생각으로 다음과 같은 처리를 했습니다. 😅</p>
<div class="language-groovy codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-groovy codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">test {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> maxHeapSize = "1G"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="그리고-구멍은-더-커지기-시작했다">그리고 구멍은 더 커지기 시작했다.<a class="hash-link" aria-label="그리고 구멍은 더 커지기 시작했다.에 대한 직접 링크" title="그리고 구멍은 더 커지기 시작했다.에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B5%AC%EB%A9%8D%EC%9D%80-%EB%8D%94-%EC%BB%A4%EC%A7%80%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%96%88%EB%8B%A4"></a></h3>
<p>그리고 다시 700개가 넘어갈 무렵 문제는 다시 시작되었습니다. 이 글을 작성하는 시점에는 이 문제에 대한 원인을 알고 있기 때문에 다시 시작되었다기보다는 고이 덮어둔 문제(?)가 커질때로 커진 것이죠. CI 로그를 확인해보았고, 이는 역시나 OOM이 발생해 있었습니다.</p>
<p>여기서 여러가지 생각이 들었습니다.</p>
<ul>
<li>메모리가 1G가 넘어갈 만큼 테스트가 많은가?</li>
<li>무거운 테스트가 있는가?</li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="메모리가-1g가-넘어갈-만큼-테스트가-많은가">메모리가 1G가 넘어갈 만큼 테스트가 많은가?<a class="hash-link" aria-label="메모리가 1G가 넘어갈 만큼 테스트가 많은가?에 대한 직접 링크" title="메모리가 1G가 넘어갈 만큼 테스트가 많은가?에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EB%A9%94%EB%AA%A8%EB%A6%AC%EA%B0%80-1g%EA%B0%80-%EB%84%98%EC%96%B4%EA%B0%88-%EB%A7%8C%ED%81%BC-%ED%85%8C%EC%8A%A4%ED%8A%B8%EA%B0%80-%EB%A7%8E%EC%9D%80%EA%B0%80"></a></h4>
<p>테스트가 수천개, 수만개를 가진 오픈소스들을 보면 1000개도 안된 테린이(?) 수준이였기 때문에 사실상 결코 많은 수준이 아니였습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="무거운-테스트가-있는가">무거운 테스트가 있는가?<a class="hash-link" aria-label="무거운 테스트가 있는가?에 대한 직접 링크" title="무거운 테스트가 있는가?에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EB%AC%B4%EA%B1%B0%EC%9A%B4-%ED%85%8C%EC%8A%A4%ED%8A%B8%EA%B0%80-%EC%9E%88%EB%8A%94%EA%B0%80"></a></h4>
<p>물론, 주로 통합 테스트 <code>@SpringBootTest</code>를 활용한 통합테스트를 작성하고 있습니다. 하지만, 주기적으로 데이터베이스 초기화 등으로 상태를 가지는 객체들을 일괄적으로 비워주고 있기 때문에 문제가 된다고 생각되진 않았습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="어디가-문제인가">어디가 문제인가?<a class="hash-link" aria-label="어디가 문제인가?에 대한 직접 링크" title="어디가 문제인가?에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EC%96%B4%EB%94%94%EA%B0%80-%EB%AC%B8%EC%A0%9C%EC%9D%B8%EA%B0%80"></a></h3>
<p>다행스럽게도 로컬 환경에서도 손쉽게 재현이 가능했기 때문에 특정 모듈에 대한 전체 테스트 실행하여 힙 덤프를 떠보았습니다.</p>
<p><img decoding="async" loading="lazy" alt="Alt as-is" src="https://heowc.dev/assets/images/as-is-fe4a3cbda4e7bd273adc949f1d393477.png" width="1686" height="1494" class="img_ev3q">
(tool: <a href="https://www.eclipse.org/mat/" target="_blank" rel="noopener noreferrer">https://www.eclipse.org/mat/</a>)</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="거대한-defaultlistablebeanfactory-수십개">거대한 <code>DefaultListableBeanFactory</code> 수십개...<a class="hash-link" aria-label="거대한-defaultlistablebeanfactory-수십개에 대한 직접 링크" title="거대한-defaultlistablebeanfactory-수십개에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EA%B1%B0%EB%8C%80%ED%95%9C-defaultlistablebeanfactory-%EC%88%98%EC%8B%AD%EA%B0%9C"></a></h4>
<p>스크린샷에 전부 표현되지 않았지만, 상당히 많은 빈 팩토리가 여러개 생긴 것을 볼 수 있습니다. 대략 <code>50MB</code> 사이즈의 빈 팩토리가 20개만 생겨도 <code>1000MB</code>이기 때문에 상당히 비정상적인 경우라고 볼 수 있을 것 같습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="defaultlistablebeanfactory는-왜-많아지는가"><code>DefaultListableBeanFactory</code>는 왜 많아지는가?<a class="hash-link" aria-label="defaultlistablebeanfactory는-왜-많아지는가에 대한 직접 링크" title="defaultlistablebeanfactory는-왜-많아지는가에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#defaultlistablebeanfactory%EB%8A%94-%EC%99%9C-%EB%A7%8E%EC%95%84%EC%A7%80%EB%8A%94%EA%B0%80"></a></h4>
<p>공식문서를 확인해본 결과, 다음과 같은 목차가 있는 것을 알 수 있습니다.
(목차만 봐도 느낌이 쌔합니다...)</p>
<blockquote>
<p><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-ctx-management-caching" target="_blank" rel="noopener noreferrer">Context Caching</a></p>
</blockquote>
<p><img decoding="async" loading="lazy" alt="Alt test-context-cache" src="https://heowc.dev/assets/images/test-context-cache-94d30c681c6ad9ae0e04ad5a44677743.png" width="1876" height="708" class="img_ev3q">
(참고: <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-ctx-management-caching" target="_blank" rel="noopener noreferrer">https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-ctx-management-caching</a>)</p>
<p>대략 요약을 해보자면, 테스트에서 <code>ApplicationContext</code>를 재사용하기 위해 테스트에 적용된 설정들을 참고하여 이를 결정합니다. 여기서 문제가 되었던 부분은 <code>@MockBean</code>이였습니다. 지금 생각해보면, 스프링 빈을 자연스럽게 mock으로 만들어주는데 이게 어떻게 동작하는걸까를 깊게 생각해보지 않는 것이 결정적인 문제였던 것 같습니다. 🤣</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="우리는-왜-mockbean를-사용하는가">우리는 왜 <code>@MockBean</code>를 사용하는가?<a class="hash-link" aria-label="우리는-왜-mockbean를-사용하는가에 대한 직접 링크" title="우리는-왜-mockbean를-사용하는가에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EC%9A%B0%EB%A6%AC%EB%8A%94-%EC%99%9C-mockbean%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80"></a></h4>
<p>우선 <code>@MockBean</code>은 mockito에서 제공해주는 mock을 스프링부트에서 테스트시에 스프링 빈을 만들때 mock으로 만들어주는 애노테이션입니다. (보다 자세한 내용은 <code>MockitoPostProcessor</code>, <code>MockitoTestExecutionListener</code>를 찾아보시면 더욱 도움이 될 것 같습니다.)</p>
<p>저도 그렇지만, 프로젝트 대부분의 테스트 코드의 <code>@MockBean</code>을 사용하는 부분이 특정되어 있습니다. 이 부분은 서비스 구조와 연관이 있는데요. 모듈로 분리된 여러 서비스는 다양하게 서로의 정보를 주고 받는데, 이 중 <strong>서로 http 요청을 하는 부분</strong>이 있습니다.</p>
<!-- -->
<p>Classicist 측면에서 접근해보자면 테스트에 활용할 각각의 테스트 서버들을 준비해두고 이를 활용하면 됩니다. 하지만, 아직 테스트 숙련도가 높지도 않고, 비용/시간/우선순위면에서도 그렇게 해야할 만큼 고도화가 필요한 상황이 아니였기에 적절한 해결책은 아니였습니다.</p>
<p>그렇디면 이를 어떻게 해야할까요? <strong>API 요청에 대한 클래스들을 미리 mock으로 만들 순 없을까요?</strong></p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="beanpostprocessor"><code>BeanPostProcessor</code><a class="hash-link" aria-label="beanpostprocessor에 대한 직접 링크" title="beanpostprocessor에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#beanpostprocessor"></a></h4>
<p>스프링에는 <code>BeanPostProcessor</code>라는 것이 있습니다. 이에 대한 자세한 내용은 <a href="https://heowc.dev/2020/07/04/how-does-autowired-work">Spring - @Autowired는 어떻게 동작하는 걸까?</a>을 참고하시면 됩니다.</p>
<p>정말 다행스럽게도 우리는 공통적으로 <code>open-feign</code>을 활용하여 API 요청을 하고 있습니다. 그렇기에 별도의 <code>BeanPostProcessor</code>를 만들고 만들어진 <code>open-feign</code>을 선별하여 mock으로 바꿔주면 테스트마다 별도의 mock을 만들지 않고 다음과 같이 사용할 수 있습니다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">//@MockBean</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token annotation punctuation" style="color:#393A34">@Autowired</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 👈 👈</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token class-name">AServiceFeign</span><span class="token plain"> feign</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>물론, 팀에 이 사실을 전파하지 않는다면 어떻게 이게 되지? 라고 할 수 있겠네요...</p>
<p align="center"><img src="https://heowc.dev/img/improve-spring-integration-test/why.png"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="결과는">결과는...?<a class="hash-link" aria-label="결과는...?에 대한 직접 링크" title="결과는...?에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EA%B2%B0%EA%B3%BC%EB%8A%94"></a></h3>
<p>결과는 성공적이였습니다.</p>
<p><img decoding="async" loading="lazy" alt="Alt to-be" src="https://heowc.dev/assets/images/to-be-c33b91d481e8cde2798bf3af246888c4.png" width="1712" height="1308" class="img_ev3q"></p>
<p><code>maxHeapSize</code>을 더 늘려보고 테스트해보면 정확한 heap 차이를 알 수 있겠지만, <code>1G</code>이라고 볼 때 <strong>약 5배이상 메모리 절감</strong>을 보여줬고, <code>ApplicationContext</code> 또한 새로 만들지 않았기 때문에 <strong>속도 측면에서도 약 2배 가량 빨라진 것</strong>을 볼 수 있었습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="as-is">AS-IS<a class="hash-link" aria-label="AS-IS에 대한 직접 링크" title="AS-IS에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#as-is"></a></h4>
<p><img decoding="async" loading="lazy" alt="Alt speed-as-is" src="https://heowc.dev/assets/images/speed-as-is-a9d2b638c27967a25f0a20805d8e5f6c.png" width="1970" height="262" class="img_ev3q"></p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="to-be">TO-BE<a class="hash-link" aria-label="TO-BE에 대한 직접 링크" title="TO-BE에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#to-be"></a></h4>
<p><img decoding="async" loading="lazy" alt="Alt speed-to-be" src="https://heowc.dev/assets/images/speed-to-be-02b1330ebcd571f9db9b1ad054f9faa1.png" width="1970" height="262" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="배운점">배운점<a class="hash-link" aria-label="배운점에 대한 직접 링크" title="배운점에 대한 직접 링크" href="https://heowc.dev/2023/04/07/improve-spring-integration-test-for-mock#%EB%B0%B0%EC%9A%B4%EC%A0%90"></a></h3>
<ol>
<li>테스트 코드도 비즈니스 코드 작성하듯이 더 많은 고민을 해보자</li>
<li>부가적인 설정은 그냥 따라오지 않는다</li>
<li>보다 나은 테스트 환경을 만들자</li>
</ol>]]></content:encoded>
<category>spring-boot</category>
<category>test</category>
<category>mock</category>
</item>
<item>
<title><![CDATA[serverless-image-handler]]></title>
<link>https://heowc.dev/2022/04/11/serverless-image-handler</link>
<guid>https://heowc.dev/2022/04/11/serverless-image-handler</guid>
<pubDate>Mon, 11 Apr 2022 20:00:00 GMT</pubDate>
<description><![CDATA[Serverless Image Handler는 AWS에서 제공하는 다양하고 검증된 서비스들을 조합하여 만들어진 솔루션(?)입니다.]]></description>
<content:encoded><![CDATA[<p>Serverless Image Handler는 AWS에서 제공하는 다양하고 검증된 서비스들을 조합하여 만들어진 솔루션(?)입니다.</p>
<p>운영하는 서비스에 종속적인 기능들이 아니라면 한번 찾아보면 이미 만들어져 있을 법한 것들이 있기 때문에 한번 서칭해보면 좋을 듯 한데요.
그 예로, 로깅 중앙화, 제가 사용해본 서버리스 이미지 핸들러 등등이 존재합니다.</p>
<p><a href="https://github.com/aws-solutions/" target="_blank" rel="noopener noreferrer">https://github.com/aws-solutions/</a></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="아키텍처">아키텍처<a class="hash-link" aria-label="아키텍처에 대한 직접 링크" title="아키텍처에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98"></a></h3>
<p><img decoding="async" loading="lazy" src="https://raw.githubusercontent.com/aws-solutions/serverless-image-handler/main/architecture.png" alt="Overview" class="img_ev3q">
< 출처: <a href="https://github.com/aws-solutions/serverless-image-handler" target="_blank" rel="noopener noreferrer">https://github.com/aws-solutions/serverless-image-handler</a> ></p>
<p>해당 기능은 말그대로 <strong>서버리스 환경에서 이미지를 요청시에 즉시 핸들링해주는 솔루션</strong>입니다. 제가 맡고 있는 서비스는 원본 이미지를 다양한 크기로 이미지를 노출시킬 수 있어야 했는데요.
미리 만드는 방법도 있지만 이는 정적이고, 마이그레이션이 다소 불편하며, 관리하는 측면에서 만족스럽지 못 했습니다.</p>
<p>이것 이외에도 CloudFront + Lambda@Edge를 사용하는 방법도 있으니 참고하시기 바랍니다.</p>
<ul>
<li><a href="https://aws.amazon.com/blogs/networking-and-content-delivery/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/" target="_blank" rel="noopener noreferrer">https://aws.amazon.com/blogs/networking-and-content-delivery/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/</a></li>
<li><a href="https://medium.com/daangn/lambda-edge%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-on-the-fly-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%A7%95-f4e5052d49f3" target="_blank" rel="noopener noreferrer">https://medium.com/daangn/lambda-edge%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-on-the-fly-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%A7%95-f4e5052d49f3</a></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="기능">기능<a class="hash-link" aria-label="기능에 대한 직접 링크" title="기능에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#%EA%B8%B0%EB%8A%A5"></a></h3>
<ol>
<li>이미지를 다양한 형태로 핸들링할 수 있습니다.<!-- -->
<ul>
<li>해당 기능은 런타임 환경이 Nodejs에서 이루어지며, Nodejs에서 이미지 처리하는 라이브러리인 <code>sharp</code>를 사용하고 있습니다.</li>
<li>리사이징</li>
<li>포맷 변경</li>
<li>퀄리티 변경</li>
<li>...</li>
</ul>
</li>
<li>이미지 URL 변조<!-- -->
<ul>
<li>만약 우리가 300x300 사이즈의 이미지를 제공하고 이를 어느 누구나 변경해서 요청할 수 있다면 어뷰징 대상이 될 수 있을 것 입니다.</li>
<li>300x301, 300x302, 300x303, 300x... 😱</li>
<li><code>signature</code> 라는 파라미터를 받아서 이를 변조하지 못하도록 제공하고 있습니다. (해당 기능은 secret-manager를 통해 별도의 키를 만들어서 사용합니다.)</li>
</ul>
</li>
<li>이미지 자르기, Amazone Rekognition을 활용한 분석 등등을 제공합니다.</li>
<li>...</li>
</ol>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="사용방법">사용방법<a class="hash-link" aria-label="사용방법에 대한 직접 링크" title="사용방법에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95"></a></h3>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="default-방식">Default 방식<a class="hash-link" aria-label="Default 방식에 대한 직접 링크" title="Default 방식에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#default-%EB%B0%A9%EC%8B%9D"></a></h4>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">const imageRequest = JSON.stringify({</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> bucket: "</span><span class="token tag punctuation" style="color:#393A34"><</span><span class="token tag" style="color:#00009f">myImageBucket</span><span class="token tag punctuation" style="color:#393A34">></span><span class="token plain">",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> key: "</span><span class="token tag punctuation" style="color:#393A34"><</span><span class="token tag" style="color:#00009f">myObjectKey</span><span class="token tag punctuation" style="color:#393A34">></span><span class="token plain">",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> edits: { </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> resize: {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> width: ...,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> height: ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">});</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">const url = `${CloudFrontUrl}/${btoa(imageRequest)}`;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34"><</span><span class="token tag" style="color:#00009f">img</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value" style="color:#e3116c">`${url}`</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>위 방식은 url을 json문자열을 base64으로 인코딩해서 전달하는 방식으로 json 안에는 bucket, object key, sharp 정보(?) 등을 포함되어 있습니다.
sharp 관련된 내용은 관련 문서를 참고해서 적용하면 손쉽게 적용할 수 있습니다.</p>
<ul>
<li><a href="https://sharp.pixelplumbing.com/" target="_blank" rel="noopener noreferrer">https://sharp.pixelplumbing.com/</a></li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="thumbor-방식">Thumbor 방식<a class="hash-link" aria-label="Thumbor 방식에 대한 직접 링크" title="Thumbor 방식에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#thumbor-%EB%B0%A9%EC%8B%9D"></a></h4>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">const url = `${CloudFrontUrl}/filter:format(webp)/${object_key}`;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34"><</span><span class="token tag" style="color:#00009f">img</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value" style="color:#e3116c">`${url}`</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>위 방식은 기존 url과 비슷하게 유지하면서 사용할 수 있는 방법 중에 하나로, path 중간에 <code>filters:~~~</code>를 넣어 옵션을 적용할 수 있습니다.</p>
<ul>
<li>스펙 참고: <a href="https://docs.aws.amazon.com/solutions/latest/serverless-image-handler/thumbor-filters.html" target="_blank" rel="noopener noreferrer">https://docs.aws.amazon.com/solutions/latest/serverless-image-handler/thumbor-filters.html</a></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="사용-후기">사용 후기<a class="hash-link" aria-label="사용 후기에 대한 직접 링크" title="사용 후기에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#%EC%82%AC%EC%9A%A9-%ED%9B%84%EA%B8%B0"></a></h3>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="장점">장점<a class="hash-link" aria-label="장점에 대한 직접 링크" title="장점에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#%EC%9E%A5%EC%A0%90"></a></h4>
<ol>
<li>
<p>CloudFormation으로 제공되기 때문에 손쉽게 구축할 수 있습니다.</p>
<ul>
<li>AWS 솔루션의 공통적인 장점이겠지만, cdk를 활용해 CloudFormation으로 빌드하여 제공하고 있습니다.</li>
</ul>
</li>
<li>
<p>CloudFront를 사용하기 때문에 한번 요청되면 캐시되어 비용 절감이 가능하다.</p>
<ul>
<li>CloudFront라는 CDN 서비스를 사용하고 있기 떄문에(사실 누가 CloudFront없이 S3를 바로 붙이겠냐만은...) 응답이 캐싱되어 첫 요청이외에는 비용이 많이 절감됩니다.</li>
</ul>
</li>
<li>
<p>On the fly 방식이기 때문에 관리가 용이합니다.</p>
</li>
</ol>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="단점">단점<a class="hash-link" aria-label="단점에 대한 직접 링크" title="단점에 대한 직접 링크" href="https://heowc.dev/2022/04/11/serverless-image-handler#%EB%8B%A8%EC%A0%90"></a></h4>
<ol>
<li>
<p>람다 자체가 요청/응답에 대한 페이로드가 6MB 제한이 있기 때문에 큰 이미지를 전달할 수 없습니다.</p>
<ul>
<li>base64로 인코딩해서 전달하기 때문에 6MB보다 더 작아야 합니다. 😥</li>
</ul>
</li>
<li>
<p>다소 과할 수 있다.</p>
<ul>
<li>어느 것이나 그렇지만 단순하게 쓰고자한다면 조금 과할 수 있다.</li>
</ul>
</li>
<li>
<p>6.0.0 버전까진 gif 대응이 안된다.</p>
<ul>
<li><code>sharp</code>가 <code>0.30.0</code> 버전부터 gif를 지원하기 시작했지만, <code>0.27.0</code> 버전을 사용하고 있기 때문에 대응이 안된다.</li>
<li>gif를 지원하려면 결국엔 별도 빌드를 거쳐서 사용해야함</li>
</ul>
</li>
</ol>
<p>결국, 1,3번 단점 때문에 나의 서비스는 api gateway에 별도 path를 추가 및 직접 S3 서비스를 연결해서 사용하고 있습니다.</p>]]></content:encoded>
<category>aws</category>
<category>aws-solution</category>
<category>serverless</category>
<category>on-the-fly</category>
<category>image</category>
</item>
<item>
<title><![CDATA[블로그 마이그레이션 (feat. docusaurus)]]></title>
<link>https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo</link>
<guid>https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo</guid>
<pubDate>Tue, 22 Feb 2022 13:00:00 GMT</pubDate>
<description><![CDATA[기존에 사용하던 SSG(Static Site Generator)를 hexo에서 docusaurus으로 변경한 작업을 공유해봅니다.]]></description>
<content:encoded><![CDATA[<p>기존에 사용하던 SSG(Static Site Generator)를 <a href="https://hexo.io/" target="_blank" rel="noopener noreferrer">hexo</a>에서 <a href="https://docusaurus.io/" target="_blank" rel="noopener noreferrer">docusaurus</a>으로 변경한 작업을 공유해봅니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="왜-변경했는가">왜 변경했는가?<a class="hash-link" aria-label="왜 변경했는가?에 대한 직접 링크" title="왜 변경했는가?에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#%EC%99%9C-%EB%B3%80%EA%B2%BD%ED%96%88%EB%8A%94%EA%B0%80"></a></h3>
<p>사실 큰 이유는 없습니다. (잉...?😓) 단지, 테마를 변경하고 싶었으나, 사용하던 기존 테마에 의존성이 강하다 보니 이를 다른 테마로 변경하는 비용과 다른 SSG로 변경하는 비용이 비슷하다고 생각했고 그렇게 다른 SSG 도구(?)들을 찾아보게 되었습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="다른-ssg-도구들">다른 SSG 도구들<a class="hash-link" aria-label="다른 SSG 도구들에 대한 직접 링크" title="다른 SSG 도구들에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#%EB%8B%A4%EB%A5%B8-ssg-%EB%8F%84%EA%B5%AC%EB%93%A4"></a></h3>
<p>대표적으로 <a href="https://jamstack.org/generators/" target="_blank" rel="noopener noreferrer">https://jamstack.org/generators/</a> 를 참고하시면 여러 도구들을 찾아볼 수 있는데요. 이왕, 바꾸는김에 jvm 계열 언어로 변경해볼까도 생각했지만 관련 플러그인이나 테마가 너무 적었기에 결국에는 javascript 쪽으로 다시 찾아보게 되었습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="gatsby">Gatsby<a class="hash-link" aria-label="Gatsby에 대한 직접 링크" title="Gatsby에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#gatsby"></a></h4>
<blockquote>
<p><a href="https://www.gatsbyjs.com/" target="_blank" rel="noopener noreferrer">https://www.gatsbyjs.com/</a></p>
</blockquote>
<p>요즘 이 도구가 가장 많이 보여 한번 시도를 해봤습니다. 결과적으로 사용하지 않았지만, 다음과 같은 이유로 <code>Gatsby</code>를 사용하지 않았습니다.</p>
<ol>
<li><code>React</code>를 깊게 알지 못하기 때문에 오히려 불편함</li>
<li>단순히 블로그만 원했던 나에겐 커스텀 요소가 많았음</li>
<li>결국 커스텀을 하기 되면 <code>Gatsby</code> 의존성이 강해질 것이라고 판단</li>
</ol>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="docusaurus">Docusaurus<a class="hash-link" aria-label="Docusaurus에 대한 직접 링크" title="Docusaurus에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#docusaurus"></a></h4>
<blockquote>
<p><a href="https://docusaurus.io/" target="_blank" rel="noopener noreferrer">https://docusaurus.io/</a></p>
</blockquote>
<p>facebook에서 만든 SSG 도구로 개인적으로는 커스텀이 그나마 적었고 쉬웠습니다. 그리고 또 다른 개인적인 이유로는 초록색을 좋아하기 때문에 vue 공식 홈페이지를 모방한(?) <a href="https://github.com/yanm1ng/hexo-theme-vexo" target="_blank" rel="noopener noreferrer">hexo-theme-vexo</a>를 사용했었는데요. docusaurus는 기본적으로 초록색이기 때문에(?) 별다른 테마를 적용하지 않아도 될 만큼 만족스러운 테마였습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="만들어보기">만들어보기<a class="hash-link" aria-label="만들어보기에 대한 직접 링크" title="만들어보기에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0"></a></h3>
<p><a href="https://docusaurus.io/docs/installation" target="_blank" rel="noopener noreferrer">https://docusaurus.io/docs/installation</a> 를 참고하시면 명령어 한줄로 손쉽게 만들 수 있는데요. 만들어진 기본구조는 공식 홈페이지를 참고하시면 자세히 설명되어 있습니다.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npx create-docusaurus@latest [name] [template]</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npx create-docusaurus@latest website classic</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="변경한-부분">변경한 부분<a class="hash-link" aria-label="변경한 부분에 대한 직접 링크" title="변경한 부분에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#%EB%B3%80%EA%B2%BD%ED%95%9C-%EB%B6%80%EB%B6%84"></a></h3>
<p>hexo로 사용하던 기능은 그대로 옮겨가는게 최종 목표였는데요.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="seo">SEO<a class="hash-link" aria-label="SEO에 대한 직접 링크" title="SEO에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#seo"></a></h4>
<p>대부분의 SSG도구들과 마찬가지로 기본적으로 해주기 때문에 따로 설정하지 않았습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="google-adsense">Google Adsense<a class="hash-link" aria-label="Google Adsense에 대한 직접 링크" title="Google Adsense에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#google-adsense"></a></h4>
<p>플러그인이 존재하긴 했지만, 간단한 기능이였기 때문에 local plugin을 만들었습니다.</p>
<p>참고) <a href="https://docusaurus.io/docs/api/plugin-methods" target="_blank" rel="noopener noreferrer">https://docusaurus.io/docs/api/plugin-methods</a> / <code>injectHtmlTags</code></p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="google-analytics">Google Analytics<a class="hash-link" aria-label="Google Analytics에 대한 직접 링크" title="Google Analytics에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#google-analytics"></a></h4>
<p>Google Analytics 4 경우에는 <code>gtag</code>를 사용한다고 하여, gtag로 변경했습니다.</p>
<p>참고) 보다 많은 기능을 제공하며, 기존에 <code>UA-</code>로 시작하는 ID로도 매핑되니 문제없이 적용할 수 있습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="검색-노출">검색 노출<a class="hash-link" aria-label="검색 노출에 대한 직접 링크" title="검색 노출에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#%EA%B2%80%EC%83%89-%EB%85%B8%EC%B6%9C"></a></h4>
<p>Google, Naver 같은 검색 서비스에 잘 노출이 되도록 meta tag를 추가해야하는데요. <a href="https://docusaurus.io/docs/api/themes/configuration#metadata" target="_blank" rel="noopener noreferrer">themeConfig#metadata</a>를 활용하면 손쉽게 추가할 수 있습니다.</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token literal-property property" style="color:#36acaa">metadata</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'google-site-verification'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">content</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'XXXXXXX'</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'naver-site-verification'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">content</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'XXXXXXX'</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="github-action">GitHub Action<a class="hash-link" aria-label="GitHub Action에 대한 직접 링크" title="GitHub Action에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#github-action"></a></h4>
<p>공식 홈페이지를 참고하시면 손쉽게 해결할 수 있습니다. (<a href="https://docusaurus.io/docs/deployment" target="_blank" rel="noopener noreferrer">deployment</a>)</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="algolia-검색창">Algolia 검색창<a class="hash-link" aria-label="Algolia 검색창에 대한 직접 링크" title="Algolia 검색창에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#algolia-%EA%B2%80%EC%83%89%EC%B0%BD"></a></h4>
<p><code>Docusaurus</code> 문서를 보면서 알게된 사실은 <a href="https://docsearch.algolia.com/" target="_blank" rel="noopener noreferrer">DocSearch</a>라는게 존재하는데 이게 sitemap을 크롤링해서 인덱싱을 해주고 UI까지 만들어줍니다. hexo를 사용할 땐 html, css, script를 전부 직접 만들었었는데... 그럴 필요가 없어졌습니다. 👍</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="hexo-tag-gdemo"><code>hexo-tag-gdemo</code><a class="hash-link" aria-label="hexo-tag-gdemo에 대한 직접 링크" title="hexo-tag-gdemo에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#hexo-tag-gdemo"></a></h4>
<p>제일 난감했던 부분인데요. hexo에서는 <a href="https://hexo.io/docs/tag-plugins" target="_blank" rel="noopener noreferrer">tag</a>라는 기능이 있어 별도의 문법을 통해 커스텀 기능을 추가할 수 있었습니다. 그래서 이를 활용해서 <a href="https://heowc.dev/2018/11/14/introduction-hexo-tag-gdemo"><code>hexo-tag-gdemo</code></a>라는걸 만들었는데... 이를 다른 방법으로 해결해야했습니다. (군데군데 사용하고 있었습니다. 🥲)</p>
<p>처음에는 plugable하게 <code>remark</code>, <code>reshape</code> 개념을 좀 더 공부해보고 적용해볼까 했지만, 아직은 너무 과하단 생각이들어서 React로 간단하게 <a href="https://docusaurus.io/docs/markdown-features/react" target="_blank" rel="noopener noreferrer">컴포넌트</a>를 만들어 마무리 했습니다.</p>
<ul>
<li><a href="https://docusaurus.io/docs/markdown-features/plugins" target="_blank" rel="noopener noreferrer">https://docusaurus.io/docs/markdown-features/plugins</a></li>
<li><a href="https://docusaurus.io/docs/markdown-features/react" target="_blank" rel="noopener noreferrer">https://docusaurus.io/docs/markdown-features/react</a></li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="그-외">그 외<a class="hash-link" aria-label="그 외에 대한 직접 링크" title="그 외에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#%EA%B7%B8-%EC%99%B8"></a></h4>
<p>그 외에도, disqus 연동이나 경로를 동일하게 잡아주는 등의 자잘한 작업도 있었습니다. 경로 같은 경우는 hexo를 사용할때 년/월/일/prelink를 적용했었는데 docusaurus에서는 아직 관련 기능을 제공하지 않아, slug를 이용하거나 디렉토리 구조를 변경하면 동일하게 가능합니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="남은-것">남은 것...<a class="hash-link" aria-label="남은 것...에 대한 직접 링크" title="남은 것...에 대한 직접 링크" href="https://heowc.dev/2022/02/22/blog-migrate-to-docusaurus-from-hexo#%EB%82%A8%EC%9D%80-%EA%B2%83"></a></h3>
<p>사실 필요없기도 한데, docusaurus에서는 블로그 기능 이외에도 doc 기능이 있어 별도의 문서 페이지를 만들 수 있습니다. 이를 활용해 자기소개라거나 포트폴리오나 등등 별도 페이지를 구성해볼 생각입니다.</p>]]></content:encoded>
<category>blog</category>
<category>migration</category>
<category>SSG</category>
<category>hexo</category>
<category>docusaurus</category>
</item>
<item>
<title><![CDATA[2021년 개발 회고]]></title>
<link>https://heowc.dev/2021/12/28/2021-develop-retrospection</link>
<guid>https://heowc.dev/2021/12/28/2021-develop-retrospection</guid>
<pubDate>Wed, 29 Dec 2021 01:00:00 GMT</pubDate>
<description><![CDATA[2020년 회고에 이어 2021년에 대한 회고를 하려고 한다.]]></description>
<content:encoded><![CDATA[<p>2020년 회고에 이어 2021년에 대한 회고를 하려고 한다.</p>
<p>올해는 상당히 많은 일들이 있었다. 개인적인 일들과 공적인 일들, 좋은 일들과 나쁜 일들... 전부 언급할 순 없지만 개발 관련된 내용만 언급해보고자한다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="오픈소스">오픈소스<a class="hash-link" aria-label="오픈소스에 대한 직접 링크" title="오픈소스에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4"></a></h3>
<p><img decoding="async" loading="lazy" alt="Alt 2021-github" src="https://heowc.dev/assets/images/2021-github-55b3bd9aef0993583f4d26ba1c9bffc3.png" width="1528" height="1462" class="img_ev3q"></p>
<p>사실 전부 다 기여한 것도 아니고, 기여했어도 대부분 typo 수정 정도다... 😆</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="스프링-프로젝트-컨트리뷰션">스프링 프로젝트 컨트리뷰션<a class="hash-link" aria-label="스프링 프로젝트 컨트리뷰션에 대한 직접 링크" title="스프링 프로젝트 컨트리뷰션에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BB%A8%ED%8A%B8%EB%A6%AC%EB%B7%B0%EC%85%98"></a></h4>
<p>올해는 작년보다 꽤나 많은 오픈소스에 기여를 해왔다. 개인적으로 좋았던 점은 드디어 <strong>스프링 프로젝트에 내 이름 한줄을 넣어보았다는 것이다.</strong> 단순한 기능 추가였기에 살짝(?) 아쉽지만, 그래도 (언제였는지 기억은 안나지만...) 목표로했던 스프링 프로젝트에 이름을 올려본 셈이다.</p>
<p><a href="https://github.com/spring-projects/spring-security/pull/10278" target="_blank" rel="noopener noreferrer">https://github.com/spring-projects/spring-security/pull/10278</a>
(빨리 5.7.0 GA 됐으면 좋겠네요 🙏🙏)</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="아르메리아-컨트리뷰션">아르메리아 컨트리뷰션<a class="hash-link" aria-label="아르메리아 컨트리뷰션에 대한 직접 링크" title="아르메리아 컨트리뷰션에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#%EC%95%84%EB%A5%B4%EB%A9%94%EB%A6%AC%EC%95%84-%EC%BB%A8%ED%8A%B8%EB%A6%AC%EB%B7%B0%EC%85%98"></a></h4>
<p>그리고 어김없이 아르메리아에 지속적인 기여도 해왔다. 횟수를 거듭할 수록 발전해나가야 하는데... 그런 모습은 보이지 않아서 개인적으로 많이 노력해야겠다고 다짐했다. 그 와중에 조금 나아진게 있다면, 바로, <strong>테스트 작성하기</strong>이다. 메인테이너분들이 항상 테스트를 작성해달라는 needs가 있었고, 덕분에 계속 작성하다보니 그래도 이전보다는 '테스트 좀 작성해본 사람이다' 라고 얘기할 수 있을 것 같다.</p>
<p>테스트를 작성하면서 느낀 것은 <strong>테스트는 다다익선</strong>이다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="젯브레인-all-product-pack">젯브레인 all-product pack<a class="hash-link" aria-label="젯브레인 all-product pack에 대한 직접 링크" title="젯브레인 all-product pack에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#%EC%A0%AF%EB%B8%8C%EB%A0%88%EC%9D%B8-all-product-pack"></a></h4>
<p>이러한 오픈소스 활동 덕분인지, 젯브레인에 오픈소스로서 라이센스를 받을 수 없냐고 문의를 넣자마자 답변으로 라이센스를 받을 수 있었다. 오픈 소스 기여를 장려하기 위해 이와 같은 라이센스가 있다는 걸 알고 있었기에, 어떻게 신청하는지 내가 해온 이력과 함께 문의를 넣었는데, 스피드하게 줘서 굉장히 당황스럽고, 놀랍고, 기뻤다..! 😍</p>
<p><img decoding="async" loading="lazy" alt="Alt jetbrain-allproduct-pack" src="https://heowc.dev/assets/images/jetbrain-allproduct-pack-d0cf6357e3c725f41d9f8899aa4a5432.png" width="2480" height="1158" class="img_ev3q"></p>
<p><strong>(사랑해요~ 젯브레인 😍)</strong></p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="hackoberfest">hackoberfest<a class="hash-link" aria-label="hackoberfest에 대한 직접 링크" title="hackoberfest에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#hackoberfest"></a></h4>
<p><img decoding="async" loading="lazy" alt="Alt hacktoberfest-completed" src="https://heowc.dev/assets/images/hacktoberfest-completed-d0bb8baecb97e4d0629794c0e69743b1.png" width="720" height="593" class="img_ev3q"></p>
<p>19년에 달성하고 20년에는 달성하지 못한 hackoberfest를 올해는 달성했다. 올해는 상품 중에 약간 특이하게 내 이름으로 기증하는(?) 나무를 심을 수 있다고 하여 나무를 선택했다. 🌳</p>
<p><img decoding="async" loading="lazy" alt="Alt hacktoberfest-gift" src="https://heowc.dev/assets/images/hacktoberfest-gift-123f2bfa0c869591175cdca837b6bfb0.png" width="1998" height="690" class="img_ev3q"></p>
<p>그리곤 아직까지 연락이 없는데... 뭐 언젠가 연락주지 않을까 싶은...</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="스터디">스터디<a class="hash-link" aria-label="스터디에 대한 직접 링크" title="스터디에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#%EC%8A%A4%ED%84%B0%EB%94%94"></a></h3>
<p>올해는 그토록 바랬던 사내 스터디도 해보았고, 색다른 스터디도 이것저것 해보았다. 현재진행형으로 이펙티브자바 3판을 하고 있는데, 항상 느끼는거지만 이펙티브자바는 여러번 읽어야할 정도로 너무 어려운 책인 것 같다.</p>
<p>스터디를 해오면서 아쉬운 점이 있다면, 바쁠때 하던 '블로깅하기'를 제대로 참여하지 못했다. 이를 계기로 소홀했던 블로그도 보다 더 작성해보고, 이를 발표(?), 공유(?)하면서 말하는 연습도 하고 싶었지만, 시기가 적절하지 못 했던지, 마음가짐이 삐뚤었던 것 인지...(음?)</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="독서">독서<a class="hash-link" aria-label="독서에 대한 직접 링크" title="독서에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#%EB%8F%85%EC%84%9C"></a></h3>
<p>JPA 프로그래밍, 함께 자라기, 테스트 관련 도서들... (분명 더 읽은 것 같은데 기억이 잘 나질 않는다...🤣)</p>
<p><strong>가장 좋았던 책은 '함께 자라기' 이다.</strong> 평소의 나의 태도를 다시 돌아보게 되고, 내가 앞으로 어떤 개발자가 되어야할지 고민을 하게된 책이다. 이제 시니어로 넘어가게 되는 년차이다보니 더더욱 와닿았던 책이였음이 분명하다... 👍</p>
<ul>
<li><a href="http://www.yes24.com/Product/Goods/67350256" target="_blank" rel="noopener noreferrer">함께자라기</a></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="블로그">블로그<a class="hash-link" aria-label="블로그에 대한 직접 링크" title="블로그에 대한 직접 링크" href="https://heowc.dev/2021/12/28/2021-develop-retrospection#%EB%B8%94%EB%A1%9C%EA%B7%B8"></a></h3>
<p>올해는 대략 5,6개의 블로그를 작성해보았다. 대다수는 서비스를 개발하면서, 운영하면서 분석해보고, 이를 기록한 것들인데 내년에는 이 보다 디테일하고 면밀히 살펴보며 보다 나은 개발자가 되어야 겠다...! 끝!</p>]]></content:encoded>
<category>2021</category>
<category>develop</category>
<category>retrospection</category>
</item>
<item>
<title><![CDATA[Hibernate NativeQuery HHH-14778 issue (with Postgres)]]></title>
<link>https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres</link>
<guid>https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres</guid>
<pubDate>Sat, 27 Nov 2021 22:00:00 GMT</pubDate>
<description><![CDATA[Postgres 환경에서 Hibernate NativeQuery를 사용할 때 생기는 버그를 디버깅해본 나름의 결과(?)를 공유해봅니다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p>Postgres 환경에서 Hibernate NativeQuery를 사용할 때 생기는 버그를 디버깅해본 나름의 결과(?)를 공유해봅니다.</p>
</blockquote>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="버그-재현">버그 재현<a class="hash-link" aria-label="버그 재현에 대한 직접 링크" title="버그 재현에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#%EB%B2%84%EA%B7%B8-%EC%9E%AC%ED%98%84"></a></h4>
<p>우선, 다음과 같은 테이블이 존재한다고 가정해보자.</p>
<div class="language-postgresql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-postgresql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">create table message (</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> id int8 generated by default as identity,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> body varchar(255), </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> count int8, </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> primary key (id)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그리고 다음과 같은 hibernate 네이티브 쿼리를 실행하면 테스트는 실패합니다. 이 버그는 Postgres에서만 발생하는데요.
보다 자세한 내용을 아래서 설명하도록 하겠습니다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">ApplicationTests</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token annotation punctuation" style="color:#393A34">@Autowired</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token class-name">EntityManager</span><span class="token plain"> entityManager</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token annotation punctuation" style="color:#393A34">@Test</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">nativeQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">assertThatThrownBy</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-></span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Long</span><span class="token plain"> id </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1L</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> query </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> entityManager</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">createNativeQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"UPDATE message SET count = :count WHERE id = :id"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"count"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> query</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">executeUpdate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">hasCauseInstanceOf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">SQLGrammarException</span><span class="token punctuation" style="color:#393A34">.</span><span class="token keyword" style="color:#00009f">class</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">hasRootCauseMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ERROR: column \"count\" is of type bigint but expression is of type bytea\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">" Hint: You will need to rewrite or cast the expression.\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">" Position: 28"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<blockquote>
<p>위 버그는 하이버네이트 이슈로 몇 달전에 리포팅되어 있으니 참고바랍니다.
(참고: <a href="https://hibernate.atlassian.net/browse/HHH-14778" target="_blank" rel="noopener noreferrer">https://hibernate.atlassian.net/browse/HHH-14778</a>)</p>
</blockquote>
<p>위 에러는 <code>query.executeUpdate()</code> 시점에 발생합니다. 어디가 어떻게 문제일까요? 🤔</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="분석">분석<a class="hash-link" aria-label="분석에 대한 직접 링크" title="분석에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#%EB%B6%84%EC%84%9D"></a></h4>
<blockquote>
<p>ERROR: column "count" is of type bigint but expression is of type bytea</p>
</blockquote>
<p>메시지를 보면 <code>bytea</code>으로 <code>bigint</code>를 표현할 수 없다(?)고 합니다. 결국 하이버네이트에서 count 파라미터를 <code>bytea</code>으로 인식하여 쿼리를 실행한다고 볼 수 있습니다. (<code>PrepareStatement</code>를 만들게 되는 것이지요.)</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="queryparameterbindingsimpl"><code>QueryParameterBindingsImpl</code><a class="hash-link" aria-label="queryparameterbindingsimpl에 대한 직접 링크" title="queryparameterbindingsimpl에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#queryparameterbindingsimpl"></a></h5>
<p>그렇게 하이버네이트를 분석해보면서 다음과 같은 코드를 찾았습니다.</p>
<p><img decoding="async" loading="lazy" alt="Alt QueryParameterBindingsImpl" src="https://heowc.dev/assets/images/QueryParameterBindingsImpl-a988bd1cf1d026efde9dd43b62016ff9.png" width="600" height="367" class="img_ev3q"></p>
<p>쿼리를 분석하고 적절한 타입을 바인딩하기 위해 추론하고 적절한 바인더를 만들기 위해 <code>bindType</code>을 정의하는데요. 결국 적절한 <code>bindType</code>을 찾지 못할 경우, <code>SerializableType.INSTANCE</code>로 초기화하게 됩니다. <code>SerializableType.INSTANCE</code>은 아래와 같은 타입을 갖게됩니다.</p>
<ul>
<li>java type: <code>Serializable</code></li>
<li>sql type: <code>Types.VARBINARY</code> (bytea)</li>
</ul>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="varbinarytypedescriptor-basicbinder"><code>VarbinaryTypeDescriptor</code> (<code>BasicBinder</code>)<a class="hash-link" aria-label="varbinarytypedescriptor-basicbinder에 대한 직접 링크" title="varbinarytypedescriptor-basicbinder에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#varbinarytypedescriptor-basicbinder"></a></h5>
<p>실제 쿼리에 파라미터들을 바인딩하기 위해 <code>PrepareStatement</code>에 다음과 같이 셋팅을 합니다. 인자로 넘어가는 <code>sqlDescriptor</code>가 <code>VarbinaryTypeDescriptor</code>인 것을 알 수 있습니다.</p>
<p><img decoding="async" loading="lazy" alt="Alt VarbinaryTypeDescriptor" src="https://heowc.dev/assets/images/VarbinaryTypeDescriptor-19d58cae80c49c6295fc77e1532aa966.png" width="600" height="353" class="img_ev3q"></p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="pgpreparestatement"><code>PgPrepareStatement</code><a class="hash-link" aria-label="pgpreparestatement에 대한 직접 링크" title="pgpreparestatement에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#pgpreparestatement"></a></h5>
<p><a href="https://github.com/pgjdbc/pgjdbc/blob/release/42.2/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java#L189...L268" target="_blank" rel="noopener noreferrer">PgPrepareStatement</a>는 전달받은 <code>sqlType</code>으로 케이스별로 파라미터에 반영합니다. 이러한 과정때문에 테스트가 깨진 것인데요.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setNull</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">int</span><span class="token plain"> parameterIndex</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">int</span><span class="token plain"> sqlType</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throws</span><span class="token plain"> </span><span class="token class-name">SQLException</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">int</span><span class="token plain"> oid</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">switch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sqlType</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token class-name">Types</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">SQLXML</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> oid </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token class-name">Oid</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">XML</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">break</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token class-name">Types</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">INTEGER</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> oid </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token class-name">Oid</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">INT4</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">break</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그렇다면, mysql에서는 어떻게 처리하길래 문제가 되지 않을까요?</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token annotation punctuation" style="color:#393A34">@Override</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setNull</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">int</span><span class="token plain"> parameterIndex</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">int</span><span class="token plain"> sqlType</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throws</span><span class="token plain"> </span><span class="token class-name">SQLException</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">synchronized</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">checkClosed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getConnectionMutex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">PreparedQuery</span><span class="token generics punctuation" style="color:#393A34"><</span><span class="token generics operator" style="color:#393A34">?</span><span class="token generics punctuation" style="color:#393A34">></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getQueryBindings</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setNull</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">getCoreParameterIndex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parameterIndex</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// MySQL ignores sqlType</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>mysql의 <a href="https://github.com/mysql/mysql-connector-j/blob/release/8.0/src/main/user-impl/java/com/mysql/cj/jdbc/ClientPreparedStatement.java#L1671...L1676" target="_blank" rel="noopener noreferrer">ClientPreparedStatement</a>를 살펴보면 다음과 같이 <code>sqlType</code>을 무시하는 것을 알 수 있었습니다. 🤔</p>
<blockquote>
<p>참고로 <code>ServerPreparedStatement</code>도 <code>ClientPreparedStatement</code>를 상속받았기 때문에 동일한 동작을 합니다.</p>
</blockquote>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="분석-정리">분석 정리<a class="hash-link" aria-label="분석 정리에 대한 직접 링크" title="분석 정리에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#%EB%B6%84%EC%84%9D-%EC%A0%95%EB%A6%AC"></a></h5>
<p>결국, 하이버네이트에서 <strong>각 드라이버의 호환성 문제로 인한 버그</strong>라고 볼 수 있을 것 같습니다. (개인적으로는 postgres가 더 정교한 것 같습니다만...) 그런데 한편으로는 HQL같은 엔티티로 쿼리를 작성하면 타입 추론이 가능하지만, 네이티브 쿼리는 어떻게 가능할까 라는 생각이 들기도 합니다.</p>
<p>이렇더라도 우리는 버그를 퇴치(?)를 해야하니 해결방법에 대해 알아보도록 하겠습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="해결방법">해결방법<a class="hash-link" aria-label="해결방법에 대한 직접 링크" title="해결방법에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#%EF%BF%BD%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95"></a></h4>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="1-namedquery로-변경">1. NamedQuery로 변경<a class="hash-link" aria-label="1. NamedQuery로 변경에 대한 직접 링크" title="1. NamedQuery로 변경에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#1-namedquery%EB%A1%9C-%EB%B3%80%EA%B2%BD"></a></h5>
<p>우리는 다음과 같이 <code>NamedQuery</code>로 변경하여, 이를 해결할 수 있습니다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token annotation punctuation" style="color:#393A34">@NamedQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fixedCount"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> query </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"UPDATE Message m SET m.count = :count WHERE m.id = :id"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">Message</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Long</span><span class="token plain"> id </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1L</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> query </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> entityManager</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">createNamedQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"fixedCount"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// <--</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"count"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">executeUpdate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="2-네이티브-쿼리를-사용하되-파라미터를-typedparametervalue를-사용하여-타입-추론이-가능하도록-변경">2. 네이티브 쿼리를 사용하되, 파라미터를 <code>TypedParameterValue</code>를 사용하여 타입 추론이 가능하도록 변경<a class="hash-link" aria-label="2-네이티브-쿼리를-사용하되-파라미터를-typedparametervalue를-사용하여-타입-추론이-가능하도록-변경에 대한 직접 링크" title="2-네이티브-쿼리를-사용하되-파라미터를-typedparametervalue를-사용하여-타입-추론이-가능하도록-변경에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#2-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%BF%BC%EB%A6%AC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%90%98-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC-typedparametervalue%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%ED%83%80%EC%9E%85-%EC%B6%94%EB%A1%A0%EC%9D%B4-%EA%B0%80%EB%8A%A5%ED%95%98%EB%8F%84%EB%A1%9D-%EB%B3%80%EA%B2%BD"></a></h5>
<p><code>setParameter</code>를 살펴보다가 <code>TypedParameterValue</code>를 사용하면 타입을 추론이 가능하다는 것을 알게되었습니다. (<a href="https://github.com/hibernate/hibernate-orm/blob/5.5/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java#L449...L462" target="_blank" rel="noopener noreferrer">AbstractProducedQuery</a>)</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Long</span><span class="token plain"> id </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1L</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> query </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> entityManager</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">createNativeQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"UPDATE message SET count = :count WHERE id = :id"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"count"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">TypedParameterValue</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">LongType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">INSTANCE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// <--</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">executeUpdate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="3-네이티브-쿼리를-사용하되-쿼리에서-강제-캐스팅을-한다">3. 네이티브 쿼리를 사용하되, 쿼리에서 강제 캐스팅을 한다.<a class="hash-link" aria-label="3. 네이티브 쿼리를 사용하되, 쿼리에서 강제 캐스팅을 한다.에 대한 직접 링크" title="3. 네이티브 쿼리를 사용하되, 쿼리에서 강제 캐스팅을 한다.에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#3-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%BF%BC%EB%A6%AC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%90%98-%EC%BF%BC%EB%A6%AC%EC%97%90%EC%84%9C-%EA%B0%95%EC%A0%9C-%EC%BA%90%EC%8A%A4%ED%8C%85%EC%9D%84-%ED%95%9C%EB%8B%A4"></a></h5>
<p>이미 우리는 <code>bytea</code>로 바인딩되다는 것을 알고 있습니다. 이를 인지하고 있다면 <code>cast()</code>함수를 통해 해결할 수 있습니다. 하지만, 이는 버그가 수정되거나 하이버네이트 내부 로직에 의존하는 것이기 때문에 <strong>좋은 방법은 아닌 듯 합니다.</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Long</span><span class="token plain"> id </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1L</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> query </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> entityManager</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">createNativeQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"UPDATE message SET count = cast(cast(:count as text) as bigint) WHERE id = :id"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"count"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">TypedParameterValue</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">LongType</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">INSTANCE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setParameter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">executeUpdate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="4-preparestatement에-직접-액세스하기">4. <code>PrepareStatement</code>에 직접 액세스하기<a class="hash-link" aria-label="4-preparestatement에-직접-액세스하기에 대한 직접 링크" title="4-preparestatement에-직접-액세스하기에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#4-preparestatement%EC%97%90-%EC%A7%81%EC%A0%91-%EC%95%A1%EC%84%B8%EC%8A%A4%ED%95%98%EA%B8%B0"></a></h5>
<p>하이버네이트를 사용하는 경우, <code>EntityManager</code>에서 <code>Session</code>을 꺼내어 <code>Connection</code>을 직접 핸들링할 수 있습니다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">Session</span><span class="token plain"> session </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> entityManager</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">unwrap</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">Session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token keyword" style="color:#00009f">class</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">doWork</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">connection</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-></span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그러므로 우리는 <code>PrepareStatement</code>에 직접 타입을 기입할 수 있습니다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">session</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">doWork</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">connection </span><span class="token operator" style="color:#393A34">-></span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">PreparedStatement</span><span class="token plain"> ps </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> connection</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">prepareStatement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"UPDATE message SET count = ? WHERE id = ?"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ps</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setNull</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token class-name">Types</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">INTEGER</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ps</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setLong</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ps</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">executeUpdate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>해당 코드들은 <a href="https://github.com/heowc-scratch/hibernate_postgres_HHH-14778" target="_blank" rel="noopener noreferrer">hibernate_postgres_HHH-14778</a>에서 확인할 수 있습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="참고">참고<a class="hash-link" aria-label="참고에 대한 직접 링크" title="참고에 대한 직접 링크" href="https://heowc.dev/2021/11/27/hibernate-nativequery-bug-with-postgres#%EC%B0%B8%EA%B3%A0"></a></h4>
<ul>
<li><a href="https://hibernate.atlassian.net/browse/HHH-14778" target="_blank" rel="noopener noreferrer">https://hibernate.atlassian.net/browse/HHH-14778</a></li>
<li><a href="https://github.com/heowc-scratch/hibernate_postgres_HHH-14778" target="_blank" rel="noopener noreferrer">https://github.com/heowc-scratch/hibernate_postgres_HHH-14778</a></li>
<li><a href="https://github.com/hibernate/hibernate-orm" target="_blank" rel="noopener noreferrer">https://github.com/hibernate/hibernate-orm</a></li>
<li><a href="https://github.com/mysql/mysql-connector-j" target="_blank" rel="noopener noreferrer">https://github.com/mysql/mysql-connector-j</a></li>
<li><a href="https://github.com/pgjdbc/pgjdbc" target="_blank" rel="noopener noreferrer">https://github.com/pgjdbc/pgjdbc</a></li>
</ul>]]></content:encoded>
<category>hibernate</category>
<category>native-query</category>
<category>postgres</category>
<category>orm</category>
<category>bug</category>
</item>
<item>
<title><![CDATA[ElastiCache Maintenance]]></title>
<link>https://heowc.dev/2021/07/22/elasticache-maintenance</link>
<guid>https://heowc.dev/2021/07/22/elasticache-maintenance</guid>
<pubDate>Thu, 22 Jul 2021 18:00:00 GMT</pubDate>
<description><![CDATA[최근 AWS의 Redis Service인 ElastiCache를 사용하면서 겪었던 유지관리 기간(Maintenance)에 대한 이슈 해결과정을 적어보고자 합니다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p>최근 AWS의 Redis Service인 ElastiCache를 사용하면서 겪었던 유지관리 기간(Maintenance)에 대한 이슈 해결과정을 적어보고자 합니다.</p>
</blockquote>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="maintenance-이란">Maintenance 이란?<a class="hash-link" aria-label="Maintenance 이란?에 대한 직접 링크" title="Maintenance 이란?에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#maintenance-%EC%9D%B4%EB%9E%80"></a></h3>
<blockquote>
<p><a href="https://aws.amazon.com/ko/elasticache/elasticache-maintenance/" target="_blank" rel="noopener noreferrer">https://aws.amazon.com/ko/elasticache/elasticache-maintenance/</a></p>
</blockquote>
<p>보안 패치나 안정성을 위해 이용자가 지정한 시기에 노드를 교체하거나 클러스터가 다운되거나 특정 샤드의 노드들이 변경됩니다. 문서상에선 몇 초의 다운타임이 발생한다고 하는데요.</p>
<p><em>제가 경험한 바로는 약간 틀린 점(?)이 있었습니다.</em></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="환경">환경<a class="hash-link" aria-label="환경에 대한 직접 링크" title="환경에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#%ED%99%98%EA%B2%BD"></a></h3>
<ul>
<li>Redis Server: AWS ElastiCache Cluster</li>
<li>Redis Client: lettuce <code>5.3.7</code></li>
</ul>
<p>※ lettuce의 경우, 사용하고 있는 버전(<code>5.1.1</code>)에서 버그가 있어 <code>5.3.7</code>으로 변경했었는데요. 자세한 내용은 아래서 다시 설명드리겠습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="maintenance-대응하기">Maintenance 대응하기<a class="hash-link" aria-label="Maintenance 대응하기에 대한 직접 링크" title="Maintenance 대응하기에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#maintenance-%EB%8C%80%EC%9D%91%ED%95%98%EA%B8%B0"></a></h3>
<p>Maintenance는 요일과 시간대(1시간 간격)를 지정해두면, AWS에서 event 알림과 함께 Maintenance 스케줄을 잡습니다. (물론, 갑작스럽게 스케줄이 잡히고 그러진 않습니다.) 이 때, 클러스터가 내려가거나 샤드의 노드들이 변경되거나 재배치하게 되는데요. lettuce client에서는 <a href="https://lettuce.io/core/release/api/io/lettuce/core/cluster/models/partitions/Partitions.html" target="_blank" rel="noopener noreferrer">각 노드들을 캐싱하고 있는 정보들(Partitions)</a>을 리로드해야 하기 때문에 이에 맞는 <a href="https://lettuce.io/core/release/reference/#redis-cluster.refreshing-the-cluster-topology-view" target="_blank" rel="noopener noreferrer">추가 옵션</a>이 필요합니다.</p>
<p><code>spring-data-redis</code>를 사용한다면, 다음과 같이 적용할 수 있습니다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">static</span><span class="token plain"> </span><span class="token class-name">LettuceClientConfiguration</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">lettuceClientConfiguration</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token class-name">ClusterClientOption</span><span class="token plain"> clientOptions </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">ClusterClientOptions</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">builder</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">topologyRefreshOptions</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">ClusterTopologyRefreshOptions</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">builder</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">enablePeriodicRefresh</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// <--</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">enableAllAdaptiveRefreshTriggers</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// <--</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">build</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">timeoutOptions</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">build</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token class-name">LettuceClientConfiguration</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">builder</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">commandTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">shutdownTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">clientOptions</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">clientOptions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">build</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="enableperiodicrefresh">enablePeriodicRefresh(...)<a class="hash-link" aria-label="enablePeriodicRefresh(...)에 대한 직접 링크" title="enablePeriodicRefresh(...)에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#enableperiodicrefresh"></a></h4>
<p>주기적으로 connection을 갱신해주는 옵션 활성화와 기간을 지정할 수 있습니다. 기간은 기본 60초입니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="enablealladaptiverefreshtriggers">enableAllAdaptiveRefreshTriggers()<a class="hash-link" aria-label="enableAllAdaptiveRefreshTriggers()에 대한 직접 링크" title="enableAllAdaptiveRefreshTriggers()에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#enablealladaptiverefreshtriggers"></a></h4>
<p><a href="https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.RefreshTrigger.html" target="_blank" rel="noopener noreferrer">문제가 될만한 operation</a>이 발생한다면 즉시 connection을 갱신해주는 이벤트를 트리거 시켜주는 설정입니다. 해당 기능은 rate-limit 같은(?) 처리가 되어 있어서 퍼포먼스에 문제가 되지 않습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="재현해보기">재현해보기<a class="hash-link" aria-label="재현해보기에 대한 직접 링크" title="재현해보기에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#%EC%9E%AC%ED%98%84%ED%95%B4%EB%B3%B4%EA%B8%B0"></a></h3>
<p>aws에서는 <code>aws-cli</code>를 통해 <a href="https://docs.aws.amazon.com/cli/latest/reference/elasticache/test-failover.html" target="_blank" rel="noopener noreferrer"><code>test-failover</code></a>라는 커맨드를 제공해주고 있으며, 이를 통해 위 현상을 재현해볼 수 있습니다.</p>
<blockquote>
<p>해당 기능은 첫 시도 기준 5회의 제한을 두고 있기 때문에 무분별한 시도는 안하는게 좋습니다.</p>
</blockquote>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="깔끔하지-않은-결론">깔끔하지 않은 결론...<a class="hash-link" aria-label="깔끔하지 않은 결론...에 대한 직접 링크" title="깔끔하지 않은 결론...에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#%EA%B9%94%EB%81%94%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%80-%EA%B2%B0%EB%A1%A0"></a></h3>
<p><code>aws-cli</code>를 통해 <code>test-failover</code>를 실행하면 클러스터가 내려갑니다. 해당 갭은 1분 정도 되는데요. 위 문서에서 얘기한 <strong>몇 초의 다운타임보다 많은 시간이 소요</strong>됨을 알 수 있습니다. 이를 해결할 방법은 딱히 없는 걸로 보여 '서비스 운영에서 있어 다운타임을 최소화 했다'라는 것에 의의를 두고 해당 이슈를 마무리 짓기로 했습니다. 기존에서는 10분정도의 다운타임이 발생했습니다.</p>
<p>다만, 클러스터가 내려가는게 아닌 샤드의 노드들이 변경되거나 재배치되는 것은 다운타임이 아예 없었습니다.</p>
<blockquote>
<p>혹시 다른 방법이 있으면 공유해주시면 감사하겠습니다 :)</p>
</blockquote>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="마무리">마무리...?!<a class="hash-link" aria-label="마무리...?!에 대한 직접 링크" title="마무리...?!에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#%EB%A7%88%EB%AC%B4%EB%A6%AC"></a></h3>
<p>옵션을 추가하고 배포를 몇 번 해보니, 배포 중 다음 에러로그와 함께 애플리케이션 서버가 내려가지 않는 현상이 발생합니다. 해당 버전은 lettuce <code>5.1.1</code>에서 발생했습니다.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ERROR: Failed to submit a listener notification task. Event loop shut down?</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">java.util.concurrent.RejectedExecutionException: event executor terminated</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.util.concurrent.SingleThreadEventExecutor.reject(SingleThreadEventExecutor.java:845)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.util.concurrent.SingleThreadEventExecutor.offerTask(SingleThreadEventExecutor.java:328)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java:321)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:756)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.util.concurrent.DefaultPromise.safeExecute(DefaultPromise.java:768)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:432)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:162)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.channel.DefaultChannelPromise.addListener(DefaultChannelPromise.java:95)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> at io.netty.channel.DefaultChannelPromise.addListener(DefaultChannelPromise.java:30)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><code>jstack</code>을 확인해보니, 해당 애플리케이션 서버에 thread 중 <code>WAITING</code>이 존재하는 것을 알 수 있었습니다. 해당 stack은 shutdown시에 쓰레드 경합이 발생하면서 생긴 문제로 보였는데요. (아쉽게도... 로그가 유실되어 첨부하지 못 했습니다.) 이는 lettuce 저장소 <a href="https://github.com/lettuce-io/lettuce-core/issues/989" target="_blank" rel="noopener noreferrer">issue#989</a>를 통해 바로 해결할 수 있었습니다.</p>
<p>해당 이슈의 커밋 로그를 잠깐 살펴보자면, eventloop가 활성화되어 있는지 여부를 판단하는 <a href="https://github.com/lettuce-io/lettuce-core/commit/75506b8489094b7ad584a3da7e5e7c9eaec5bd39#diff-786974f002247796101b7c31ad0a26c363adc3c02df789d8981c4e2455c52ebaR262" target="_blank" rel="noopener noreferrer">방어코드</a>가 추가된 것을 알 수 있습니다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">boolean</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">isEventLoopActive</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">EventExecutorGroup</span><span class="token plain"> eventExecutors </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> clientResources</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">eventExecutorGroup</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">eventExecutors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isShuttingDown</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>저희는 이것을 <code>5.2.0</code>에 해결된 것으로 보였으나, 그냥 마이너 최신버전(<code>5.3.7</code>)까지 올려서 테스트 해보기로 했습니다. 다행히 배포시에 위 현상이 해결되어 안정적인 스무스하게(?) 배포할 수 있었습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="참고">참고<a class="hash-link" aria-label="참고에 대한 직접 링크" title="참고에 대한 직접 링크" href="https://heowc.dev/2021/07/22/elasticache-maintenance#%EC%B0%B8%EA%B3%A0"></a></h3>
<ul>
<li><a href="https://aws.amazon.com/ko/elasticache/elasticache-maintenance/" target="_blank" rel="noopener noreferrer">https://aws.amazon.com/ko/elasticache/elasticache-maintenance/</a></li>
<li><a href="https://lettuce.io/core/release/reference/#redis-cluster.refreshing-the-cluster-topology-view" target="_blank" rel="noopener noreferrer">https://lettuce.io/core/release/reference/#redis-cluster.refreshing-the-cluster-topology-view</a></li>
<li><a href="https://docs.aws.amazon.com/cli/latest/reference/elasticache/test-failover.html" target="_blank" rel="noopener noreferrer">https://docs.aws.amazon.com/cli/latest/reference/elasticache/test-failover.html</a></li>
<li><a href="https://github.com/lettuce-io/lettuce-core/issues/989" target="_blank" rel="noopener noreferrer">https://github.com/lettuce-io/lettuce-core/issues/989</a></li>
</ul>]]></content:encoded>
<category>elasticache</category>
<category>redis</category>
<category>maintenance</category>
<category>aws</category>
</item>
<item>
<title><![CDATA[Spring WebSocket (with STOMP)]]></title>
<link>https://heowc.dev/2021/07/15/spring-websocket-with-stomp</link>
<guid>https://heowc.dev/2021/07/15/spring-websocket-with-stomp</guid>
<pubDate>Thu, 15 Jul 2021 18:00:00 GMT</pubDate>
<description><![CDATA[최근 웹소켓을 활용한 서비스를 개발하면서 알게된 내용을 간략하게나마 적어봅니다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p>최근 웹소켓을 활용한 서비스를 개발하면서 알게된 내용을 간략하게나마 적어봅니다.</p>
</blockquote>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="websocket-이란">WebSocket 이란?<a class="hash-link" aria-label="WebSocket 이란?에 대한 직접 링크" title="WebSocket 이란?에 대한 직접 링크" href="https://heowc.dev/2021/07/15/spring-websocket-with-stomp#websocket-%EC%9D%B4%EB%9E%80"></a></h3>
<p>웹소켓은 보통 http, long polling, sse와 비교되어 언급되는 기술 중 하나입니다. <code>WebSocket</code>을 검색해보면 위키피디아에 다음과 같이 기록되어 있습니다.</p>
<blockquote>
<p>웹소켓(WebSocket)은 하나의 TCP 접속에 전이중 통신 채널을 제공하는 컴퓨터 통신 프로토콜이다.
<a href="https://ko.wikipedia.org/wiki/%EC%9B%B9%EC%86%8C%EC%BC%93" target="_blank" rel="noopener noreferrer">https://ko.wikipedia.org/wiki/%EC%9B%B9%EC%86%8C%EC%BC%93</a></p>
</blockquote>
<p>전이중 통신 채널을 제공한다는 말은 무엇일까요? 조금 더 읽어보면 다음과 같은 문장이 있습니다.</p>
<blockquote>
<p>웹소켓 프로토콜은 HTTP 폴링과 같은 반이중방식에 비해 더 낮은 부하를 사용하여 웹 브라우저(또는 다른 클라이언트 애플리케이션)과 웹 서버 간의 통신을 가능케...</p>
</blockquote>
<p>단순 HTTP 통신은 반이중방식이라고 적혀있는데요. 우리가 흔히 사용하는 HTTP 통신은 요청을 해야 그것에 대한 응답을 전달받을 수 있습니다. 이를 단방향통신이라고도 부르기도 하지요. 반면에 <strong>WebSocket은 양방향 통신으로 요청을 하지 않아도 서버에서 응답을 전달할 수 있습니다.</strong> 뿐만 아니라, 서버와 클라이언트는 양방향 통신을 하기 위해 서로 연결을 유지하고 있기 때문에 여러가지 장점이 있습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="spring-websocket-with-stomp">Spring WebSocket with Stomp<a class="hash-link" aria-label="Spring WebSocket with Stomp에 대한 직접 링크" title="Spring WebSocket with Stomp에 대한 직접 링크" href="https://heowc.dev/2021/07/15/spring-websocket-with-stomp#spring-websocket-with-stomp"></a></h3>
<p><strong>웹소켓은 현재 텍스트와 바이너리 형태의 메시지를 지원합니다.</strong> (이외에도 확장성을 염두해두고 있기도 합니다.) <a href="https://datatracker.ietf.org/doc/html/rfc6455#section-1.2" target="_blank" rel="noopener noreferrer">RFC 6455#section-1.2</a> 문서를 보면 TCP frame에 Opcode라는 값으로 메시지 타입을 결정하는데요. Spring에서는 이를 사용자가 처리할 수 있도록 <code>WebSocketHandler</code> 인터페이스 하위에 <code>TextWebSocketHandler</code>, <code>BinaryWebSocketHandler</code>를 제공합니다. 하지만, 본문에 대한 포맷이라던지(plain text? json? xml?) 정의된 것이 하나도 없습니다. 다르게 말하면 자유롭게 정의할 수 있다는 뜻이죠.</p>
<p>그렇기때문에 때로는 이러한 정의를 내리는데 상당히 많은 시간을 소비하기도 합니다. (이러한 과정은 우리가 이벤트기반 시스템을 설계할 때 메시지를 정의하는 것과 비슷하다고 볼 수 있을 것 같네요.) <strong>Spring에서는 공식적으로 Stomp를 상위 레벨의 프로토콜로 사용할 수 있도록 지원하고 있습니다.</strong> Stomp란 텍스트 지향 메시징 프로토콜로 여러 방면에서 이를 사용하고 있습니다.</p>
<ul>
<li><a href="https://stomp.github.io/" target="_blank" rel="noopener noreferrer">https://stomp.github.io</a></li>
<li><a href="https://stomp.github.io/implementations.html" target="_blank" rel="noopener noreferrer">https://stomp.github.io/implementations.html</a></li>
</ul>
<p>Stomp는 very simple 이라는 강점을 내세울 수 있을 정도로 단순하며 사용하가 쉽습니다. 형태는 다음과 같습니다.</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">COMMAND</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">header1:value1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">header2:value2</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Body^@</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="메세지-처리-흐름">메세지 처리 흐름<a class="hash-link" aria-label="메세지 처리 흐름에 대한 직접 링크" title="메세지 처리 흐름에 대한 직접 링크" href="https://heowc.dev/2021/07/15/spring-websocket-with-stomp#%EB%A9%94%EC%84%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-%ED%9D%90%EB%A6%84"></a></h3>
<p><img decoding="async" loading="lazy" src="https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/images/message-flow-simple-broker.png" alt="img message-flow" class="img_ev3q"></p>
<p><em><a href="https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow" target="_blank" rel="noopener noreferrer">https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow</a></em></p>
<p>공식 문서에 있는 그림으로, 한 방(?)에 정리할 수 있습니다.</p>
<ol>
<li>클라이언트로 부터 header와 payload 담은 메시지를 전달받으면</li>
<li>request channel (InboundChannel)에서 이를 알맞은 MessageHandler에 전달합니다.</li>
<li>애노테이션 기반의 로직 처리가 포함되는 경우, <code>SimpAnnotationMethodMessageHandler</code>를 통해 <code>@Controller</code>를 호출합니다.</li>
<li>로직 처리 후, 반환된 값을 기반으로 메시지를 만들어 broker channel에 전달합니다.</li>
<li>broker channel은 <code>SimpleBrokerMessageHandler</code>를 통해 구독자들을 가져옵니다.</li>
<li>그리고 각각의 구독자들에게 response channel (OutboundChannel)로 메시지를 전달합니다.</li>
</ol>
<p>하면서 편했던 점은 날 것(?)에 경우, 별도 처리해줘야 할 부분이 상당히 많았겠지만 이를 프레임워크 레벨에서 Stomp와 결합해 상당히 많은 것을 지원해주고있어 단순 command만으로 손쉽게 처리할 수 있다는 점입니다.</p>
<blockquote>
<p>CONNECT, DISCONNECT, SUBSCRIBE, SEND, ....</p>
<ul>
<li>커넥션 연결/끊기,</li>
<li>구독을 하게된 유저들을 별도로 관리할 필요가 없다는 점, (물론 필요할 수도 있음)</li>
<li>메시지 전송</li>
</ul>
</blockquote>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="튜닝-포인트">튜닝 포인트<a class="hash-link" aria-label="튜닝 포인트에 대한 직접 링크" title="튜닝 포인트에 대한 직접 링크" href="https://heowc.dev/2021/07/15/spring-websocket-with-stomp#%ED%8A%9C%EB%8B%9D-%ED%8F%AC%EC%9D%B8%ED%8A%B8"></a></h3>
<p>각 채널에는 <strong>(cpu processor * 2) 갯수 만큼의 쓰레드를 가진 쓰레드풀</strong>을 기본적으로 가집니다. blocking IO가 거의 없는 경우라면 기본설정으로도 충분하다고 생각합니다만, <a href="https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html#websocket-stomp-configuration-performance" target="_blank" rel="noopener noreferrer">문서에도 나와있듯이</a> blocking io가 많은 경우에는 적절한 설정을 해야합니다. 이번에 개발한 서비스의 경우는 거의 모든 메시지마다 외부 api를 호출하기 때문에 stress test를 통해 적정 수치를 맞추어 사용하고 있습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="배포전략">배포전략<a class="hash-link" aria-label="배포전략에 대한 직접 링크" title="배포전략에 대한 직접 링크" href="https://heowc.dev/2021/07/15/spring-websocket-with-stomp#%EB%B0%B0%ED%8F%AC%EC%A0%84%EB%9E%B5"></a></h3>
<p><img decoding="async" loading="lazy" src="https://www.redhat.com/cms/managed-files/blue-green-deployment-model.gif" alt="img blue-green-deployment" class="img_ev3q"></p>
<p>본 서비스를 기존 서비스랑 다르게(rolling 배포) 약간 변형된 <a href="https://www.redhat.com/ko/topics/devops/what-is-blue-green-deployment" target="_blank" rel="noopener noreferrer">blue/green 배포전략</a>을 사용하고 있습니다. 웹소켓은 결국 커넥션을 연결하고 있고 배포를 하게되면 기존 애플리케이션이 내려가면서 커넥션이 끊기게 됩니다. 그럼 중간 로드밸런서를 통해 다른 애플리케이션에 다시 커넥션을 맺고자 시도할텐데요. 이를 rolling 배포로 하게되면 최악의 경우, 서버 댓수만큼 커넥션을 연결/끊기를 반복할 것 입니다. 그렇기 떄문에 새배포 버전에 트래픽을 스위칭하는 방식의 배포 전략인 <strong>blue/green을 사용하는 것이 사용자 경험 측면에서 좋을 수 있습니다.</strong></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="참고">참고<a class="hash-link" aria-label="참고에 대한 직접 링크" title="참고에 대한 직접 링크" href="https://heowc.dev/2021/07/15/spring-websocket-with-stomp#%EC%B0%B8%EA%B3%A0"></a></h3>
<ul>
<li><a href="https://ko.wikipedia.org/wiki/%EC%9B%B9%EC%86%8C%EC%BC%93" target="_blank" rel="noopener noreferrer">https://ko.wikipedia.org/wiki/%EC%9B%B9%EC%86%8C%EC%BC%93</a></li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc6455" target="_blank" rel="noopener noreferrer">https://datatracker.ietf.org/doc/html/rfc6455</a></li>
<li><a href="https://stomp.github.io/implementations.html" target="_blank" rel="noopener noreferrer">https://stomp.github.io/implementations.html</a></li>
<li><a href="https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html" target="_blank" rel="noopener noreferrer">https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html</a></li>
<li><a href="https://www.redhat.com/ko/topics/devops/what-is-blue-green-deployment" target="_blank" rel="noopener noreferrer">https://www.redhat.com/ko/topics/devops/what-is-blue-green-deployment</a></li>
</ul>]]></content:encoded>
<category>spring</category>
<category>websocket</category>
<category>stomp</category>
</item>
<item>
<title><![CDATA[MySQL 인덱스 통계]]></title>
<link>https://heowc.dev/2021/06/17/mysql-index-statistics</link>
<guid>https://heowc.dev/2021/06/17/mysql-index-statistics</guid>
<pubDate>Thu, 17 Jun 2021 13:00:00 GMT</pubDate>
<description><![CDATA[쿼리를 실행시에 예상했던 인덱스를 타지 않는 현상을 알아보고자 합니다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p>쿼리를 실행시에 예상했던 인덱스를 타지 않는 현상을 알아보고자 합니다.</p>
</blockquote>
<p>다음과 같은 테이블이 있다고 가정해보자.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="테이블-생성">테이블 생성<a class="hash-link" aria-label="테이블 생성에 대한 직접 링크" title="테이블 생성에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%83%9D%EC%84%B1"></a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">payment_history</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">id</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">bigint</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">unsigned</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">AUTO_INCREMENT</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">-- 결제 id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">user_id</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">varchar</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">DEFAULT</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">NULL</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">-- 유저 정보</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">status</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">varchar</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">DEFAULT</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">NULL</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">-- 결제 상태</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">id</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">KEY</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">idx_user_id</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">user_id</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">KEY</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">idx_user_id_status</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">user_id</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">status</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="explain">explain<a class="hash-link" aria-label="explain에 대한 직접 링크" title="explain에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#explain"></a></h3>
<p>그리고 다음과 같은 쿼리를 실행한다면 어떤 인덱스를 타게 될까요?</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">payment_history</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">user_id</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'AAAAAAA'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">status</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Completed'</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>다음과 같은 실행 계획을 예상해볼 수 있습니다.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">... | possible_keys | key | ... | rows | ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">------------------------------------------------------------------------</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | idx_user_id, idx_user_id_status | idx_user_id_status | ... | 17714 | ...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>하지만 경우에 따라, 이는 다른 실행 계획 결과가 도출될 수 있습니다.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">... | possible_keys | key | ... | rows | ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">------------------------------------------------------------------------</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | idx_user_id, idx_user_id_status | idx_user_id | ... | 16670 | ...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그 이유는 <strong>옵티마이저가 예상과 다른 인덱스를 탐색</strong>했기 때문입니다. 옵티마이저는 최적의 실행계획을 세우기 위해 인덱스 통계 정보를 의존하게 되는데, MySQL 같은 경우는 <a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-persistent-stats.html#innodb-index-stats-table" target="_blank" rel="noopener noreferrer"><code>mysql.innodb_index_stats</code></a> 라는 테이블에서 해당 정보를 얻을 수 있습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="innodb_index_stats">innodb_index_stats<a class="hash-link" aria-label="innodb_index_stats에 대한 직접 링크" title="innodb_index_stats에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#innodb_index_stats"></a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">mysql.innodb_index_stats</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">database_name</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'test'</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token identifier">table_name</span><span class="token identifier punctuation" style="color:#393A34">`</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'payment_history'</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">... | table_name | index_name | ... | stat_name | stat_value | ... | stat_description</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-------------------------------------------------------------------------------------------------------------------------</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | PRIMARY | ... | n_diff_pfx01 | ... | ... | id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | PRIMARY | ... | n_leaf_pages | ... | ... | Number of leaf pages in the index</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | PRIMARY | ... | size | ... | ... | Number of pages in the index</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id | ... | n_diff_pfx01 | 1,360,401 | ... | id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id | ... | n_diff_pfx02 | 17,432,752 | ... | user_id,id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id | ... | n_leaf_pages | 66,084 | ... | Number of leaf pages in the index</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id | ... | size | 75,904 | ... | Number of pages in the index</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id_status | ... | n_diff_pfx01 | 1,566,677 | ... | user_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id_status | ... | n_diff_pfx02 | 1,660,688 | ... | user_id,status</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id_status | ... | n_diff_pfx03 | 15,970,551 | ... | user_id,status,id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id_status | ... | n_leaf_pages | 81,288 | ... | Number of leaf pages in the index</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">... | payment_history | idx_user_id_status | ... | size | 93,540 | ... | Number of pages in the index</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>여기서 각 인덱스에 대한 <code>stat_value</code>를 보고 대략적으로 판단할 수 있는건, 위 쿼리에서 <code>idx_user_id</code>를 탐색하는 것이 보다 빠르다는 것을 기대할 수 있을 것 같습니다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="인덱스-통계-구성법">인덱스 통계 구성법<a class="hash-link" aria-label="인덱스 통계 구성법에 대한 직접 링크" title="인덱스 통계 구성법에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#%EC%9D%B8%EB%8D%B1%EC%8A%A4-%ED%86%B5%EA%B3%84-%EA%B5%AC%EC%84%B1%EB%B2%95"></a></h3>
<ul>
<li><a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-performance-optimizer-statistics.html" target="_blank" rel="noopener noreferrer">https://dev.mysql.com/doc/refman/5.6/en/innodb-performance-optimizer-statistics.html</a></li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="1-innodb_stats_persistenton">1. innodb_stats_persistent=ON<a class="hash-link" aria-label="1. innodb_stats_persistent=ON에 대한 직접 링크" title="1. innodb_stats_persistent=ON에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#1-innodb_stats_persistenton"></a></h4>
<p><a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html#sysvar_innodb_stats_persistent" target="_blank" rel="noopener noreferrer">innodb_stats_persistent</a>은 MySQL 5.6.2에서 생겼으며, 5.6.6부터 기본값이 ON입니다.</p>
<p><a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html#sysvar_innodb_stats_auto_recalc" target="_blank" rel="noopener noreferrer">innodb_stats_auto_recalc</a>은 자동 계산 여부를 지정하는 옵션입니다. (기본값은 ON) 테이블의 10% 변경이 있을 때 재계산을 하는데, 아쉽게도 이 값은 하드코딩 되어있어 변경할 수 없습니다. (<a href="https://github.com/mysql/mysql-server/blob/5.6/storage/innobase/row/row0mysql.cc#L1106" target="_blank" rel="noopener noreferrer">mysql/mysql-server</a>)</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="2-innodb_stats_persistentoff">2. innodb_stats_persistent=OFF<a class="hash-link" aria-label="2. innodb_stats_persistent=OFF에 대한 직접 링크" title="2. innodb_stats_persistent=OFF에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#2-innodb_stats_persistentoff"></a></h4>
<p><a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html#sysvar_innodb_stats_on_metadata" target="_blank" rel="noopener noreferrer">innodb_stats_on_metadata</a>을 ON하면 다음 쿼리시 재계산을 할 수 있습니다. (기본값은 OFF)</p>
<ul>
<li>SHOW TABLE STATUS</li>
<li>SHOW INDEX</li>
<li>INFORMATION_SCHEMA.TABLES</li>
<li>INFORMATION_SCHEMA.STATISTICS</li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="3-analyze-table-table_name">3. ANALYZE TABLE <code>table_name</code><a class="hash-link" aria-label="3-analyze-table-table_name에 대한 직접 링크" title="3-analyze-table-table_name에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#3-analyze-table-table_name"></a></h4>
<p><a href="https://dev.mysql.com/doc/refman/5.6/en/analyze-table.html" target="_blank" rel="noopener noreferrer">ANALYZE TABLE <code>table_name</code></a></p>
<ul>
<li>
<p>인덱스 통계 테이블을 재계산합니다.</p>
</li>
<li>
<p>하지만 역시 막쓰는게 아닌 것이... 너무 느리기도 하고 테이블 read lock이 걸리기 때문에 조심해서 사용해야 합니다</p>
<blockquote>
<p>During the analysis, the table is locked with a read lock for InnoDB and MyISAM.</p>
</blockquote>
</li>
<li>
<p>또한 재계산한다고 좋아진다는 보장이 없습니다. 결국 랜덤으로 샘플링을 하기 때문에 좋아질 수도, 나빠질 수도 있습니다.</p>
</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="그렇다면-어떻게-해야할까">그렇다면 어떻게 해야할까?<a class="hash-link" aria-label="그렇다면 어떻게 해야할까?에 대한 직접 링크" title="그렇다면 어떻게 해야할까?에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#%EA%B7%B8%EB%A0%87%EB%8B%A4%EB%A9%B4-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C"></a></h3>
<ul>
<li>테이블 별로 <code>innodb_stats_persistent_sample_pages</code>를 조절하자</li>
<li>적절히 <code>ANALYZE TABLE</code>를 사용하자</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="참고">참고<a class="hash-link" aria-label="참고에 대한 직접 링크" title="참고에 대한 직접 링크" href="https://heowc.dev/2021/06/17/mysql-index-statistics#%EC%B0%B8%EA%B3%A0"></a></h3>
<ul>
<li><a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-performance-optimizer-statistics.html" target="_blank" rel="noopener noreferrer">https://dev.mysql.com/doc/refman/5.6/en/innodb-performance-optimizer-statistics.html</a></li>
<li><a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-analyze-table-complexity.html" target="_blank" rel="noopener noreferrer">https://dev.mysql.com/doc/refman/5.6/en/innodb-analyze-table-complexity.html</a></li>
</ul>]]></content:encoded>
<category>mysql</category>
<category>index</category>
<category>index-statistics</category>
</item>
<item>
<title><![CDATA[Amazon Linux에서 /var/log가 꽉차는 이슈]]></title>
<link>https://heowc.dev/2021/05/23/amazone-linux-log-issue</link>
<guid>https://heowc.dev/2021/05/23/amazone-linux-log-issue</guid>
<pubDate>Sun, 23 May 2021 18:00:00 GMT</pubDate>
<description><![CDATA[개발 초기, 개발서버에서 /var/log에 상당히 많은 데이터가 쌓이면서 골치가 아픈 적이 있다. 리눅스에 대해 많은 지식이 없는 상황에서 다행히 팀원의 도움으로 빨리 해결 방법을 알게 되었지만, 모르는 내용이 있어 이를 블로깅하여 기록해두려 한다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p>개발 초기, 개발서버에서 <code>/var/log</code>에 상당히 많은 데이터가 쌓이면서 골치가 아픈 적이 있다. 리눅스에 대해 많은 지식이 없는 상황에서 다행히 팀원의 도움으로 빨리 해결 방법을 알게 되었지만, 모르는 내용이 있어 이를 블로깅하여 기록해두려 한다.</p>
</blockquote>
<p>리눅스에서는 내부에서 발생하는 이벤트에 대한 로그를 관리하는 시스템이 존재한다. 일단 한 놈(?)만 파보고자 회사에서 사용하는 <code>amazon linux2</code>의 로깅 시스템에 대해서 알아보고자 한다.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="amazone-linux2">Amazone Linux2<a class="hash-link" aria-label="Amazone Linux2에 대한 직접 링크" title="Amazone Linux2에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#amazone-linux2"></a></h2>
<p>공식문서는 <a href="https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/amazon-linux-ami-basics.html" target="_blank" rel="noopener noreferrer">여기</a>서 확인할 수 있다.</p>
<blockquote>
<p>Amazon Linux는 CentOS처럼 RedHat계열의 리눅스이다.</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="systemd">systemd<a class="hash-link" aria-label="systemd에 대한 직접 링크" title="systemd에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#systemd"></a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="systemd-이란"><code>systemd</code> 이란?<a class="hash-link" aria-label="systemd-이란에 대한 직접 링크" title="systemd-이란에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#systemd-%EC%9D%B4%EB%9E%80"></a></h3>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>위키피디아 - <a href="https://ko.wikipedia.org/wiki/Systemd" target="_blank" rel="noopener noreferrer">https://ko.wikipedia.org/wiki/Systemd</a></div><div class="admonitionContent_BuS1"><p>systemd는 일부 리눅스 배포판에서 유닉스 시스템 V나 BSD init 시스템 대신 사용자 공간을 부트스트래핑하고 최종적으로 모든 프로세스들을 관리하는 init 시스템이다.</p></div></div>
<p><code>Amazone Linux2</code>는 systemd를 <a href="https://ko.wikipedia.org/wiki/Init" target="_blank" rel="noopener noreferrer">init 시스템</a>으로 채택하여 사용하고 있다. 여기서 <strong>init 시스템이란 컴퓨터 시스템의 부팅 과정 중 최초의 프로세스</strong>이다.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="systemd-구조"><code>systemd</code> 구조<a class="hash-link" aria-label="systemd-구조에 대한 직접 링크" title="systemd-구조에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#systemd-%EA%B5%AC%EC%A1%B0"></a></h3>
<p><img decoding="async" loading="lazy" alt="alt systemd-components" src="https://heowc.dev/assets/images/Systemd_components-10de7e9713e740ba7c3d0ee25eb6bfcb.png" width="720" height="405" class="img_ev3q"></p>
<p>위 그림과 같은 구조를 가지고 있다. 이 글에서 모두 언급하기 어렵기도 하고 이슈를 해결하기 위해 봐야했던 부분만 언급해보고자 한다.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="systemd-로깅-시스템"><code>systemd</code> 로깅 시스템<a class="hash-link" aria-label="systemd-로깅-시스템에 대한 직접 링크" title="systemd-로깅-시스템에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#systemd-%EB%A1%9C%EA%B9%85-%EC%8B%9C%EC%8A%A4%ED%85%9C"></a></h2>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>위키피디아(Systemd#journald) - <a href="https://en.wikipedia.org/wiki/Systemd#journald" target="_blank" rel="noopener noreferrer">https://en.wikipedia.org/wiki/Systemd#journald</a></div><div class="admonitionContent_BuS1"><p>systemd-journald is a daemon responsible for event logging, with append-only binary files serving as its logfiles. The system administrator may choose whether to log system events with systemd-journald, syslog-ng or rsyslog. The potential for corruption of the binary format has led to much heated debate</p></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="사용하고-있는-로깅-서비스-확인해보기">사용하고 있는 로깅 서비스 확인해보기<a class="hash-link" aria-label="사용하고 있는 로깅 서비스 확인해보기에 대한 직접 링크" title="사용하고 있는 로깅 서비스 확인해보기에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#%EC%82%AC%EC%9A%A9%ED%95%98%EA%B3%A0-%EC%9E%88%EB%8A%94-%EB%A1%9C%EA%B9%85-%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%99%95%EC%9D%B8%ED%95%B4%EB%B3%B4%EA%B8%B0"></a></h3>
<div id="demo-systemctl" style="min-height:240px"></div><br><br>
<p>현재 실행 중인 <strong>systemd-journald</strong>, <strong>rsyslog</strong>를 찾아보자!</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="systemd-journald">systemd-journald<a class="hash-link" aria-label="systemd-journald에 대한 직접 링크" title="systemd-journald에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#systemd-journald"></a></h3>
<ul>
<li><a href="https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html" target="_blank" rel="noopener noreferrer">https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html</a></li>
<li>시스템의 전반적인 로그를 수집 및 저장하는 데몬 서비스</li>
<li><code>/var/log/journal</code> 하위에 바이너리 형태로 쌓임</li>
<li>설정 파일: <code>/etc/systemd/journald.conf</code></li>
<li>관련된 서비스 및 도구<!-- -->
<ul>
<li><code>systemd-journald.service</code></li>
<li><code>systemd-journal-flush.service</code></li>
<li><code>journalctl</code></li>
</ul>
</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="rsyslog">rsyslog<a class="hash-link" aria-label="rsyslog에 대한 직접 링크" title="rsyslog에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#rsyslog"></a></h3>
<blockquote>
<p><a href="https://www.fluentd.org/" target="_blank" rel="noopener noreferrer">fluentd</a>와 흡사한 도구라고 느껴짐</p>
</blockquote>
<ul>
<li><a href="https://www.rsyslog.com/" target="_blank" rel="noopener noreferrer">https://www.rsyslog.com</a></li>
<li>설정 파일: <code>/etc/rsyslog.conf</code></li>
<li>input/output/parser 모듈을 설정하여 로깅할 수 있음<!-- -->
<ul>
<li><a href="https://www.rsyslog.com/plugins/" target="_blank" rel="noopener noreferrer">https://www.rsyslog.com/plugins/</a></li>
</ul>
</li>
<li><code>amazonlinux2</code>에는 다음과 같은 input 설정이 기본으로 되어 있음 (<code>uxsock</code>,<code>journal</code>)<!-- -->
<ul>
<li>GitHub에서 <a href="https://github.com/rsyslog/rsyslog/blob/91885676001c9df1c2c91514d144cf71755d5d14/platform/redhat/centos/rsyslog.conf" target="_blank" rel="noopener noreferrer">rsyslog/rsyslog - rsyslog.conf</a>를 찾아보니 기본 설정 파일인 것으로 보임</li>
<li><a href="https://www.rsyslog.com/doc/v8-stable/configuration/modules/imuxsock.html" target="_blank" rel="noopener noreferrer">https://www.rsyslog.com/doc/v8-stable/configuration/modules/imuxsock.html</a></li>
<li><a href="https://www.rsyslog.com/doc/v8-stable/configuration/modules/imjournal.html" target="_blank" rel="noopener noreferrer">https://www.rsyslog.com/doc/v8-stable/configuration/modules/imjournal.html</a></li>
</ul>
</li>
</ul>
<div id="demo-rsyslog_conf" style="min-height:220px"></div><br><br>
<ul>
<li>전부 찾아보진 않았지만 Output Module에는 기본 내장 모듈이 존재 (<code>omusrmsg</code>, <code>omfile</code>)</li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="input-moduleimjournal">Input Module(imjournal)<a class="hash-link" aria-label="Input Module(imjournal)에 대한 직접 링크" title="Input Module(imjournal)에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#input-moduleimjournal"></a></h4>
<ul>
<li>imjournal으로도 imklog와 동일하게 커널 로그를 읽을 수 있다고 함</li>
</ul>
<div id="demo-logger" style="min-height:60px"></div><br><br>
<div id="demo-tail" style="min-height:80px"></div><br><br>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="logrotate">logrotate<a class="hash-link" aria-label="logrotate에 대한 직접 링크" title="logrotate에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#logrotate"></a></h2>
<p>그런데 <code>/var/log/messages</code> 를 검색해보면 날짜별로 rotate되는 것으로 보인다. 또 이건 어떤 서비스가 이를 처리하는 것 일까?</p>
<div id="demo-ls-message" style="min-height:100px"></div><br><br>
<p>그것이 바로 <a href="https://linux.die.net/man/8/logrotate" target="_blank" rel="noopener noreferrer">logrotate</a>라는 녀석이다. 요약하자면, <code>/etc/cron.daily/logrotate</code> 이 스크립트가 매일 실행되면서 <code>/etc/logrotate.conf</code>과 <code>/etc/logrotate.d/</code> 하위에 파일들의 설정으로 rotate가 되는 것인데, 자세한 설명은 <a href="https://server-talk.tistory.com/271" target="_blank" rel="noopener noreferrer">이 블로그</a>에 자세히 설명되어 있어 이를 대신한다.</p>
<blockquote>
<p>실제로 <code>/etc/logrotate.d/syslog</code> 설정을 확인 해보면 <code>/var/log/messages</code>가 rotate 되는 목록에 포함되어 있는 것을 알 수 있다.</p>
</blockquote>
<div id="demo-cat" style="min-height:200px"></div><br><br>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="결론">결론<a class="hash-link" aria-label="결론에 대한 직접 링크" title="결론에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#%EA%B2%B0%EB%A1%A0"></a></h2>
<ol>
<li>시스템 이벤트가 발생하면 systemd-journald가 이를 수집</li>
<li>systemd-journald에서 바이너리 형태로 로그 파일을 만듬</li>
<li>rsyslog(input module=imjournal)으로 systemd-journald에서 syslog로 구조화된 로그를 가져옴</li>
<li><strong>/var/log</strong> 하위에 rule에 맞는 로그로 출력</li>
<li><strong>/var/log</strong> 하위 로그들은 logrotate로 인해 매일 rotate</li>
</ol>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="느낀점">느낀점<a class="hash-link" aria-label="느낀점에 대한 직접 링크" title="느낀점에 대한 직접 링크" href="https://heowc.dev/2021/05/23/amazone-linux-log-issue#%EB%8A%90%EB%82%80%EC%A0%90"></a></h2>
<ul>
<li><code>/var/log</code>에 굉장히 다양한 로그가 쌓이는 것을 알게되었다. (부팅, 메일, 메시지, ... 등등)</li>
<li><code>systemd</code>에 기본적인 로그 시스템만으로도 다양한 로그를 가공 및 처리를 할 수 있다. 그렇기 떄문에 이를 생각하지 않고 별도의 로그를 가공한다고 작업을 추가하면, 이중으로 작업하게 되는 것이라고 판단된다.</li>
<li><code>systemd-journald</code>을 사용하면 <code>/var/log/journal</code>에 바이너리 형태로 로그가 쌓이는데 이게 맞는 것일까? 🤔<!-- -->
<ul>
<li><code>journalctl</code>으로도 볼 수 있지만, <code>/var/log/messages</code>에 쌓이는 것만으로도 충분하지 않나라는 생각이 든다. (이것도 이중 작업이지 않나...? 🤔)</li>
</ul>
</li>
<li>(java) 무심코 logback의 console appender으로 설정해두고 서버에 반영하면, <code>/var/log/messages</code>에 쌓이는 것을 경험했다. 🤣</li>
<li>현재 서비스의 애플리케이션 로그를 <code>crontab</code>으로 별도의 스크립트를 등록하여 rotate하고 있는데 <code>logrotate</code>으로 처리하면 보다 깔끔한 처리가 되지 않을까 싶다.</li>
</ul>]]></content:encoded>
<category>linux</category>
<category>amazonelinux</category>
<category>systemd</category>
<category>systemd-journald</category>
<category>rsyslog</category>
<category>log</category>
</item>
<item>
<title><![CDATA[AWS Aurora DB Cluster - FailOver 대응하기]]></title>
<link>https://heowc.dev/2021/02/13/aurora-cluster-failover</link>
<guid>https://heowc.dev/2021/02/13/aurora-cluster-failover</guid>
<pubDate>Sat, 13 Feb 2021 17:19:00 GMT</pubDate>
<description><![CDATA[최근 회사에서 오로라디비를 스케일 업하기 위해서 failover 테스트를 하다가 있었던 이슈를 공유합니다.]]></description>
<content:encoded><![CDATA[<p>최근 회사에서 오로라디비를 스케일 업하기 위해서 failover 테스트를 하다가 있었던 이슈를 공유합니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="진행-플로우">진행 플로우<a class="hash-link" aria-label="진행 플로우에 대한 직접 링크" title="진행 플로우에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#%EC%A7%84%ED%96%89-%ED%94%8C%EB%A1%9C%EC%9A%B0"></a></h4>
<p>진행했던 플로우는 다음과 같습니다.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">slave 스케일 업</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">slave를 master로 승격(failover)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">slave 스케일 업</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="에러-발생">에러 발생<a class="hash-link" aria-label="에러 발생에 대한 직접 링크" title="에러 발생에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#%EC%97%90%EB%9F%AC-%EB%B0%9C%EC%83%9D"></a></h4>
<p><strong>1번의 경우,</strong> 인스턴스가 내려갔다 올라오기 때문에 잠시 동안 slave를 사용할 수 없게 됩니다. 하지만, cluster end-point를 사용하게 되면 자동적으로 slave로 가던 트래픽이 master를 바라보게 됩니다.
<strong>2번의 경우,</strong> master와 slave가 바뀌니 아주 잠깐의 순단이 있겠지만 이는 내부적으로 커넥션이 다시 맺어 정상 동작할것이라고 생각했습니다.</p>
<p>하지만... 2번을 진행함과 동시에 서버에서는 다음과 같은 예외가 출력되고 있었습니다.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">The MySQL server is running with the --read-only option so it cannot execute this statement</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>자동으로 커낵션을 다시 맺어줄꺼란 생각이 틀렸고, 구글링을 통해 다음과 같은 게시물을 찾을 수 있었습니다.
(참고: <a href="https://aws.amazon.com/ko/premiumsupport/knowledge-center/aurora-mysql-db-cluser-read-only-error/" target="_blank" rel="noopener noreferrer">https://aws.amazon.com/ko/premiumsupport/knowledge-center/aurora-mysql-db-cluser-read-only-error/</a>)</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="해결방법">해결방법<a class="hash-link" aria-label="해결방법에 대한 직접 링크" title="해결방법에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95"></a></h4>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="1-클러스터-라이터-엔드포인트-활용">1. 클러스터 라이터 엔드포인트 활용<a class="hash-link" aria-label="1. 클러스터 라이터 엔드포인트 활용에 대한 직접 링크" title="1. 클러스터 라이터 엔드포인트 활용에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#1-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EB%9D%BC%EC%9D%B4%ED%84%B0-%EC%97%94%EB%93%9C%ED%8F%AC%EC%9D%B8%ED%8A%B8-%ED%99%9C%EC%9A%A9"></a></h5>
<p>우리 서비스에서는 이미 cluster endpoint를 사용하고 있기 때문에 해당이 안되는 내용이였습니다. 혹시 instance endpoint를 사용하고 있다면 cluster endpoint를 사용하길 권장드립니다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="2-dns를-과도하게-캐시하지-않음">2. DNS를 과도하게 캐시하지 않음<a class="hash-link" aria-label="2. DNS를 과도하게 캐시하지 않음에 대한 직접 링크" title="2. DNS를 과도하게 캐시하지 ��않음에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#2-dns%EB%A5%BC-%EA%B3%BC%EB%8F%84%ED%95%98%EA%B2%8C-%EC%BA%90%EC%8B%9C%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%8C"></a></h5>
<p>이번에 알게된 사실이지만 JVM 애플리케이션이 실행된 이우에 DNS 캐시하게 됩니다. 이는 jdk 구현체 마다 옵션이 다르다고 알고 있지만 오라클 jdk를 사용하는 경우는 이를 무기한으로 가지게 됩니다. 변경이 되지 않는다는 얘기죠. 그래서 우리는 <code>networkaddress.cache.ttl</code>를 추가하여 테스트 해보기로 했습니다만... 동일한 예외가 발생했습니다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="3-스마트-드라이버-사용">3. 스마트 드라이버 사용<a class="hash-link" aria-label="3. 스마트 드라이버 사용에 대한 직접 링크" title="3. 스마트 드라이버 사용에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#3-%EC%8A%A4%EB%A7%88%ED%8A%B8-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EC%82%AC%EC%9A%A9"></a></h5>
<p>마지막 방법으로 커넥터를 변경하는 것입니다. 현재 사용 중인 커넥터는 <code>mysql-connector</code>였고, 이를 <code>mariadb-connector</code>로 변경하는 것이였습니다. 조금 더 찾아보니 <code>mariadb-connector</code>에는 <code>mysql-connector</code>와 달리 failover에 대한 대응이 가능한 옵션을 제공해주고 있었습니다.
(참고: <a href="https://mariadb.com/kb/en/failover-and-high-availability-with-mariadb-connector-j/#specifics-for-amazon-aurora" target="_blank" rel="noopener noreferrer">https://mariadb.com/kb/en/failover-and-high-availability-with-mariadb-connector-j/#specifics-for-amazon-aurora</a>)</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">jdbc:mysql:aurora:.....</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>AWS 커뮤니티에도 질문을 남겼지만 대다수의 분들이 <code>mariadb-connector</code>로 변경해서 해결했다는 것을 알 수 있었습니다.</p>
<p>그리고 이제는 마지막일거라고 생각하고 테스트를 했고 성공했습니다..! 조금 더 코드를 살펴보니 실패 시에 내부적으로 커낵션을 다시 맺는 과정이 포함되어 있는 것을 알 수 있었습니다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="또-다른-이슈">또 다른 이슈<a class="hash-link" aria-label="또 다른 이슈에 대한 직접 링크" title="또 다른 이슈에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#%EB%98%90-%EB%8B%A4%EB%A5%B8-%EC%9D%B4%EC%8A%88"></a></h4>
<p>커낵션에 대한 부분은 해결했지만... 다른 예외가 발생합니다. connector를 변경함으로써 발생한 문제인데요. 이런 문제가 발생할 수 있구나 했습니다.</p>
<p>안타까운 현실이지만 서비스에서 <strong>SQL Mapper인 <code>Mybatis</code>를 사용</strong>하며, <strong>datetime의 컬럼 값을 String으로 받는 케이스</strong>가 있었습니다. 기존 <code>mysql-connector</code>에서는 이 값이 <code>2021-02-07 17:19:00</code>으로 할당 됐었다면, <code>mariadb-connector</code>를 사용하면 <code>2021-02-07 17:19:00.0</code>으로 할당 됩니다.</p>
<blockquote>
<p>AS-IS:</p>
<ul>
<li>mysql-connector</li>
<li>2021-02-07 17:19:00</li>
</ul>
<p>TO-BE</p>
<ul>
<li>mariadb-connector</li>
<li>2021-02-07 17:19:00.0</li>
</ul>
</blockquote>
<p>어떻게 된 일까요? 잠깐 코드를 디버깅해보니 이는 커넥터 구현체의 차이에서 서로 다른 응답을 주는 것을 알 수 있었습니다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="mysql-connector">mysql-connector<a class="hash-link" aria-label="mysql-connector에 대한 직접 링크" title="mysql-connector에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#mysql-connector"></a></h5>
<ul>
<li><a href="https://github.com/mysql/mysql-connector-j/blob/18bbd5e68195d0da083cbd5bd0d05d76320df7cd/src/main/core-impl/java/com/mysql/cj/result/StringValueFactory.java#L95...L98" target="_blank" rel="noopener noreferrer">https://github.com/mysql/mysql-connector-j/blob/18bbd5e68195d0da083cbd5bd0d05d76320df7cd/src/main/core-impl/java/com/mysql/cj/result/StringValueFactory.java#L95...L98</a></li>
</ul>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createFromTimestamp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">InternalTimestamp</span><span class="token plain"> its</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">format</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%s %s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createFromDate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">its</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 2021-02-07 17:19:00</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">createFromTime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">InternalTime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">its</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getHours</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> its</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getMinutes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> its</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getSeconds</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> its</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getNanos</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> its</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getScale</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="mariadb-connector">mariadb-connector<a class="hash-link" aria-label="mariadb-connector에 대한 직접 링크" title="mariadb-connector에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#mariadb-connector"></a></h5>
<ul>
<li><a href="https://github.com/mariadb-corporation/mariadb-connector-j/blob/2.7.1/src/main/java/org/mariadb/jdbc/internal/com/read/resultset/rowprotocol/TextRowProtocol.java#L216...L225" target="_blank" rel="noopener noreferrer">https://github.com/mariadb-corporation/mariadb-connector-j/blob/2.7.1/src/main/java/org/mariadb/jdbc/internal/com/read/resultset/rowprotocol/TextRowProtocol.java#L216...L225</a></li>
</ul>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">DATETIME</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">Timestamp</span><span class="token plain"> timestamp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getInternalTimestamp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">columnInfo</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cal</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> timeZone</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">timestamp </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">lastValueNull </span><span class="token operator" style="color:#393A34">&</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BIT_LAST_ZERO_DATE</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> lastValueNull </span><span class="token operator" style="color:#393A34">^=</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">BIT_LAST_ZERO_DATE</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">buf</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pos</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> length</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token class-name">StandardCharsets</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">UTF_8</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> timestamp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 2021-02-07 17:19:00.0</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>대략적으로 정리하자면, <code>mysql-connector</code>는 내부적으로 String.format을 사용하여 <code>YYYY-MM-dd HH:mm:ss</code> 형태를 만들어주는 듯 보입니다. 반면, <code>mariadb-connector</code>는 <code>Timestamp.toString()</code>을 사용합니다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="mybatis---typehandler">mybatis - typeHandler<a class="hash-link" aria-label="mybatis - typeHandler에 대한 직접 링크" title="mybatis - typeHandler에 대한 직접 링크" href="https://heowc.dev/2021/02/13/aurora-cluster-failover#mybatis---typehandler"></a></h5>
<p>우리는 이것 때문에 비즈니스 로직을 건드는 것은 크리티컬한 이슈가 발생할 수 있다고 판단하여 고민 끝에 Mybatis의 <a href="https://mybatis.org/mybatis-3/ko/configuration.html#typeHandlers" target="_blank" rel="noopener noreferrer">TypeHandler</a>를 이용하기로 했습니다. (JPA를 사용하는 경우, <a href="https://www.baeldung.com/jpa-attribute-converters" target="_blank" rel="noopener noreferrer"><code>AttributeConverter</code></a>를 사용할 수 있습니다.)</p>
<p>이미 기본적인 typeHandler로 구현되어 있는 것을 어느정도 커스텀하면 비즈니스 로직을 수정하지 않고도 간단히 커넥터 변경 이슈를 수정할 수 있었고, 성공적으로 테스트를 완료할 수 있었습니다.</p>]]></content:encoded>
<category>aws</category>
<category>aurora</category>
<category>failover</category>
</item>
<item>
<title><![CDATA[2020년 개발 회고]]></title>
<link>https://heowc.dev/2020/12/27/2020-develop-retrospection</link>
<guid>https://heowc.dev/2020/12/27/2020-develop-retrospection</guid>
<pubDate>Sun, 27 Dec 2020 17:19:00 GMT</pubDate>
<description><![CDATA[2019년 회고에 이어 2020년에 대한 회고를 하려고 한다.]]></description>
<content:encoded><![CDATA[<p>2019년 회고에 이어 2020년에 대한 회고를 하려고 한다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="2019년에-세웠던-계획과-2020년-일을-되돌아-보며"><em>2019년에 세웠던 계획과 2020년 일을 되돌아 보며...</em><a class="hash-link" aria-label="2019년에-세웠던-계획과-2020년-일을-되돌아-보며에 대한 직접 링크" title="2019년에-세웠던-계획과-2020년-일을-되돌아-보며에 대한 직접 링크" href="https://heowc.dev/2020/12/27/2020-develop-retrospection#2019%EB%85%84%EC%97%90-%EC%84%B8%EC%9B%A0%EB%8D%98-%EA%B3%84%ED%9A%8D%EA%B3%BC-2020%EB%85%84-%EC%9D%BC%EC%9D%84-%EB%90%98%EB%8F%8C%EC%95%84-%EB%B3%B4%EB%A9%B0"></a></h4>
<blockquote>
<p>별다른 일이 없다면, 꾸준히 <strong>Armeria에 컨트리뷰션</strong> 해볼 생각이다. 아직은 부족한게 많으니 부족한 점을 하나씩 채우면서 보다 딥한 이슈를 처리해보는 것이 첫번째 목표 중 하나이다.</p>
</blockquote>
<p>2019년에 이어 <a href="https://github.com/line/armeria" target="_blank" rel="noopener noreferrer">armeria</a>에 십여 개의 다양한 PR을 진행했다. (내가 만들어낸 버그도 수정해보고...) 언제나 그렇듯 덕분에 너무 재밌었고, 많은 인사이트를 얻어 갈 수 있었다. 그리고 뜻 밖의 선물도 받아 내년에도 열심히 기여를 해야겠다는 생각이 든다. (음?)</p>
<p><img decoding="async" loading="lazy" alt="Alt armeria gift" src="https://heowc.dev/assets/images/armeria_gift-518f0757941adef7a39407dd90e5dc52.png" width="1326" height="672" class="img_ev3q">
<em>(개개인에게 다른 메시지를 보낸 준 것에 또 한 번 감동이다.)</em></p>
<p>2019년에 이어 2020년에도 컨트리뷰톤에 참여했다. 이번 오픈소스는 <a href="https://github.com/pinpoint-apm/pinpoint" target="_blank" rel="noopener noreferrer">pinpoint</a> 였으며, 개발자로서 APM 개발이라는 색다른 영역의 경험을 할 수 있었다. 다만, 아쉬운 부분이라면 활발히 참여하지 못해 하고자 했던 목표를 달성하지 못 하고 간단한 작업밖에 하지 못 했다.</p>
<p><em>그리고 장려상을 수상했다.</em></p>
<p>그 외에도 여러 오픈소스에 다양한 기여를 해보았다.</p>
<ul>
<li><a href="https://github.com/Playtika/testcontainers-spring-boot/pull/540" target="_blank" rel="noopener noreferrer">https://github.com/Playtika/testcontainers-spring-boot/pull/540</a> (<code>typo</code>)</li>
<li><a href="https://github.com/ua-parser/uap-core/pull/451" target="_blank" rel="noopener noreferrer">https://github.com/ua-parser/uap-core/pull/451</a> (<code>new feature</code>)</li>
<li><a href="https://github.com/LogNet/grpc-spring-boot-starter/issues/169" target="_blank" rel="noopener noreferrer">https://github.com/LogNet/grpc-spring-boot-starter/issues/169</a> (<code>clean up</code>)</li>
<li><a href="https://github.com/glorious-codes/glorious-demo/issues/77" target="_blank" rel="noopener noreferrer">https://github.com/glorious-codes/glorious-demo/issues/77</a> (<code>suggestion</code>)</li>
</ul>
<p>마지막으로 올해는 아쉽게도 Hacktoberfest를 완주하지 못 했다.</p>
<blockquote>
<p>현 회사에서 내년에 전체적인 아키텍처 개선을 할 것이라고 하여 <strong>업무에 집중</strong>할 생각이다. 물론, 개선 과정에서 트러블 슈팅이 있다면 이를 명확히 알고 블로깅 해보는 것도 생각하고 있다.</p>
</blockquote>
<p><em>결론을 얘기하자면, 아키텍처 개선은 없었다...</em></p>
<p>하지만 기술적인 큰 변화가 있었다면, 스프링 버전 업그레이드(<code>4.3</code> -> <code>5.1</code>)와 레디스 클라이언트 변경(<code>jedis</code> -> <code>lettuce</code>)을 하였다. <code>lettuce</code>는 적은 커넥션으로 높은 처리량을 자랑하여 많이들 사용하는 라이브러리이다. 하지만 <code>spring-data-redis</code>을 기반으로 사용한다면 특정 버전, 특정 환경에서 문제가 있었고, 이는 <code>2.1.0.RELEASE</code>에서 해결되었다. 혹시 클러스터 환경에서 <code>lettuce</code>를 사용한다면, <code>spring-data-redis</code>를 사용한다면, <code>2.1.0.RELEASE</code> 이상을 사용하고 있는지 확인해보기 바란다.</p>
<ul>
<li><a href="https://github.com/lettuce-io/lettuce-core/issues/816" target="_blank" rel="noopener noreferrer">https://github.com/lettuce-io/lettuce-core/issues/816</a></li>
<li><a href="https://jira.spring.io/browse/DATAREDIS-731" target="_blank" rel="noopener noreferrer">https://jira.spring.io/browse/DATAREDIS-731</a></li>
</ul>
<p>그리고 이메일 발송 기능이 필요하여 <a href="https://aws.amazon.com/ko/ses/" target="_blank" rel="noopener noreferrer">AWS SES</a>를 활용해보기도 했다. 처음 경험해보는 도메인이지만, 잘 정리된 문서, 쉬운 인터페이스덕분에 러닝커브가 많이 높지 않았다. 다만, 평판이라는 개념은 아직도 익숙하지 않다... (이것 때문에 꽤 고생을 했다... - <a href="https://heowc.dev/2020/04/11/aws-ses-review/" target="_blank" rel="noopener noreferrer">AWS - SES 사용 후기</a>)</p>
<blockquote>
<p>읽기로한 개발 도서 읽는 것에 소홀히 하지 않아야 겠다. <strong>다독이 아닌 정독!</strong> 2020년 첫 도서로는 Netty 관련 내용이 될 것 같은데.. Armeria 컨트리뷰션에 조금 보탬이 되지 않을까싶다.</p>
</blockquote>
<p>정독이 목표였지만... 애초에 책을 읽는 것에 소홀해졌다.</p>
<ul>
<li><code>스프링 인 액션</code></li>
<li><code>http 완벽 가이드</code></li>
</ul>
<blockquote>
<p>프로그래밍 관련은 아니지만 그 동안 몸에 너무 소홀히 한 것 같아 <strong>운동</strong>을 할 생각이다. 헬스 정기권 같은 것만 하면 금방 안할게 뻔하니 P.T나 다른 대안책을 찾아보는 중 이다.</p>
</blockquote>
<p>상반기까지는 식단 관리도 하고 필라테스도 하며 몸이 건강해졌지는 것을 느낄 수 있었다. 하지만 하반기부터는 여러가지 이유로 못 하게되어 다시 소홀해졌고 원상복구 중 이다...</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="2021년-계획">2021년 계획<a class="hash-link" aria-label="2021년 계획에 대한 직접 링크" title="2021년 계획에 대한 직접 링크" href="https://heowc.dev/2020/12/27/2020-develop-retrospection#2021%EB%85%84-%EA%B3%84%ED%9A%8D"></a></h4>
<p>2020년은 코로나 19로 인해 몸도 마음도 느러지는 한 해였던 것 같다. 다시 마음을 다 잡고 보다 의미있는 일, 보람찬 일, 재미있는 일을 찾아 2021년에는 뜻깊은 한 해를 보내야겠다.</p>
<ul>
<li>꾸준한 식단관리</li>
<li>독서 4권 이상</li>
<li>적극적인 오픈소스 참여</li>
</ul>]]></content:encoded>
<category>2020</category>
<category>develop</category>
<category>retrospection</category>
</item>
<item>
<title><![CDATA[Network JSON Filter 소개 (Chrome Extension)]]></title>
<link>https://heowc.dev/2020/10/21/introduction-network-json-filter-as-chrome-extension</link>
<guid>https://heowc.dev/2020/10/21/introduction-network-json-filter-as-chrome-extension</guid>
<pubDate>Wed, 21 Oct 2020 19:00:00 GMT</pubDate>
<description><![CDATA[소개]]></description>
<content:encoded><![CDATA[<h3 class="anchor anchorWithStickyNavbar_LWe7" id="소개">소개<a class="hash-link" aria-label="소개에 대한 직접 링크" title="소개에 대한 직접 링크" href="https://heowc.dev/2020/10/21/introduction-network-json-filter-as-chrome-extension#%EC%86%8C%EA%B0%9C"></a></h3>
<p><code>network-json-filter</code>는 주고받는 네트워크 JSON 응답을 필터링하여 보고자 개발한 크롬 익스텐션으로, 다음과 같은 경우에 유용할 수 있다.</p>
<ul>
<li>JSON 응답이 긴 경우, 특정 depth만 보고싶을 때</li>
<li>특정 URL에 대한 JSON 응답만 보고싶은 경우</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="설치">설치<a class="hash-link" aria-label="설치에 대한 직접 링크" title="설치에 대한 직접 링크" href="https://heowc.dev/2020/10/21/introduction-network-json-filter-as-chrome-extension#%EC%84%A4%EC%B9%98"></a></h3>
<p><a href="https://chrome.google.com/webstore/detail/network-json-filter/flcfiogpdlddkjiekpeiedkeoihppekm" target="_blank" rel="noopener noreferrer">chrome 웹 스토어 - network-json-filter</a></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="사용법">사용법<a class="hash-link" aria-label="사용법에 대한 직접 링크" title="사용법에 대한 직접 링크" href="https://heowc.dev/2020/10/21/introduction-network-json-filter-as-chrome-extension#%EC%82%AC%EC%9A%A9%EB%B2%95"></a></h3>
<p><img decoding="async" loading="lazy" src="https://github.com/heowc/network-json-filter-chrome-extension/blob/main/docs/screenshot.png?raw=true" alt="img screenshot" class="img_ev3q">
※ 해당 스크린샷은 <code>https://httpbin.org</code>를 활용한 예시</p>
<ol>
<li>url: 단순히 url에 해당 문자열 포함여부 체크</li>
<li>expression: JSON Path 표현식을 활용한 응답 변환 (<a href="https://github.com/dchester/jsonpath" target="_blank" rel="noopener noreferrer">참고</a>)</li>
<li>응답 접기/펼기 (<code>▶/▼</code>)</li>
<li>응답 내역을 삭제</li>
</ol>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="추가될-기능-리스트">추가될 기능 리스트<a class="hash-link" aria-label="추가될 기능 리스트에 대한 직접 링크" title="추가될 기능 리스트에 대한 직접 링크" href="https://heowc.dev/2020/10/21/introduction-network-json-filter-as-chrome-extension#%EC%B6%94%EA%B0%80%EB%90%A0-%EA%B8%B0%EB%8A%A5-%EB%A6%AC%EC%8A%A4%ED%8A%B8"></a></h3>
<p><a href="https://github.com/heowc/network-json-filter-chrome-extension/issues" target="_blank" rel="noopener noreferrer">https://github.com/heowc/network-json-filter-chrome-extension/issues</a></p>]]></content:encoded>
<category>chrome-extension</category>
<category>json</category>
<category>network-tool</category>
</item>
<item>
<title><![CDATA[Spring - @Autowired는 어떻게 동작하는 걸까?]]></title>
<link>https://heowc.dev/2020/07/04/how-does-autowired-work</link>
<guid>https://heowc.dev/2020/07/04/how-does-autowired-work</guid>
<pubDate>Sat, 04 Jul 2020 18:00:00 GMT</pubDate>
<description><![CDATA[@Autowired의 동작 원리를 간단하게 이해해보자.]]></description>
<content:encoded><![CDATA[<blockquote>
<p><code>@Autowired</code>의 동작 원리를 간단하게 이해해보자.</p>
</blockquote>
<p>스프링에서 Bean으로 등록된 객체에 특정 Bean에 대한 의존성을 주입할 때, 스프링에서 제공(<code>@Autowired</code>, <code>@Value</code>)하는 혹은 자바 제공(<code>@Inject</code>, <code>@Resource</code>)하는 애노테이션들이 어떤 원리로 주입이 되는 것?에 대한 궁금증이 생겼다. 그것은 바로 <code>BeanPostProcessor</code> 이라는 클래스에 해답을 얻을 수 있다.</p>
<p><code>BeanPostProcessor</code>는 스프링 컨테이너 안에서 만든 bean에 전/후처리 작업을 할 수 있도록 만든 인터페이스이다. 여러 구현체들을 보면 <code>AutowiredAnnotationBeanPostProcessor</code>, <code>CommonAnnotationBeanPostProcessor</code> 등등 다양한 BeanPostProcessor들이 존재하며, 서드파티 라이브러리 중에서도 스프링 위에서 동작할 수 있도록 <code>BeanPostProcessor</code>를 추가로 제공하기도 한다. (<code>MeterRegistryBeanPostProcessor</code>, <code>ArmeriaBeanPostProcessor</code>, ...)</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">BeanPostProcessor</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 빈 생성 이전에 실행되는 메소드</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">postProcessBeforeInitialization</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">Object</span><span class="token plain"> bean</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> beanName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throws</span><span class="token plain"> </span><span class="token class-name">BeansException</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 빈 생성 이후에 실행되는 메소드</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">postProcessAfterInitialization</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">Object</span><span class="token plain"> bean</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> beanName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throws</span><span class="token plain"> </span><span class="token class-name">BeansException</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>인터페이스는 굉장히 깔끔하고 단순하다.</p>
<p>그렇다면 <code>BeanPostProcessor</code>의 생성시점은 언제일까? 코드를 들여다 보면 다음과 같다.</p>
<p><a href="https://github.com/spring-projects/spring-framework/blob/5.2.x/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L535" target="_blank" rel="noopener noreferrer">https://github.com/spring-projects/spring-framework/blob/5.2.x/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L535</a></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token annotation punctuation" style="color:#393A34">@Override</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">refresh</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throws</span><span class="token plain"> </span><span class="token class-name">BeansException</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token class-name">IllegalStateException</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">synchronized</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">startupShutdownMonitor</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Prepare this context for refreshing.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">prepareRefresh</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Tell the subclass to refresh the internal bean factory.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">ConfigurableListableBeanFactory</span><span class="token plain"> beanFactory </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">obtainFreshBeanFactory</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Prepare the bean factory for use in this context.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">prepareBeanFactory</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">beanFactory</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Allows post-processing of the bean factory in context subclasses.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">postProcessBeanFactory</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">beanFactory</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Invoke factory processors registered as beans in the context.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">invokeBeanFactoryPostProcessors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">beanFactory</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Register bean processors that intercept bean creation.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">registerBeanPostProcessors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">beanFactory</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>호출 시점만 놓고 보자면 Bean 작업 이전에 BeanPostProcessor에 대한 작업을 한 후에 Bean 작업이 되는 것을 볼 수 있다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="커스텀-beanpostprocessor-만들어보기">커스텀 BeanPostProcessor 만들어보기<a class="hash-link" aria-label="커스텀 BeanPostProcessor 만들어보기에 대한 직접 링크" title="커스텀 BeanPostProcessor 만들어보기에 대한 직접 링크" href="https://heowc.dev/2020/07/04/how-does-autowired-work#%EC%BB%A4%EC%8A%A4%ED%85%80-beanpostprocessor-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0"></a></h4>
<p>간단한 컴포넌트와 커스텀 BeanPostProcessor를 준비하자.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token annotation punctuation" style="color:#393A34">@Component</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">Person</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">String</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token annotation punctuation" style="color:#393A34">@Configuration</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token class-name">MyBeanPostProcessor</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">implements</span><span class="token plain"> </span><span class="token class-name">BeanPostProcessor</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token annotation punctuation" style="color:#393A34">@Override</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">postProcessBeforeInitialization</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">Object</span><span class="token plain"> bean</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> beanName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throws</span><span class="token plain"> </span><span class="token class-name">BeansException</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">bean </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Person</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token class-name">Person</span><span class="token plain"> heowc </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">Person</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> bean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> heowc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"heowc"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> bean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token annotation punctuation" style="color:#393A34">@Override</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">postProcessAfterInitialization</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">Object</span><span class="token plain"> bean</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> beanName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throws</span><span class="token plain"> </span><span class="token class-name">BeansException</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> bean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>간단하게 이름을 필드로 갖는 Person이라는 클래스를 만들었다. 그리고 이것이 Bean으로 등록이 된다면, Person 객체 name 필드에 'heowc'를 초기화해주는 코드다. 이를 테스트한 코드는 아래와 같다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token annotation punctuation" style="color:#393A34">@SpringBootTest</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MyBeanPostProcessorTest</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token annotation punctuation" style="color:#393A34">@Autowired</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token class-name">Person</span><span class="token plain"> heowc</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token annotation punctuation" style="color:#393A34">@Test</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">test_personInjection</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">assertThat</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">heowc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isEqualTo</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"heowc"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>]]></content:encoded>
<category>spring</category>
<category>BeanPostProcessor</category>
<category>autowired</category>
</item>
<item>
<title><![CDATA[AWS - SES 사용 후기]]></title>
<link>https://heowc.dev/2020/04/11/aws-ses-review</link>
<guid>https://heowc.dev/2020/04/11/aws-ses-review</guid>
<pubDate>Sat, 11 Apr 2020 17:30:00 GMT</pubDate>
<description><![CDATA[AWS SES 사용 후기이다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p>AWS SES 사용 후기이다.</p>
</blockquote>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="aws-ses란">AWS SES란?<a class="hash-link" aria-label="AWS SES란?에 대한 직접 링크" title="AWS SES란?에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#aws-ses%EB%9E%80"></a></h4>
<p><code>Amazon Simple Email Service(SES)</code>는 AWS에서 제공하는 클라우드 기반 이메일 발송 서비스이다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="사전-준비">사전 준비<a class="hash-link" aria-label="사전 준비에 대한 직접 링크" title="사전 준비에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EC%82%AC%EC%A0%84-%EC%A4%80%EB%B9%84"></a></h4>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="계정-신청">계정 신청<a class="hash-link" aria-label="계정 신청에 대한 직접 링크" title="계정 신청에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EA%B3%84%EC%A0%95-%EC%8B%A0%EC%B2%AD"></a></h5>
<p>인스턴스를 하나 할당받는 것처럼 SES를 사용하기 위한 신청을 해야 되는데, 정상적으로 신청이 되었다면 이 상태가 <strong>샌드박스</strong> 상태이다. 만약, 실서비스에서 사용하려면 <strong>샌드박스</strong> 상태를 벗어나야한다.</p>
<blockquote>
<p>샌드박스 상태는 일일(200건), 초당(1건) 발송 제한이 걸려있고, 등록된 이메일에만 메일 전송이 가능하다.</p>
</blockquote>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="문서">문서<a class="hash-link" aria-label="문서에 대한 직접 링크" title="문서에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EB%AC%B8%EC%84%9C"></a></h5>
<p>당연할 수 도 있겠지만, SES에 대한 가이드 문서를 꼭 읽어보고 진행하길 권한다. 이메일 도메인 지식이 없는 사람도 이메일 도메인 지식 습득할 수 있을 뿐만 아니라 SES 기능 숙지도할 수 있다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="사용방법">사용방법<a class="hash-link" aria-label="사용방법에 대한 직접 링크" title="사용방법에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95"></a></h4>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="전송-api">전송 API<a class="hash-link" aria-label="전송 API에 대한 직접 링크" title="전송 API에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EC%A0%84%EC%86%A1-api"></a></h5>
<p>SMTP 인터페이스 또는 AWS SDK, HTTP API형태로 제공한다.</p>
<ul>
<li><a href="https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/send-using-smtp-java.html" target="_blank" rel="noopener noreferrer">SMTP 인터페이스</a></li>
<li><a href="https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/send-using-sdk-java.html" target="_blank" rel="noopener noreferrer">AWS SDK</a></li>
</ul>
<p>※ AWS SDK를 사용한다면 이메일 템플릿을 저장해두고 사용하는 방법도 존재한다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="알림">알림<a class="hash-link" aria-label="알림에 대한 직접 링크" title="알림에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EC%95%8C%EB%A6%BC"></a></h5>
<p>메일은 수신거부, 반송 등의 알림을 받을 수 있다. 이를 받기 위한 수단(방식)으로는 <strong>AWS SNS</strong> 또는 <strong>별도의 email</strong>를 등록하여 받아볼 수 있다.</p>
<ul>
<li><a href="https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/monitor-sending-activity-using-notifications-email.html" target="_blank" rel="noopener noreferrer">이메일 알림</a></li>
<li><a href="https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/notification-contents.html" target="_blank" rel="noopener noreferrer">AWS SNS 알림</a></li>
</ul>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="시뮬레이터">시뮬레이터<a class="hash-link" aria-label="시뮬레이터에 대한 직접 링크" title="시뮬레이터에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0"></a></h5>
<p>알림에 대한 후처리기를 빠르게 테스트하기 위해 시뮬레이터를 제공한다. 특정 메일으로 메일을 전송하면 각각의 시나리오를 테스트해볼 수 있다. 예를 들면, <code>bounce@simulator.amazonses.com</code>에 메일을 전송하면 반송 알림을 받을 수 있다.</p>
<ul>
<li>참고: <a href="https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/send-email-simulator.html" target="_blank" rel="noopener noreferrer">https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/send-email-simulator.html</a></li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="주의-해야할-점">주의 해야할 점<a class="hash-link" aria-label="주의 해야할 점에 대한 직접 링크" title="주의 해야할 점에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EC%A3%BC%EC%9D%98-%ED%95%B4%EC%95%BC%ED%95%A0-%EC%A0%90"></a></h4>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="평판">평판<a class="hash-link" aria-label="평판에 대한 직접 링크" title="평판에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%ED%8F%89%ED%8C%90"></a></h5>
<p>평판이라는 개념이 존재하여, 평판 낮아지면 할당받은 일일/초당 발송 횟수가 낮아질 수 있고 나중에는 계정이 정지될 수 있다. 평판에 대한 관리가 지속적으로 되어야 하며, 이에 대한 대책을 강구해야 한다. 그렇다보니, <strong>알림에 대한 후처리는 필수</strong>라고 볼 수 있다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="반송율-수신거부율">반송율, 수신거부율<a class="hash-link" aria-label="반송율, 수신거부율에 대한 직접 링크" title="반송율, 수신거부율에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EB%B0%98%EC%86%A1%EC%9C%A8-%EC%88%98%EC%8B%A0%EA%B1%B0%EB%B6%80%EC%9C%A8"></a></h5>
<p>평판은 반송율과 수신거부율로 인해 정해지는데 권장사항으로는 <strong>반송율는 5%미만</strong>, <strong>수신거부율는 0.1%미만</strong> 을 유지할 수 있도록 해야 한다. 이를 포함해 <a href="https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/best-practices.html" target="_blank" rel="noopener noreferrer">모범 사례</a>는 문서를 참고하면 좋다.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="개발운영하면서-얻는-팁">개발,운영하면서 얻는 팁<a class="hash-link" aria-label="개발,운영하면서 얻는 팁에 대한 직접 링크" title="개발,운영하면서 얻는 팁에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EA%B0%9C%EB%B0%9C%EC%9A%B4%EC%98%81%ED%95%98%EB%A9%B4%EC%84%9C-%EC%96%BB%EB%8A%94-%ED%8C%81"></a></h4>
<ul>
<li>
<p>실서비스로 가기까지 생각보다 <strong>실제 반송/수선거부 처리를 유도하기가 어려웠다.</strong> (방법이 잘 못된 것일 수도 있겟지만) 스팸처리, 자동응답 등등을 설정해도 정상적으로 메일이 전송되는 것으로 처리되었다. 그래서 실서비스 반영까지 시뮬레이터 테스트와 제한된 이메일을 저장해둘 별도의 테이블을 만들어 필터링이 잘되는지 정도의 테스트만 진행했었다.</p>
</li>
<li>
<p>Google Gmail경우, 반송/수선거부 데이터를 제공하지 않는다.</p>
</li>
<li>
<p><strong>Yahoo 이메일에 대한 반송율이 꽤 높았다.</strong> (초반 데이터로만 봤을 때, 50%정도 되었다.) 확실하지는 않지만 <a href="https://blog.zerobounce.net/2019/03/28/yahoo-emails-bouncing-here-is-why-and-what-you-can-do/" target="_blank" rel="noopener noreferrer">2019년 3월쯤 관련 내용</a>을 볼 수 있는데, 장기 미사용 유저에 대해 휴먼계정으로 전환되어 나타난 현상으로 추측된다. 이로 인해, 각종 포럼이나 stackoverflow에도 여러 질의응답이 오가는 것을 볼 수 있다. 그래서 우리가 할 수 있는 처리는 다른 이메일을 등록하도록 유도하거나 유효한 이메일인지, 반송율이 높은 이메일이 알 수 있는 별도 API를 사용하여 반송율을 낮춰야 할 것이다.</p>
</li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="아쉬운-점">아쉬운 점<a class="hash-link" aria-label="아쉬운 점에 대한 직접 링크" title="아쉬운 점에 대한 직접 링크" href="https://heowc.dev/2020/04/11/aws-ses-review#%EC%95%84%EC%89%AC%EC%9A%B4-%EC%A0%90"></a></h4>
<ul>
<li><strong>평판이 계산되는 날짜 범위 기준이 명확하지 않다.</strong> <code>AWS 콘솔 > SES > 평판 대시보드</code>에서 반송율, 수신거부율을 볼 수 있는데, 기준 날짜도 다르고 언제 기준이 바뀌는지도 알 수가 없다.</li>
<li>매번 콘솔에 들어가서 보자니 귀차니즘이 생겨서 <strong>반송율, 수신거부율을 알기 위한 API를 찾아봤지만 없었다.</strong> 그나마 2주간에 발송건 수, 반송건 수, 수신거부건 수를 알수 있는 데이터가 있어서 이를 가공하여 주기적으로 슬랙으로 알림을 전송하고 있다.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Alt slack message" src="https://heowc.dev/assets/images/slack-7b475dafce21fb4e1a00255629ddf765.png" width="1146" height="220" class="img_ev3q"></p>]]></content:encoded>
<category>aws</category>
<category>ses</category>
<category>mail</category>
<category>review</category>
</item>
<item>
<title><![CDATA[Spring Boot 예제 개선하기 (2)]]></title>
<link>https://heowc.dev/2020/02/20/improve-spring-boot-example-2</link>
<guid>https://heowc.dev/2020/02/20/improve-spring-boot-example-2</guid>
<pubDate>Thu, 20 Feb 2020 23:30:00 GMT</pubDate>
<description><![CDATA[GitHub Action을 통해 실행된 테스트가 실패시, test reports를 업로드하는 기능을 소개하고자 합니다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p><code>GitHub Action</code>을 통해 실행된 테스트가 실패시, <code>test reports</code>를 업로드하는 기능을 소개하고자 합니다.</p>
</blockquote>
<p>이전 글에 이어 보다 개선된 지속적 통합을 하고자 2가지 개선이 있었다.</p>
<ul>
<li>캐시 적용</li>
<li>테스트 보고</li>
</ul>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="캐시-적용">캐시 적용<a class="hash-link" aria-label="캐시 적용에 대한 직접 링크" title="캐시 적용에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#%EC%BA%90%EC%8B%9C-%EC%A0%81%EC%9A%A9"></a></h4>
<p><code>GitHub Action</code>은 특정 경로에 대해서 캐싱할 수 있는 action을 제공하고 있다. 이에 대한 자세한 스펙과 설명은 <a href="https://github.com/actions/cache" target="_blank" rel="noopener noreferrer">actions/cache</a>를 참고해볼 수 있다.</p>
<p><strong>heowc/SpringBootSample</strong>에 적용할 수 있는 포인트는 두 곳이다. (적용된 코드: <a href="https://github.com/heowc/SpringBootSample/commit/7064ed1381f3dfc7f184cf3d24985828c38f5f9d" target="_blank" rel="noopener noreferrer"><code>7064ed1</code></a>)</p>
<ol>
<li><code>gradle-wrapper</code>가 설치된 부분</li>
<li>각 프로젝트의 <code>build.gradle</code>에 정의된 <code>dependency</code>이 설치된 부분</li>
</ol>
<blockquote>
<p>꼭 Gradle이 아니더라도 다른 언어와 빌드 툴에 대한 샘플도 나열되어 있으니 <a href="https://github.com/actions/cache#implementation-examples" target="_blank" rel="noopener noreferrer">참고</a>하면 좋다.</p>
</blockquote>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="1-gradle-wrapper-설치하는-부분">1. <code>gradle-wrapper</code> 설치하는 부분<a class="hash-link" aria-label="1-gradle-wrapper-설치하는-부분에 대한 직접 링크" title="1-gradle-wrapper-설치하는-부분에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#1-gradle-wrapper-%EC%84%A4%EC%B9%98%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84"></a></h5>
<p><code>gradle-wrapper</code>를 사용하는 경우에는 <code>gradle/wrapper/gradle-wrapper.properties</code> 파일을 참고하면 현재 사용 중인 버전을 알 수 있다. 결국에는 해당 파일이 변경되었다면 버전 변경이 되었다고 판단할 수 있으므로 이를 기준으로 cache-key를 가질 수 있다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="2-buildgradle-정의된-dependency">2. <code>build.gradle</code> 정의된 <code>dependency</code><a class="hash-link" aria-label="2-buildgradle-정의된-dependency에 대한 직접 링크" title="2-buildgradle-정의된-dependency에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#2-buildgradle-%EC%A0%95%EC%9D%98%EB%90%9C-dependency"></a></h5>
<p>이미 <a href="https://github.com/actions/cache#implementation-examples" target="_blank" rel="noopener noreferrer">action/cache의 예시</a>를 참고했다면 쉽게 알 수 있듯이, 이 또한 <code>build.gradle</code>를 기준으로 cache-key를 가질 수 있다.</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="그래서-빨라졌나">그래서 빨라졌나?<a class="hash-link" aria-label="그래서 빨라졌나?에 대한 직접 링크" title="그래서 빨라졌나?에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#%EA%B7%B8%EB%9E%98%EC%84%9C-%EB%B9%A8%EB%9D%BC%EC%A1%8C%EB%82%98"></a></h5>
<p>아직 히스토리로 남은 전체 워크플로우 갯수도 적고 테스트 갯수도 적어 (또한, 대부분의 워크플로우가 디펜던시 변경이기 때문에) 단정지을 순 없지만 초기에는 7분대가 보였던 것에 반해 cache를 적용한 이후에는 7분대는 거의 볼 수 없었다. <strong>대략 10퍼센트 정도의 속도 개선이 있었다는 것을 알 수 있다.</strong></p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="테스트-보고">테스트 보고<a class="hash-link" aria-label="테스트 보고에 대한 직접 링크" title="테스트 보고에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B3%B4%EA%B3%A0"></a></h4>
<p>워크플로우를 보면 대략적으로 실패했다는 것을 알 수 있지만 자세히는 알 수 없다. 그래서 테스트 결과물로 나온 <code>reports</code>를 어디선가 볼 수 없을까? 라는 생각과 내 주변 프로젝트는 어찌하고 있을까? 싶어 찾아보았다.</p>
<p><img decoding="async" loading="lazy" alt="Alt local-test" src="https://heowc.dev/assets/images/local-test-809917cf4184097c71a61e32990bf386.png" width="974" height="1302" class="img_ev3q"></p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="1-armeria">1. <a href="https://github.com/line/armeria" target="_blank" rel="noopener noreferrer">Armeria</a><a class="hash-link" aria-label="1-armeria에 대한 직접 링크" title="1-armeria에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#1-armeria"></a></h5>
<p>요즘 라인에서 만든 <code>Armeria</code>에 관심이 많아 조금씩 기여도 하고 유용한 코드 조각이 있으면 활용하는 편이다. <code>Armeria</code>는 appveyor라는 CI 도구와 codecov라는 코드 커버리지 도구를 사용하는데, appveyor에서 테스트가 깨지면 <code>file.io</code> 서비스를 이용해 하나의 test-reports로 압축하여 업로드하고 있다.</p>
<blockquote>
<p><a href="https://file.io/" target="_blank" rel="noopener noreferrer"><code>file.io</code></a>는 무료이고 유효기간을 지정할 수 있어 굉장히 유용한 도구이다.</p>
</blockquote>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="2-actionsupload-artifact">2. <a href="https://github.com/actions/upload-artifact" target="_blank" rel="noopener noreferrer">actions/upload-artifact</a><a class="hash-link" aria-label="2-actionsupload-artifact에 대한 직접 링크" title="2-actionsupload-artifact에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#2-actionsupload-artifact"></a></h5>
<p>최근 베타 기능으로 GitHub에서 artifact API라는 파일 업로드 기능이 추가되었고, 이 또한 action으로 활용할 수 있었다. 하지만 아쉽게도 몇 가지 문제점이 있다.</p>
<ol>
<li>유효기간을 지정할 수 없어 사용량에 제한이 될 수 있다. 수동으로 제거해야한다.</li>
<li>여러 경로에 대해서 한번에 묶을 수 없다.</li>
</ol>
<p><a href="https://github.com/actions/upload-artifact/pull/54" target="_blank" rel="noopener noreferrer">V2 버전</a>에는 2번이 해결될 것으로 보이지만 아직까진 유효기간 설정에 대한 기능이 이슈로만 얘기되고 있어 <code>file.io</code>를 사용하고 나중에 <code>actions/upload-artifact</code>를 변경하기로 마음 먹었다. (적용된 코드: <a href="https://github.com/heowc/SpringBootSample/commit/3d436751e89368bee2ebf71e0a8ba009e622b5b4" target="_blank" rel="noopener noreferrer"><code>3d43675</code></a>)</p>
<h5 class="anchor anchorWithStickyNavbar_LWe7" id="실패하는-테스트-코드-만들어-실행하기">실패하는 테스트 코드 만들어 실행하기<a class="hash-link" aria-label="실패하는 테스트 코드 만들어 실행하기에 대한 직접 링크" title="실패하는 테스트 코드 만들어 실행하기에 대한 직접 링크" href="https://heowc.dev/2020/02/20/improve-spring-boot-example-2#%EC%8B%A4%ED%8C%A8%ED%95%98%EB%8A%94-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EB%A7%8C%EB%93%A4%EC%96%B4-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0"></a></h5>
<p>무조건 실패하는 테스트를 만들어보고 workflow를 확인해 보았다.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token annotation punctuation" style="color:#393A34">@Test</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">test_failure</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">assertThat</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isFalse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복��사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><img decoding="async" loading="lazy" alt="alt workflow-log" src="https://heowc.dev/assets/images/workflow-log-c1ecc150913f1dfdba701f8c113b2610.png" width="1984" height="1252" class="img_ev3q"></p>
<p>실패시, <code>https://file.io/m5KLSY</code>에 <code>test-report</code>가 업로드되고 이를 14일간 다운로드 받을 수 있다.</p>
<p><img decoding="async" loading="lazy" alt="alt download-report" src="https://heowc.dev/assets/images/download-report-5c3b820cda6eca07b2aa7c6a2a0b0902.png" width="1462" height="1014" class="img_ev3q"></p>]]></content:encoded>
<category>ci</category>
<category>github-action</category>
<category>test-reports</category>
<category>file.io</category>
</item>
<item>
<title><![CDATA[Spring Boot 예제 개선하기]]></title>
<link>https://heowc.dev/2020/01/24/improve-spring-boot-example</link>
<guid>https://heowc.dev/2020/01/24/improve-spring-boot-example</guid>
<pubDate>Fri, 24 Jan 2020 23:30:00 GMT</pubDate>
<description><![CDATA[Dependabot를 적용하고 GitHub Action을 활용하여 CI까지 적용해 예제 코드를 개선한 내용을 소개하고자 합니다.]]></description>
<content:encoded><![CDATA[<blockquote>
<p><code>Dependabot</code>를 적용하고 <code>GitHub Action</code>을 활용하여 CI까지 적용해 예제 코드를 개선한 내용을 소개하고자 합니다.</p>
</blockquote>
<p><a href="https://spring.io/projects/spring-boot" target="_blank" rel="noopener noreferrer">spring-boot</a>를 처음 접하고 이에 대한 <a href="https://github.com/heowc/SpringBootSample" target="_blank" rel="noopener noreferrer">간단한 예제</a>를 만든지 꽤 시간이 지났다. 2020년 1월 기준으로는 약 30여개의 예제와 400여개의 커밋으로 이루어진 예제 저장소가 되었는데 처음에는 나만을 위해 나만이 참고하기 위한 공개 저장소 목적으로 만들었다.</p>
<p>하지만 해를 거듭하면서 다른 개발자분들이 간접적으로 관심을 가져주시는 표현으로 가끔씩 스타도 눌러주시고 포크도 해가신다. 공개 저장소이니 간단한 예제지만 쓰시는 분들에게 제대로된 예제 코드를 노출시키고 싶고 관리가 잘 되고 있다는 느낌을 들게하고 싶었다. (현재로선 안돌아가는 예제도 있다.)</p>
<p><img decoding="async" loading="lazy" alt="alt intro" src="https://heowc.dev/assets/images/intro-ab96917d06fabe004c1a636465a55067.png" width="2198" height="372" class="img_ev3q"></p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="주기적으로-version-갱신을-하자">주기적으로 Version 갱신을 하자<a class="hash-link" aria-label="주기적으로 Version 갱신을 하자에 대한 직접 링크" title="주기적으로 Version 갱신을 하자에 대한 직접 링크" href="https://heowc.dev/2020/01/24/improve-spring-boot-example#%EC%A3%BC%EA%B8%B0%EC%A0%81%EC%9C%BC%EB%A1%9C-version-%EA%B0%B1%EC%8B%A0%EC%9D%84-%ED%95%98%EC%9E%90"></a></h4>
<p>우선적으로 디펜던시를 최신 버전으로 올리고 싶었다. 처음에는 가끔씩 크게 버전업을 하고 이에 맞게 코드도 수정했으나, 하나하나 알기도 어렵고 귀찮았다.</p>
<p><strong>그러다가 <code>Dependabot</code>라는 녀석을 알게되었다.</strong> 지정한 주기로 디펜던시를 찾아 추가 버전 릴리즈가 있는지 찾아 pull request를 날려주는 봇이다.</p>
<p><img decoding="async" loading="lazy" alt="alt dependabot-intro" src="https://heowc.dev/assets/images/dependabot-intro-2bc135c9254475f3481fb8b3c1764649.png" width="2116" height="666" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="alt dependabot-setting" src="https://heowc.dev/assets/images/dependabot-setting-49dcd20b956108f69bcdd80c416ad0fc.png" width="2140" height="852" class="img_ev3q"></p>
<p>해당 방법은 preview 단계의 설정이므로, <a href="https://docs.github.com/en/github/administering-a-repository/about-github-dependabot" target="_blank" rel="noopener noreferrer">다음 문서</a>를 참고하여 <code>.github/dependabot.yml</code>를 작성해보도록 하자.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="version-up에-안정적인-예제코드를-만들자">Version UP에 안정적인 예제코드를 만들자<a class="hash-link" aria-label="Version UP에 안정적인 예제코드를 만들자에 대한 직접 링크" title="Version UP에 안정적인 예제코드를 만들자에 대한 직접 링크" href="https://heowc.dev/2020/01/24/improve-spring-boot-example#version-up%EC%97%90-%EC%95%88%EC%A0%95%EC%A0%81%EC%9D%B8-%EC%98%88%EC%A0%9C%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%9E%90"></a></h4>
<p>두번째로는 이렇게 머지되는 코드들 혹은 내가 직접 수정하거나 추가 작성한 코드가 정말 문제가 없는지 알고 싶었다. (보통 지속적 통합이라고 하죠.) 최근까지는 <code>Dependabot</code>을 활용해서 손쉽게 많은 디펜던시들을 주기적으로 갱신되거나 (개인적인 판단하에) 굳이 갱신될 필요가 없다고 판단되면 닫고 있었다.</p>
<p><img decoding="async" loading="lazy" alt="alt dependabot-pr" src="https://heowc.dev/assets/images/dependabot-pr-6246de71cf4999d3504ee6879a31660d.png" width="2198" height="1622" class="img_ev3q"></p>
<p>그런데 이 많은 디펜던시들이 갱신되면서 '예제가 정상적으로 빌드가 될까' 라는 의문이 생겼고...</p>
<p>...</p>
<p>역시나 예제가 정상적으로 안돌아갔다. 이런 코드는 <strong>Revert를 하거나 예제를 수정하기도 했다.</strong></p>
<p><img decoding="async" loading="lazy" alt="alt revert" src="https://heowc.dev/assets/images/revert-be46dc062d88e088079677953dc4f284.png" width="2198" height="1548" class="img_ev3q"></p>
<p>그리하여 쓸만한 CI 도구를 찾다가 약 1년 전에 작성한 <a href="https://heowc.dev/2019/02/03/deploy-gh-page-with-github-action/">'GitHub Action을 활용한 GitHub Page 배포'</a>이 생각이나서 <code>github-action</code> 을 적용해보고자 한다. 사실 이를 적용할 생각은 예전부터 있었으나 1년 전에는 베타였고 자료도 많이 없어 꽤나 불편함이 조금 있었다.</p>
<blockquote>
<p>다시 찾아보니 <a href="https://github.com/marketplace?type=actions" target="_blank" rel="noopener noreferrer">마켓플레이스</a>에 편리한 많은 액션들이 추가되었다. (거의 2천개정도 되더란다.)</p>
</blockquote>
<p>디테일한 작업은 추후작업으로 미루고 우선 <strong>push와 pull request 이벤트를 트리거하여 test를 실행</strong>하는 것이다. <a href="https://github.com/heowc/SpringBootSample/blob/master/.github/workflows/test.yml" target="_blank" rel="noopener noreferrer">코드</a>는 다음과 같다.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Test</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">push</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">pull_request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">types</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">opened</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> reopened</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">jobs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">test</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">runs-on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">18.04</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">steps</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> actions/checkout@v2</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Set up JDK 1.8</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> actions/setup</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">java@v1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">with</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">java-version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.8</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Test task with Gradle Wrapper</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 제일 중요한 부분 </span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> chmod +x gradlew</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> ./gradlew --version</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> ./gradlew test</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="최종-결과">최종 결과<a class="hash-link" aria-label="최종 결과에 대한 직접 링크" title="최종 결과에 대한 직접 링크" href="https://heowc.dev/2020/01/24/improve-spring-boot-example#%EC%B5%9C%EC%A2%85-%EA%B2%B0%EA%B3%BC"></a></h4>
<p><code>Dependabot</code>에는 <code>Bump now</code>이라는 버튼 있어, 이를 클릭하면 주기에 상관없이 즉시 디펜던시를 찾아 pull request를 날려준다. 그러면 반영된 디펜던시로 test가 돌아 코드에 문제가 없는지 확인할 수 있다. 이로써 <strong>목표로 했던 안정적인 예제 만들기에 한 걸음 나아갔다.</strong> (아직 개선할게 많다...)</p>
<blockquote>
<p><code>Dependabot</code>, <code>github-action</code>은 완전 무료이기 때문에 간단한 저장소에 부담없이 사용하기 굉장히 좋은 기능인 것 같다.</p>