반응형

스터디를 시작한 이유


스터디를 시작했었던 가장 큰 이유는 CS 공부를 하는 데 있어서 어느 정도의 강제성이 필요하다고 스스로 느꼈고, 마침 활동하고 있는 디프만에서 인프런 강의를 이용한 "인프런 스터디"를 모집하고 있어서 바로 관심 버튼 꾹 눌렀습니다.

 

다른 재미있는 강의들도 많았는데, 그중에서 지금 당장 저에게 유용하고 공부를 해도 해도 항상 부족하다고 느껴졌던 CS 스터디였습니다.

CS 강의는 인프런에 있는 개발남노씨님의 "기출로 대비하는 개발자 전공면접 [CS 완전정복]" 였습니다.

 

커리큘럼을 봤을 때, 자료구조, 운영체제, 데이터베이스, 네트워크의 핵심적인 부분들을 깔끔한 예시 영상을 통해서 정리해 주시는 거 같았습니다. 12월 31일까지 계획된 스터디에서 1주에 1개의 과목씩 해결한다고 생각했을 때는 가장 적당했던 강의라고 생각됩니다.

 

쿠폰이 50%까지 할인이 되었는데, 할인받아서 강의를 구매했습니다.

 

 

 

스터디 방식은?


스터디원은 총 7명이 모였고, OT를 진행하며 스터디 방식, 규칙에 대해서 정하는 시간을 가졌습니다.

  1. 일주일에 한 과목(자료구조, 운영체제, 데이터베이스, 네트워크)씩 강의를 듣고 개인적으로 정리해 보기.
    • 정리해 둘 수 있는 노션 페이지를 따로 만들어서 진행했습니다.
  2. 해당 과목에 대한 예상 질문 만들기
    • 최대한 다른 사람의 질문과 겹치지 않도록 하고, 최소한 2개씩은 만들어오기로 정했습니다.
    • 스터디를 매주 월요일 오후 9시 비대면으로 진행되었고, 내용 정리 + 예상 질문은 매주 목요일 오후 10시까지 만들기로 정했습니다. (만들어진 예상 질문을 스터디원 중 한 분이 매우 공정한 방법(사다리 타기)을 이용해서 담당자를 분배했습니다.)
    • 여기서 어느 정도의 강제성을 주기 위해서 인당 40,000원씩 모아서 매주 정한 규칙을 지키지 않았을 때 10,000원씩 차감하기로 규칙을 정했습니다.
  3. 매주 월요일 이전까지 질문 답변 담당자는 자신이 맡은 질문에 대한 대답을 작성합니다.
  4. 매주 월요일 오후 9시에 비대면으로 게더 타운에서 모여 스터디를 진행합니다.
    1. 모두 모이면 서로 오늘 있었던 일이나, 가벼운 스몰 토크로 체크인을 진행했습니다.
    2. 맡았던 질문들에 대한 답변과 추가적으로 준비한 꼬꼬무(꼬리에 꼬리를 무는) 질문에 대해서도 발표를 합니다.
    3. 준비해 온 답변에 궁금한 점이나 보충할 부분에 대한 피드백을 진행합니다.
    4. 모든 스터디가 끝나면 오늘 스터디는 어땠는지에 대한 응답으로 체크아웃을 진행하고 마무리했습니다.

위와 같은 과정으로 스터디를 진행했습니다.

 

 

 

스터디 후기


1주차에는 서로 어색한 분위기를 풀고, 서로의 소소한 스몰토크(?)도 하면서 분위기를 풀고 스터디 규칙을 만들었습니다.

 

각자 주차에 정해진 강의를 듣고, 강의 내용을 정리하는 과정에서 궁금했던 부분도 많이 찾아볼 수 있었고 알았던 부분은 다시 한번 리마인드 해서 좋았고, 가볍게 알고 넘어갔던 부분들은 보충하는 시간을 가질 수 있었습니다.

 

서로 준비해 온 질문에 대한 "질문자"와 "답변자"를 정해서 노션에 페이지를 하나 만들어 답변을 준비했습니다.

 

 

먼저 팀원분들이 너무 적극적으로 참여해 주셨고, 서로 말하지 않아도 꼬꼬무 질문을 스스로 만들어서 준비해 왔던 것이 너무 좋았습니다.

서로 꼬리까지 물어가며 적극적으로 참여를 했고, 네트워크를 마지막으로 2024년 1월 2일에 스터디를 종료했습니다!

 

처음에 벌금으로 모았던 돈으로 추후 대면 일정이 있을 때, 커피나 밥을 먹기로 하고, 이제 2월까지 마무리해야 하는 각자 팀의 프로젝트가 있기 때문에 거기에 집중하기로 했습니다.

 

이렇게 강의에서 제공해 주는 질문들도 충분히 도움이 되지만, 서로 공부하고 고민하면서 모았던 질문들도 소중한 자산이 된 것 같습니다.

물론 계속해서 공부해야겠지만, 앞으로 할 공부에 큰 도움이 되지 않았을까 하는 마음으로 스터디를 마무리하게 되었습니다.

 

반응형
반응형

20210번 파일 탐색기

 

문자열 유형을 혼내주고 있는 도중 내가 혼나버렸다.

문제를 골랐는데, 항상 정답률을 보고 적당한 문제들을 골랐었는데 하하 왜 안 보고 골랐을까.

일단 골라버렸고, 코드는 작성해버렸고 문제를 풀었다.

 


1. 문제 분석


문제를 처음 보고나서 조건이 되게 많다는 것을 느꼈다.

얼핏 보기에는 뭔가 간단하게 그냥 조건 따라서 정렬하면 되는 문제라고 판단됐다. 그래서 선택했을지도...

 

  1. 둘 다 숫자인 경우
  2. 둘 중 하나만 숫자인 경우
  3. 둘 다 숫자가 아닌 경우

크게 이렇게 3가지 경우로 나눌 수 있었다.

 

  1. 둘 다 숫자라면?
    • 십진법으로 두 숫자의 크기를 비교한다.
    • 더 작은 숫자가 우선순위가 더 높다.
    • 만약 두 숫자의 크기가 같다면, 앞에 0의 개수가 적은 것이 우선순위가 더 높다.
    • 두 숫자의 크기도 같고, 앞에 0의 개수도 동일하다면 우선순위가 동일하다. (저는 여기서...시간낭비를...)
  2. 둘 중 하나만 숫자라면?
    • 숫자가 문자보다 우선순위가 높다.
  3. 둘 다 문자라면?
    • 문자의 우선순위는 다음과 같은 우선순위를 갖는다.
    • 두 문자가 같으면(A와 A) 우선순위가 동일하다.
    • 두 문자의 종류가 같으면(A와 a) 대문자가 우선순위가 높다.
    • 두 문자의 종류가 다르면(A와 c) AaBbCcDd....XxYyZz와 같은 우선순위를 갖는다. (문제 제대로 안 읽으면 여기서 고생)

또 고려해야 할 것이 있다. 이어져있는 숫자를 하나의 문자열로 사용을 하는데, 주어지는 숫자는 2^63을 초과할 수 있기 때문에 일반적인 Long 타입은 사용할 수 없다.

 

문제를 다 풀고 나서 다른 분들의 답을 봤는데, 반복문과 Index를 아주 매력적으로 사용해서 푸신 분들을 많이 봤는데... 존경스럽다.

나는 아주 큰 수도 저장할 수 있는 Java의 BigDecimal을 사용했다.

 

왜냐! BigDecimal은 Comparable 인터페이스를 구현하고 있어서 compareTo를 사용할 수 있기 때문에 사용해 봤다.

 

 

 

2. 소스코드


import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(reader.readLine());
        PriorityQueue<Value> queue = new PriorityQueue<>();

        for (int i = 0; i < N; i++) {
            queue.add(new Value(reader.readLine()));
        }

        StringBuilder builder = new StringBuilder();
        while (!queue.isEmpty()) {
            builder.append(queue.poll()).append("\n");
        }
        System.out.println(builder);
    }
}

class Value implements Comparable<Value> {
    String value;
    ArrayList<String> seperatedValue;

    public Value(String value) {
        this.value = value;
        setSeperatedValue(value);
    }

    private void setSeperatedValue(String value) {
        this.seperatedValue = new ArrayList<>();
        StringBuilder tempBuilder = new StringBuilder();

        for (int i = 0; i < value.length(); i++) {
            char temp = value.charAt(i);

            if (Character.isDigit(temp)) {
                tempBuilder.append(temp);
                continue;
            } else if (tempBuilder.length() > 0) { // 숫자 아니고, 빌더에 값이 있는 경우
                seperatedValue.add(tempBuilder.toString());
                tempBuilder = new StringBuilder();
            }
            seperatedValue.add(String.valueOf(temp));
        }

        if (tempBuilder.length() > 0) { // 다 돌고 빌더에 남아 있는 것이 있으면 마저 넣어줌
            seperatedValue.add(tempBuilder.toString());
        }
    }

    @Override
    public int compareTo(Value other) {
        ArrayList<String> seperatedValueOfOther = other.seperatedValue;
        int smallSize = Math.min(seperatedValueOfOther.size(), this.seperatedValue.size());
        for (int i = 0; i < smallSize; i++) {
            String valueOfThis = this.seperatedValue.get(i);
            String valueOfOther = seperatedValueOfOther.get(i);

            boolean isNumberOfThis = isNumber(valueOfThis);
            boolean isNumberOfOther = isNumber(valueOfOther);

            // 둘 다 숫자인 경우
            if (isNumberOfThis && isNumberOfOther) {
                BigDecimal decimalOfThis = convertToDecimal(valueOfThis);
                BigDecimal decimalOfOther = convertToDecimal(valueOfOther);

                if (decimalOfThis.equals(decimalOfOther)) {
                    // 값도 같고, 0의 개수도 같은 경우
                    if (valueOfThis.length() == valueOfOther.length()) {
                        continue;
                    }
                    return valueOfThis.length() - valueOfOther.length();
                }
                return decimalOfThis.compareTo(decimalOfOther);
            }

            // 둘 중 하나만 숫자인 경우
            if (isNumberOfThis || isNumberOfOther) {
                return isNumberOfThis ? -1 : 1;
            }

            // 둘 다 문자인 경우
            int comparedValue = compare(valueOfThis, valueOfOther);

            if (comparedValue == 0) {
                continue;
            }

            return comparedValue;
        }

        return this.value.length() - other.value.length();
    }

    private int compare(String value1, String value2) {
        if (value1.equals(value2)) {
            return 0;
        }
        if (value1.equalsIgnoreCase(value2)) {
            if (value1.toUpperCase().compareTo(value1) == 0) {
                return -1;
            }
            return 1;
        }

        return value1.compareToIgnoreCase(value2);
    }

    private boolean isNumber(String value) {
        return Character.isDigit(value.charAt(0));
    }

    private BigDecimal convertToDecimal(String value) {
        return new BigDecimal(value);
    }

    @Override
    public String toString() {
        return value;
    }
}

 

처음에 BigDecimal의 생성자를 무자비하게 그냥 사용했었는데, 속도가 많이 떨어지는 것을 느꼈다.

 

그래서 isNumber()라는 메서드를 분리해서 해당 문자열이 숫자인지 확인한 후에 숫자인 경우에만 BigDecimal을 사용하도록 변경했더니 속도가 어느 정도 개선은 됐다.

 

그래도 확실히 속도는 조금 떨어지는 것 같아 보이기는 했다. 집념으로... 풀어서 그래도 오늘 문제 해결해서 잠들 수는 있을 거 같다.

 

집념의 흔적....isNumber()를 적용한 후에 1848ms에서 1384ms까지 속도가 올라가는 것을 확인할 수 있었다.

반응형
반응형

오랜만에 알고리즘이다... 알고리즘은 꾸준히 해야 하는데, 이 꾸준히라는 게 너무 어려운 것 같다.

 

코딩 테스트를 몇 번 보면서 느낀점이 있었다. "문자열" 문제 진짜 많이 나오는데, 아슬아슬하게 맨날 못 풀고 있는 것 같았다.

그래서 문자열 유형을 한번 혼내주기로 했다.

 

 

16934번 게임 닉네임


1. 문제 분석


문자열의 Prefix를 확인해야 하고, 이미 포함되어 있는지에 대한 여부도 확인하면서 문제에 접근해야 했다.

여기서 생각났던 알고리즘은 "트라이" 알고리즘이다. (사실 써보고 싶었습니다.)

 

왜 "트라이"인가? 새롭게 추가되는 문자열을 이미 추가되어 있는 문자열의 prefix에 해당하는지 확인을 해야 한다.

문자열 접두어 확인을 할 때는 "트라이" 알고리즘을 선택하면 빠르게 해결할 수 있다.

 

트라이의 생성 시간 복잡도는 주어지는 닉네임 개수(N, 최대 100,000)와 최대 문자열 길이(L, 10)를 고려해 보면 O(N*L)의 시간복잡도로 트라이를 만들 수 있고, 탐색에서는 O(L)의 시간복잡도로 탐색을 할 수 있어서 빠른 생성과 탐색이 가능한 알고리즘이다. (혁-신)

 

 

 

2. 소스코드


import java.io.*;
import java.util.*;

public class Main {
    private static final String NEW_LINE = "\n";
    private static final String BLANK = "";

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        Trie trie = new Trie();
        NicknameCounts nicknameCounts = new NicknameCounts();
        StringBuilder answer = new StringBuilder();

        int N = Integer.parseInt(reader.readLine());
        for (int i = 0; i < N; i++) {
            String nickname = reader.readLine();
            nicknameCounts.insert(nickname);

            int matchedLastIndex = trie.searchMatchLastIndex(nickname);
            if (matchedLastIndex == nickname.length() - 1) { // nickname 전체가 prefix로 존재
                int x = nicknameCounts.getCount(nickname);
                answer.append(nickname).append(x > 1 ? x : BLANK).append(NEW_LINE);
            } else if (matchedLastIndex > -1) { // trie 안에 겹치는 prefix 존재
                answer.append(nickname, 0, matchedLastIndex + 2).append(NEW_LINE);
            } else if (matchedLastIndex == -1) { // trie 안에 겹치는 prefix 없음
                answer.append(nickname.charAt(0)).append(NEW_LINE);
            }

            trie.insert(nickname);
        }
        System.out.println(answer);
    }
}

class TrieNode {
    Map<Character, TrieNode> childNodes;
    boolean isTerminated;

    public TrieNode() {
        this.childNodes = new HashMap<>();
        this.isTerminated = false;
    }
}

class Trie {
    TrieNode rootNode;

    public Trie() {
        this.rootNode = new TrieNode();
    }

    public void insert(String word) {
        TrieNode node = rootNode;
        for (char ch : word.toCharArray()) {
            node = node.childNodes.computeIfAbsent(ch, key -> new TrieNode());
        }
        node.isTerminated = true;
    }

    public int searchMatchLastIndex(String word) {
        int index = -1;
        TrieNode node = rootNode;
        for (char ch : word.toCharArray()) {
            node = node.childNodes.getOrDefault(ch, null);

            if (node == null) {
                break;
            }
            index++;
        }
        return index;
    }
}

class NicknameCounts {
    private final Map<String, Integer> nicknameCounts;

    public NicknameCounts() {
        this.nicknameCounts = new HashMap<>();
    }

    public void insert(String nickname) {
        if (nicknameCounts.containsKey(nickname)) {
            nicknameCounts.put(nickname, nicknameCounts.get(nickname) + 1);
            return;
        }
        nicknameCounts.put(nickname, 1);
    }

    public int getCount(String nickname) {
        return nicknameCounts.getOrDefault(nickname, 0);
    }
}

 

  1. 입력된 nickname 전체가 이전에 등록된 닉네임의 prefix로 존재하는 경우
  2. 입력된 nickname 일부가 이전에 등록된 닉네임의 prefix로 존재하는 경우
  3. 입력된 nickname이 이전에 등록된 닉네임의 prefix로 존재하지 않는 경우

위와 같이 3개의 경우로 나눠서 코드를 작성해봤습니다.

 

(1) 번의 경우 입력된 nickname 전체가 이미 다른 닉네임의 prefix로 존재하기 때문에 동일한 nickname의 개수가 있는지 확인해 보고, 만약 동일한 nickname의 개수가 1보다 큰 경우 "nickname{개수}"를 붙여서 별칭으로 지정해 줍니다. 만약 1보다 작다면 그냥 본인의 닉네임 자체를 별칭으로 사용합니다.

 

(2) 번의 경우 입력된 nickname의 일부가 이미 다른 닉네임의 prefix로 존재하기 때문에 matchedLastIndex를 사용해서 substring을 통해 가장 짧은 prefix를 만들어냈습니다.

 

예를 들어 "abcd"가 이미지 저장되어 있고, "abdef"가 입력된 닉네임이라면 matchedLastIndex는 1이 될 것입니다. 그럼 여기서 가장 짧은 prefix를 잘라내려면 "abd"가 되어야 하는데, substring(0, 1 + 2)으로 잘라낼 수 있습니다.

 

(3) 번의 경우 nickname이 이전에 등록된 어떤 닉네임의 prefix와도 겹치지 않기 때문에 그냥 자신의 nickname을 그대로 별칭으로 사용하면 됩니다.

 

 

 

 

트라이를 최근에 동아리에서 CS 스터디하면서 알게 되었는데, 그때 검색에서 많이 사용되는 알고리즘이라고 해서 한번 문제에 직접 사용해보고 싶었는데, 마침 문자열 문제에 사용할 수 있게 돼서 재미(?) 있었습니다.

 

다시... 백준 스트릭을 연속으로 채우는 그날까지... 화이팅

 

 

 

반응형
반응형

Enum 타입 조회하기


select enum_range(null::membership_enum);

 

membership_enum이라는 enum 타입을 만들어서 "FREE", "STANDARD", "DELUXE"를 저장해 놨었습니다.

여기서 만약 "VIP"라는 membership_enum 타입이 추가된다면 어떻게 요소를 추가할 수 있을까요?

 

 

 

Enum 타입에 Value 추가하기


alter type membership_enum add value 'VIP';

 

위와 같은 SQL문으로 membership_enum에 "VIP"라는 요소를 추가할 수 있습니다.

근데 만약 VIP가 사라지고, VVIP가 추가되었다면 어떻게 해야 할까요?

 

 

 

Enum 타입 Value 이름 변경하기


alter type membership_enum rename value 'VIP' to 'VVIP';

 

먼저 간단하게 위와 같은 명령어로 "VIP"를 "VVIP"로 변경해 볼 수 있습니다.

 

여기서 만약 member 테이블에서 만약 membership_enum을 필드 타입으로 가지고 있을 때는 "VIP"를 이미 값으로 가지고 있는 데이터가 있을 때는 어떻게 될까요?

지금 보면 park가 아직 membership에 VIP를 가지고 있습니다. 여기서 만약 위 SQL 명령어로 "VIP"를 "VVIP"로 이름을 바꾸면 어떻게 될까요?

membership_enum은 아무런 오류를 발생시키지 않고, 변경되어 버립니다. 이제 변경되었으면 "VIP"라는 값을 갖는 member인 "park"의 membership이 변경되었을까요?

park가 가지고 있던 VVIP가 membership이 "VIP"에서 "VVIP"로 변경되었습니다.

 

이제 만약 "VVIP"를 없애야 한다면 어떻게 해야 할까요? 기존에 "VVIP"인 고객들을 "DELUXE" 등급으로 변경하고, "VVIP"를 membership_enum에서 제거해야 합니다.

 

alter table membership_enum drop value 'VIP'가 될까요? 되지 않습니다. Postgresql에서는 Enum의 value 추가는 가능한데, 삭제가 불가능합니다.

 

사실 불편함이 없다면 기존의 membership_enum을 사용하고 "VVIP"만 사용하지 않으면 됩니다. 하지만 쓰이지도 않는 "VVIP"를 계속 놔두기도 그렇고 잘못해서 사용해서 잠재적 오류를 발생시킬 수 있습니다.

 

이때는 새로운 enum 타입을 만들어서 기존의 member.membership 타입을 변경해 주면 됩니다.

 

 

 

Enum 타입 생성하여 변경하기

create type renew_membership_enum as enum('FREE', 'STANDARD', 'DELUXE');

 

새로 변경할 Enum 타입을 생성해 줍니다. 이때 우리는 "VVIP"는 뺄 것이기 때문에 "FREE", "STANDARD", "DELUXE"만 넣어줍니다.

 

alter table member
alter column membership drop default,
alter column membership drop not null;

 

저는 기존 member 테이블의 membership 필드에 NOT NULL 제약조건과 DEFAULT 조건을 넣어두었기 때문에 이것을 제거해 줍니다.

 

alter table member 
alter column membership type renew_membership_enum
using membership::text::renew_membership_enum,
alter column membership set default 'FREE'::renew_membership_enum,
alter column membership set not null;

 

이제 member 테이블에서 membership 필드의 타입을 새로 생성한 renew_membership_enum으로 변경을 해줍니다.

 

여기서 using 하고 "membership::text::renew_membership_enum"이라는 부분이 있는데, 이것은 기존의 membership 타입의 필드를 문자열(text) 값으로 변경하고, 그 문자열을 renew_membership_enum 타입으로 형변환을 해주라는 것입니다. 그리고 이어서 바로 새로운 NOT NULL 제약조건과 DEFAULT 조건을 추가해 줍니다.

 

위 쿼리를 실행을 해보면 이렇게 오류가 뜹니다.

왜 오류가 떴는지 보면 입력된 "VVIP"는 renew_membership_enum 타입의 value가 아니라는 오류입니다. 이는 아직 우리의 member 테이블에 있는 park라는 회원이 membership 값으로  "VVIP"을 가지고 있기 때문입니다. Case When을 써서 분기를 주기도 하는 거 같은데, 저는 UPDATE 벌크 연산으로 처리를 해주겠습니다.

update member set membership = 'DELUXE' where membership = 'VVIP';

 

이제 alter table 쿼리를 실행해 보면 정상적으로 바뀐 것을 확인할 수 있습니다. 이제 사용하지 않는 기존의 membership_enum은 제거해 줍니다.

drop type membership_enum;

 

변경이 완료되었습니다!

 

 

 

 

반응형
반응형

디프만을 선택한 이유


이번에 백엔드 부트캠프를 마무리하고, 지금까지 배웠던 기술 스택을 종합해서 프로젝트를 만들어 보고 싶다는 생각을 했습니다. 그리고 내가 만든 서비스를 런칭해보고 싶다는 생각을 많이 했었는데, 디프만에 들어오시는 분들 대부분이 같은 목표와 열정을 가지고 있다고 생각을 했습니다. 

 

13기 때 디프만을 알게 되었었는데, 그때는 사실 "넣어도 떨어지겠지..?"라는 생각으로 도전 조차 못해봤었습니다. 백엔드 부트캠프에서 Java, Spring을 학습하고, 프로젝트도 참여해 보면서 사실 자신감도 많이 얻었던 것 같습니다. 부트캠프에서도 열정적인 분들과 만나서 치열하게 고민하고, 프로젝트를 했던 경험이 너무 의미 있었습니다. 그래서 지금까지 배운 지식들을 가지고, 새로운 개발자, 디자이너분들과 함께 서비스 런칭을 목표로 프로젝트를 진행해 보는 것도 너무 의미있고, 재미있을 거 같아서 디프만을 선택하게 되었습니다. 😄

 

 

 

 

서류 접수


서류 접수는 10월 2일부터 8일까지 진행되었습니다. 저는 13기 때 디프만을 알게 된 후로 멀리서 쳐다보고 있어야지 하는 생각으로 디프만 인스타 공식계정을 팔로우하고 있다가 14기 모집 소식을 알게 되었습니다.

 

문항은 총 8개 정도였는데, 마지막 2개는 깃허브 링크와 포트폴리오 링크여서 작성해야 하는 문항은 6개 정도입니다. 저는 2일 날 서류 접수 소식을 바로 알아서 조금 여유 있게(?) 진행했던 거 같습니다. 막판에는 여유가 없었습니다. 🙀

 

저는 노션을 많이 활용하는 편이어서 질문을 모두 노션에 옮겨적고, 문항들을 채워나가기 시작했습니다. 부트캠프를 바로 마무리한 후에 지원을 하게 되어서 쓸 말이 너무 많았습니다. 그래서 문항을 읽고 흐름대로 일단 작성을 한 것 같습니다. 제출하기 전에 계속 검토를 하고... 검토를 하고... 검토를 하다가 제출을 하게 되었습니다. 문항은 지원동기, 프로젝트 협업 경험, 자신있는 기술 스택에 관련된 문항들이 있었습니다.

 

그렇게 서류 마감 후 10일 정도 있다가 메일이 하나 왔습니다. 메일 제목은 "디프만 14기 서류 결과 안내"였습니다. 

서류 합격 메일

서류 합격 메일을 받고, 바로 4일 뒤인 22일 날 면접을 봐야 했었기 때문에 조급해하면서 여기저기 막 구글을 찾아 나서기 시작했습니다. 

 

 

 

 

온라인 면접 준비


뭘 물어보실지 모르기 때문에 면접은 항상 긴장되고, 무서운 것 같습니다. 서류 결과가 나오고 하루 정도 있다가 면접과 관련된 메일이 하나 도착했습니다.

면접 안내 메일

 

면접은 Zep이라는 가상 공간에서 진행됐습니다. 면접 날짜는 서류 접수 당시에 선택할 수 있었던 거 같습니다. 저는 일요일이 모든 시간이 널널했기 때문에 22일을 선택했었습니다. 저는 13시 30분에 면접을 30분 동안 봐야 하는 사람이 되었습니다. 덜덜덜 🥲

 

진짜 인터넷에서 찾을 수 있는 디프만 면접 관련 링크는 다 확인해봤던거 같습니다. 이전 기수분들이 블로그에 많이 작성을 해두셔서 도움이 많이 되었습니다.

 

저는 4일이라는 기간동안 어떻게 준비해야 할까 하다가 서류에 작성했던 내용을 집중적으로 먼저 확인을 하자는 목표를 세웠습니다. 후기들을 많이 참고하긴 했는데, 기술 질문 관련 내용들은 후기마다 많이 달랐던 것 같습니다. 그래서 "내가 써본 것 중에서 여쭤보실 것이다!"라는 결론을 내리게 되었습니다.

 

그래서 관련해서 적었던 기술들, 프로젝트에서 썼던 기술들을 위주로 준비를 했던 것 같습니다. 제가 서류에 자신 있는 기술에 QueryDSL을 썼었습니다. 가장 최근에 마무리했던 프로젝트에서 복잡한 쿼리를 많이 다뤘어서 QueryDSL과 완전 친구를 먹었어서 썼던 거 같습니다.

 

그래서 프로젝트에서 사용했었던 DB(MySQL), JPA, QueryDSL, Spring, Java, AWS, CI/CD 위주로 기술 질문을 준비했습니다. 혼자 내가 면접관이면 나한테 무슨 질문을 할까라는 생각을 하면서 질문을 만들어 스스로 답변을 했습니다. 하나 하나 다 적고 나니 너무 많아서... 사실 모두 답변을 준비하지는 못했습니다.

 

 

 

 

온라인 면접 당일


일요일날 아침 7시 40분에 일어나서... 어제 보다가 잠든 질문들을 한번 다시 쓱 봤습니다. 면접은 다대다 면접이었고, 같은 파트 사람들끼리 면접을 진행합니다. 같이 보는 인원은 2명에서 3명이라고 되어있었습니다.

 

저는 면접관님이 두 분이셨고, 면접자는 저를 포함해서 2명이었습니다. 기본적인 질문들을 해주시는 면접관님 한 분과 기술 질문을 담당해 주시는 면접관님이 계셨습니다.

 

기본적으로 자기소개를 시작으로 해서 각자 위치에 맞는 질문을 해주시는 것 같았습니다. 저는 취준을 하고 있었기 때문에 취준과 병행이 가능한지에 대해서 여쭤보셨었습니다. 아무래도 팀을 이뤄서 2월까지 프로젝트를 마무리하는 동아리이기 때문에 "이 사람 끝까지 포기하지 않고 할 수 있는 사람인가?"를 확인해보시기 위한 질문이 대부분이었던 거 같습니다. 편하게 질문을 이어가주셔서 되게 좋았습니다. 😄

 

이제 대망의 기술 질문이 시작되고, 심장이 요동쳤었는데 커피챗한다는 느낌으로 참여해 주시면 좋을 거 같다고 말씀을 하시고 시작했습니다. 기술 질문은 공통 질문이 없었고, 각자 사용해 보거나 자신 있다고 적었던 기술 스택 위주로 질문을 주셨습니다. 저는 QueryDSL을 적었어서 관련된 질문을 많이 받았습니다. 그 외에도 DB, JPA 위주의 질문을 해주셨습니다. 준비한 것 외에서 나온 것도 몇 개 있었는데 프로젝트하면서 알게 된 내용 위주로 답을 했습니다. 사실 뭐라고 대답을 했는지도 긴장하고 있어서 기억이 안 났습니다. 기술 질문에서 제가 느꼈던 것은 "이 기술을 쓴 이유가 있는지? 제대로 알고 프로젝트에 사용한 것이 맞는지?"에 대해서 확인을 하시려고 했던 것 같습니다.

 

그렇게 30분을 순삭하고, 나와서 유부 초밥을 왕창 먹었습니다. 디프만 사이트에는 최종 합격은 10월 31일 날 나온다고 쓰여 있어서 이제 기다림의 시간만이 남았습니다.

 

 

 

 

최종 발표


최종 발표 메일은 10월 29일날 왔습니다. 제가 언제부터인가 메일을 거의 카톡처럼 확인하는 습관이 생겨서 계속 수시로 보다가 저녁에 메일을 확인하게 되었습니다.

 

최종 합격 메일

 

진짜 확인하고 나서 진짜 붙은건가 진짜인가 이러면서 확인을 했었습니다. 디프만 13기 때 쳐다만 보다가 14기에 이렇게 붙으니 진짜 기분이 묘했습니다. 개발직군 경쟁률은 10.7대 1이라고 인스타그램에 나와있어서 기분이 뭔가 좋았습니다. 😁

 

디프만에서 이제 2월까지 활동을 하게 되었는데, 중간중간 회고를 작성해 볼 생각입니다. (계획은...항상 거창하게)

 

OT가서 소심하게 한장 찍었습니다.

 

 

반응형

'🎒 Activity > 디프만' 카테고리의 다른 글

[디프만 x 인프런] CS 완전 정복 스터디  (1) 2024.01.04
반응형

Client에서 EC2까지 가는 아키텍처 (RDS, S3 등 제외)

 

최근 아키텍처를 설계해야 하는 일이 있어서 위와 같이 Cloud Front를 앞단에 두고, ELB에 요청을 전달해서 EC2로 가는 방향으로 설계를 했습니다.

 

예상했던 결과대로 서버 도메인을 입력하면 API 서버에 접근할 수 있었습니다. 하지만, 원하는 결과와는 조금 다르게 ELB의 DNS 서버로도 아무나 접근이 가능했습니다.

 

누구나 ELB의 DNS를 알면 접근이 가능

 

서버에 접근하는 경로는 단 하나만 있는 게 보안상으로도 좋을 거 같아서 ELB(Origin)에서는 내가 배포한 Cloud Front에서 오는 요청만 받고, 나머지 요청은 모두 403 Forbbiden으로 처리하도록 하고자 했습니다.

 

 

 

1. Cloud Front에 등록된 Origin(ELB)로 보내는 요청에 헤더 추가하기


Cloud Front 콘솔 -> 배포 -> 원본 -> 내 ELB 체크 -> 편집
헤더 추가 -> 헤더 이름, 값 입력 -> 변경 사항 저장

위에서는 헤더 이름을 "X-Custom-Access-Token"이라고 했지만, 본인이 원하는 이름으로 하면 됩니다.

기존에 사용되는 통상적인 헤더 이름은 피해 주시는 것이 좋습니다.

값도 본인이 원하는 값으로 입력합니다. 값을 정하기 어렵다면 여기를 눌러서 한번 Random한 Key 값을 만들어보세요.

모두 입력했으면 이제 변경 사항 저장을 눌러서 저장해 줍니다.

 

 

 

2. ELB(Elastic Load Balancer) 리스너 및 규칙 추가하기


EC2 -> 로드밸런서 -> 내 로드 밸런서 선택 -> HTTP:80 또는 본인이 설정한 규칙 선택 -> 규칙 편집

 

이제 Cloud Front에서 보내는 커스텀 헤더를 받아서 내 Cloud Front에서 온 요청이구나를 알 수 있도록 로드 밸런서(ELB)의 리스너 규칙을 편집해줘야 합니다. 위에 파란 부분은 최종 결과물입니다. 저는 이미 설정을 해뒀었기 때문에 저렇게 뜨니 참고만 해주시면 될 거 같습니다.

 

들어와서 아래로 내린 후 "규칙 추가"를 눌러줍니다.
규칙의 이름을 입력해줍니다. 아무 거나 입력하셔도 됩니다. Ex) "Listener Rule"
"조건 추가"를 클릭해서 조건을 추가해줍니다.
저희는 HTTP 헤더를 이용할 것이기 때문에 HTTP 헤더를 선택해줍니다.
헤더의 이름과 헤더의 값을 입력해줍니다.

 

여기서 입력할 때는 1번에서 Cloud Front 원본(Origin)에 설정했던 헤더의 이름과 값을 입력해줘야 합니다.

이제 확인을 누르고 쭉쭉쭉 넘겨서 대상 그룹(EC2 인스턴스 그룹)을 선택하고, 규칙을 저장합니다.

 

규칙을 저장을 한 후의 화면

 

규칙을 저장하면 이제 내가 만든 규칙이 뜨는 것을 확인할 수 있습니다. 이제 거의 다 왔습니다.

처음 ELB를 만들면 "기본값"으로 규칙이 하나 추가됩니다. 

보면 "다른 규칙이 적용되지 않는 경우"에는 대상 그룹으로 전달된다고 되어 있습니다.

이렇게 되면 내가 정해둔 조건 이외의 어떤 요청이 오더라도 대상 그룹으로 전달되어 처음과 달라지는 것이 없습니다.

그렇기 때문에 기본값의 작업 내용을 바꿔줘야 합니다.

기본값 규칙 체크 -> 규칙 편집 클릭
고정 응답 반환 선택 -> 응답 코드 입력 -> 응답 본문 입력

 

기존에 "대상 그룹으로 전달"로 되어 있는 선택지를 "고정 응답 반환"으로 변경합니다.

응답 코드는 어떤 걸로 입력해도 상관없습니다. 저는 Forbidden으로 사용하기 위해서 403을 입력해 줬습니다.

응답 본문은 선택 내용입니다. 저는 "Access denied"라고 뜰 수 있도록 설정해 뒀습니다.

이제 변경 내용 저장을 눌러줍니다.

 

이제 서버 도메인으로 한번 접근을 해보겠습니다. (Cloud Front을 통해서 접근하는 방법)

200 OK를 응답하면서 접근에 성공했습니다.

 

이 부분도 사실 추후에 Client가 완성되면 Cors에 등록해서 외부 접근은 막아 주는 것이 좋습니다.

 

이제 ELB의 DNS로 접근을 한번 해보겠습니다. (Elastic Load Balancer에 직접 접근하는 방법)

의도했던 대로 403 에러를 발생하며, "Access denied"라는 문구가 출력됩니다.

 

모두 의도한 대로 설정에 성공했습니다!

 

 

 

반응형

+ Recent posts