주요글: 도커 시작하기
반응형

회사에서 흥미로운 기술을 도입하고 싶은데 반대에 부딪힌 적 있는가? 누가 봐도 좋은 의견을 냈는데 수용은커녕 무시만 당한 적이 있는가? 분명 도움이 되고 하고 싶은 일이기도 한데 왜 못 하게 하는지 이해가 안 되고 답답한가?

먼저 할 일은 인정받기

종종 이런 고충을 토로하는 개발자를 접한다. 내 맘 같이 안 움직이니 답답할 것이다. 기존 방식보다 좋은 걸 말해도 안 움직이고 하고 싶은 것도 못 하게 하니 말이다. 하지만 먼저 해야 할 게 있다. 바로 동료, 상급자, 팀원에게 인정받는 것이다. 신뢰를 쌓는데 시간이 필요하듯 인정에도 시간이 걸린다. 전에 다른 곳에서 어땠는지 여부는 중요하지 않다. 지금 있는 곳에서 인정을 받아야 원하는 것을 시작하기 쉽다.

경험상 최소 1달은 지나야 인정하는 동료가 나타나기 시작했다. 보통은 2~3 개월은 같이 일해야 팀 내에서 인정받기 시작했고 오래 걸리는 동료는 6개월을 넘기기도 했다. 일단 한 두 명한테 인정을 받고 나면 내 주장에 조금씩 힘이 실리면서 원하는 것을 진행할 수 있었다.

날 인정하는 것은 내가 아닌 상대가 하는 것

나를 인정하는 것은 내가 아니다. 상대가 하는 거다. 그래서 상대가 인정할만한 결과를 먼저 보여주는 게 중요하다. 재미없거나 다소 하기 싫은 일이어도 새로 들어간 조직에서 원하는 결과를 내는 게 먼저다. 이런 결과가 쌓여야 다수로부터 인정받을 수 있고 이런 지지를 바탕으로 내가 원하는 것을 동료와 함께 할 수 있게 된다.

지지를 얻지 못한 상태에서 급한 마음에 본인이 하고 싶은 일을 혼자서 밀어 부치면 결과는 낼 수 있어도 인정받기는 쉽지 않다. 혼자 진행하다 실수라도 하면 인정은커녕 불신과 갈등만 키운다.

고려할 것

인정을 받았고 이제 원하는 것을 할 수 있는 상황이 되었다. 그러면 이제 남은 건 하는 것뿐인가? 그렇지 않다. 조직의 상태를 고려해야 한다. 예를 들어 모두 자바만 익숙한데 파일 파싱을 고속으로 하겠다고 혼자 러스트로 개발하면 안 된다. 이건 내 이력서에 한 줄 적자고 조직에 위험을 떠 넘기는 것과 같다. 가능하면 같이 할 동료를 만들자. 혼자 해야 한다면 동료를 고려한다.

맺음말

입사한지 얼마 안 된 회사에서 환영 회식을 했는데 그중 한 명이 인사말로 한 말이 잊히지 않는다.

개발을 잘하는 지 아직은 모르겠고...

다들 깔깔거리며 웃었지만 듣기 좋지 만은 않았다. 그래도 이게 다수의 본심 아니었을까? 같이 일 해 본 적이 없는데 실력을 어떻게 알겠는가?

  1. 본인은 월급루팡이었어요ㅠㅠㅠ 2021.09.20 10:18

    본인은 개발 하나도 할 줄 모르면서 못 믿겠다고 말하는 사람도 많아요. 못 믿겠다고 하면 일단은 그 사람이 날카롭고 예리해 보이니까요.... 노드 서버 띄울 줄도 모르면서 입코딩하는 사람들도 잇긴 있습니다. 그 사람들에게 인정받으려면 그 사람들이 시키는 걸 잘해줘야 했어요 ㅠㅠ

    • 최범균 madvirus 2021.09.23 08:37 신고

      처음 보는 사람을 못 믿는 건 어찌 보면 당연하니 증명하는 수밖에 없는데, 그 사람 입장에서 인정할 만한 걸로 증명해야 한다는 게 어려운 일 같아요.

반응형

자바 9 - 16의 주요 특징을 정리했다. 자세한 내용은 https://youtu.be/7SlDdzVk6GE 영상을 참고하기 바란다.

 

자바 9-16 주요 특징 복습하기(코딩 위주)

자바 17 버전이 곧 출시됩니다. 그 전에 자바 9-16의 주요 특징 복습하고 가시죠!

youtu.be

자바 9

  • 인터페이스에 private 메서드 추가 가능
  • try-with-resources : 자원에 실질적인 final 변수 사용 가능
  • 콜렉션 of 팩토리 메서드 : List.of(1, 2, 3)
  • Arrays 클래스: compare 메서드, mismatch 메서드

자바 10

  • 로컬 변수 타입 추론 : var a = 10;

자바 11

  • String 클래스 : isBlank, lines, repeat, strip / stripLeading / stripTrailing 메서드 추가
  • Files 클래스 writeString() 메서드와 readString() 메서드 : 문자열을 간단하게 파일 입출력 가능

자바 12

  • Streing 클래스 : indent 메서드, transform 메서드

자바 14

  • switch 식

자바 15

  • 텍스트 블록
  • String 클래스 : formatted 메서드
  • 개선된 NPE 에러 메시지

자바 16

  • Stream : toList(), mapMulti()
  • instanceof와 패턴 매칭
  • record 클래스 

 

반응형

초보자가 저지르기 쉬운 DB 코딩 실수 3가지

  • 프라이머리/리플리카
  • @Transactional과 try-catchthis
  • this @Transactional 메서드 호출 관련

https://youtu.be/n85UzIReFjY

 

반응형

G1 GC를 검증하기 위해 실환경에서 비교한 결과를 공유합니다.

한줄 요약: 효과 있네!

https://youtu.be/H4yuuYXK8_o

 

Full GC를 줄이는데 효과가 있었다

 

반응형

1년 전 작년 이맘때 새 시스템에 대한 개발을 시작했다. 새 시스템과 기존 시스템 간 데이터 연동이 필요했는데 몇 가지 이유(선택한 이유는 https://youtu.be/guVFK09yjXw 영상 참고)로 CDC(Change Data Capture)를 연동 기술로 선택했다.

MariaDB에 대한 CDC 모듈을 조사했는데 검색 결과로 많이 나온 게 Debezium이었다. 흥미롭긴 했는데 원하는 것을 하기에는 알맞지 않았다. 필요한 것은 다음과 같았다.

  • 여러 테이블이 변경되어도 한 개 이벤트 발생
    • 예를 들어 한 트랜잭션에서 A 주문의 주문 개요, 주문 상세, 계약 상세 등의 테이블이 변경되었을 때 각 테이블의 변경 내역을 이벤트로 만들지 않고 A 주문의 요약 정보를 한 개 이벤트로 생성해서 기존 시스템에 전달 필요
  • 한 모델의 변경 시 영향받는 다른 모델 데이터도 전파
    • 예를 들어 B회원의 결제 수단 정보가 변경되면 B회원과 관련된 모든 주문의 요약 정보를 이벤트로 생성해서 기존 시스템에 전달(주문 요약 정보에 결제 수단 정보가 포함되어 있어 전파 필요)
  • 변경 데이터를 칼럼명으로 접근하고 싶음
    • 변경 데이터를 칼럼 인덱스가 아닌 칼럼명으로 접근하고 싶음

이런 걸 하기에 Debezium은 적합하지 않아 요구에 맞는 CDC 모듈을 직접 만들기로 했다.

CDC 모듈 개발

다행히 MariaDB의 바이너리 로그를 읽을 수 있는 mysql-binlog-connector-java가 있어 이걸 활용해서 모듈을 만들었다(MySQL과 MariaDB가 프로토콜이 같아서 다행이었다). 다음 그림은 구현하는 과정에서 도출된 설계 결과물이다.

MariadbCdc에는 CDC 시작/중지 처리와 변경 데이터를 MariadbCdcListener에 전달하는 기능을 넣었다. 변경 데이터의 칼럼명 조회를 위해 ColumnNameGetter 인터페이스를 추가했고, (위 그림엔 표시하지 않았지만) 테이블 스키마 변경 시 칼럼명을 다시 조회할 수 있도록 스키마 변경 감지 기능을 넣었다.

MariadbCdcListener는 onDataChanged() 메서드로 변경 데이터를 RowChangedData에 담아서 전달받도록 했다. RowChangedData 클래스는 변경 데이터와 관련된 변경 타입, 변경된 테이블 이름, 변경 데이터, (UPDATE면) 변경 전 데이터를 갖도록 했다. DataRow는 데이터를 표현하며 칼럼명을 이용한 조회 기능과 칼럼 인덱스를 이용한 조회 기능을 제공하도록 구현했다.

한 트랜잭션에서 변경된 데이터를 모아서 한 번에 처리할 수 있도록 하기 위해 onXid() 메서드도 추가했다. 트랜잭션 커밋 로그를 읽을 때 이 메서드를 호출하도록 했다.

CDC 모듈 초기 버전을 만들고 https://github.com/madvirus/mariadb-cdc 리포지토리에 올리고 메이븐 의존 설정에 쉽게 추가할 수 있도록 jitpack.io를 사용했다.

개밥 먹기 1년

개발 환경에 적용한 뒤 1년이 지났다. 그 사이에 새 시스템을 오픈해서 운영 환경에도 적용했다. 이 과정에서 몇 가지 놓친 점을 발견했고 점진적으로 보완해 나갔다. 보완한 것 중에서는 다음이 기억에 남는다.

  • 동시에 두 프로세스를 실행하면 서로 연결이 끊기는 증상: 같은 슬레이브 ID를 사용하면서 발생한 것으로 슬레이브ID를 지정할 수 있는 기능 추가하고 각 CDC 모듈이 겹치지 않는 슬레이브ID를 사용하도록 처리
  • 한 테이블에서 많은 행이 변경될 때 감지가 누락되는 증상: 연속된 변경 ROWS 이벤트 발생 시 두 번째 이벤트부터 테이블 이름 처리가 잘못되어 발생한 것으로 테이블 이름 매핑 처리 버그 수정
  • 칼럼 이름을 제대로 가져오지 못하는 버그(다른 테이블에 참조키를 제공하는 테이블 변경 시 발생): TABLE_MAP 이벤트가 연속될 때 발생한 버그 수정

이 중 두 번째는 운영 환경에서 알게 되었다. 관련 데이터가 일정 개수 이상으로 증가하면서 발생하기 시작했다. 처음에는 원인을 몰라 주기적으로 손으로 보정하는 짓을 좀 했다.

세 번째는 최근에 다른 DB에 CDC를 적용하면서 발견했다. 문제를 해결하는 과정에서 리플리케이션 프로토콜에 대해 조금 더 알 수 있었다.

두 번째 개밥 먹기 중

보완을 하면서 동시에 실험 목적의 바이너리 로그 리더 구현도 병행했다. mysql-binlog-connector-java가 충분히 좋았지만 리플리케이션 프로토콜에 대한 호기심이 발동해 바이너리 로그를 직접 구현해 보고 싶은 마음이 생겼기 때문이다. 주로 주중 저녁이나 주말에 구현했는데 토요일과 일요일에 이틀 동안 꼬박 이것만 했던 날도 있었다.

MariaDB/MySQL 사이트에서 제공하는 프로토콜 문서를 주로 참조했고 프로토콜 문서만으로 감이 안 올 때는 mysql-binlog-connector-java 코드를 뒤져가면서 점진적으로 구현했고 최근에 필요한 최소 기능 개발을 완료했다. 모듈을 설계할 때 mysql-binlog-connector-java의 타입 노출을 최소화하고 MariadbCdc, MariadbCdcListener, DataRow 등으로 한 번 감싸서 구현했기에 실험용 버전을 사용하도록 변경하는 것도 간단한 설정으로 진행할 수 있었다.

마침 CDC를 다른 곳에 적용해야 할 일이 생겨 새로 구현한 바이너리 로그 리더를 사용해보고 있다. 이 과정에서 또 다른 보완점을 찾을 수 있었는데 그건 바로 enum 타입에 대한 처리였다. enum 타입 처리를 위한 코드를 보완하고 있는데 MySQL과 MariaDB에서는 enum 타입을 최대한 쓰면 안 되겠다는 생각을 갖게 되었다. 내가 결정에 영향을 줄 수 있는 한 최대한 못 쓰게 할 거다!

다음 목표는 직접 구현한 바이너리 로그 리더를 별도 모듈로 분리하고 SSL이나 GTID 등에 대한 지원을 추가하는 것이다. 생각나면 하고 틈나면 하고 그런지라 언제 될지 모르지만 언젠가는 다음 목표를 달성하고 싶다.

반응형

https://youtu.be/bBFi48Azvks

마리아DB 10.3, 10.4, 10.5에 있는 파티션 테이블과 관련된 치명적인 버그를 살펴봅니다.

반응형

 

https://youtu.be/xqqjCTt_l3E

 

반응형

동영상 주소: https://youtu.be/ZNDDy77WInY

 

반응형

https://youtu.be/fnH_SR3n9Ew

 

반응형

youtu.be/gd9aeUywGcM

 

반응형

프로그래밍 왕초식: if 조건 역으로 바꾸기

youtu.be/z4qE_IfSrD4

 

반응형

CodeMetrics 플러그인 소개: 복잡도 점수로 자극 받기

youtu.be/8NtKoANOezI

 

반응형

어제 이어 오늘도 사소하게 코드를 정리했다. 정리하기 전에 코드 형태는 다음과 같다.

if(obj.getData().equals("01") || obj.getData().equals("02") || obj.getData().equals("03")) {
    ....
}

문자열이 여러 값 중 하나인지 비교하는 코드이다. 이런 형태의 코드가 곳곳에 있어 어떻게 변경할까 고민하다가 SQL의 in 구문이 떠올라 다음과 같이 변경했다.

if ( Cond.str(obj.getData()).in("01", "02", "03") ) {
    ...
}

코틀린이었다면 확장 함수를 사용해서 더 간결하게 표현할 수 있었을텐데 하는 아쉬움이 살짝 들었지만 컴파일 속도를 생각하면 이것도 괜찮다.

다음처럼 여러 값이 모두 아닌지 비교하는 코드도 빈번하게 출현한다.

if( !obj.getData().contentEquals("01") && !obj.getData().contentEquals("02") ) {
    ....
}

이를 위해 notIn() 메서드도 추가했다.

if ( Cond.str(obj.getData()).notIn("01", "02") ) {
    ...
}

다음은 Str 클래스의 구현 코드다.

public class Cond {
    public static StrCond str(String s) {
        return new StrCond(s);
    }

    public static class StrCond {
        private String value;

        public StrCond(String value) {
            this.value = value;
        }

        public boolean in(String ... values) {
            for (String v : values) {
                if (v.equals(value)) return true;
            }
            return false;
        }

        public boolean notIn(String ... values) {
            for (String v : values) {
                if (v.equals(value)) return false;
            }
            return true;
        }
    }
}

 

  1. 김광수 2021.04.13 11:30

    구현코드를 변경해 보왔습니다. 그런데 Cond<T> 가 별로 의미 없어 보이긴 합니다 😅
    <code>
    public class CondTest {
    @Test
    void testStr() {
    assertThat(new Cond.Str("a").in("a", "b", "c")).isTrue();
    assertThat(new Cond.Str("d").in("a", "b", "c")).isFalse();
    assertThat(new Cond.Str("d").notIn("a", "b", "c")).isTrue();
    assertThat(new Cond.Str("a").notIn("a", "b", "c")).isFalse();
    }

    interface Cond<T> {
    boolean in(T... values);

    default boolean notIn(T... value) {
    return !in(value);
    }

    class Str implements Cond<String> {
    private final String value;

    public Str(String value) {
    this.value = value;
    }

    @Override
    public boolean in(String... values) {
    for (String v : values) {
    if (v.equals(value))
    return true;
    }
    return false;
    }
    }
    class Int implements Cond<Integer> {
    ...
    }
    }
    }
    </code>

반응형

곧 전달 받을 코드를 이리 저리 훑어 보다가 아래 형태 코드가 눈에 띄었다.

// someData.getIdList()는 String 타입으로 "id1|id2|id3"과 같은 형식
List<String> idList = Arrays.stream(someData.getIdList().split("[|]"))
        .collect(Collectors.toList());

split("[|]") 문자열로 검색해 보니 7 군데에서 완전 똑같은 형태의 코드를 사용하고 있다. 중복이 3번 이상 나고 있어서 이를 위한 보조 클래스 Splits을 만들었다.

public class Splits {
    public static List<String> splitByVbar(String str) {
        return Arrays.asList(str.split("[|]"));
    }
}

그리고 검색한 7 곳의 코드를 다음과 같이 바꿨다.

List<String> idList = Splits.splitByVbar(someData.getIdList().split("[|]"));

테스트 코드가 없어서 Splits.splitByVbar()를 사용하는 코드는 테스트를 할 수 없었다. 대신 기존의 분리 코드와 새 분리 코드가 같은 결과를 내는지 확인하는 테스트 코드를 작성했다.

@Test
void same() {
    assertThat(Splits.splitByVbar("1|2|3")).isEqualTo(oldSplitCode("1|2|3"));
    assertThat(Splits.splitByVbar("1|2|3|")).isEqualTo(oldSplitCode("1|2|3|"));
    assertThat(Splits.splitByVbar("|1|2|3|")).isEqualTo(oldSplitCode("|1|2|3"));
    assertThat(Splits.splitByVbar("1|2||3")).isEqualTo(oldSplitCode("1|2||3"));
}

private List<String> oldSplitCode(String str) {
    return Arrays.stream(str.split("[|]")).collect(Collectors.toList());
}

통과 됨을 확인하고 코드를 푸시했다.

반응형

아키텍트에게 기대하는 8가지 핵심 역량 영상: youtu.be/FbWHw6GBCrU

 

요약

  • 아키텍처 결정
  • 지속적인 아키텍처 분석
  • 최신 트렌드 유지
  • 결정 사항 준수 확인
  • 다양한 경험
  • 도메인 지식
  • 대인 관계 기술
  • 정치

 

+ Recent posts