728x90

이진 트리란?

이진 트리는 트리 자료구조의 한 종류로, 각 노드가 최대 2개의 자식노드를 가질 수 있는 구조입니다.

 

이진 트리를 구성하는 노드들은 3가지로 분리할 수 있습니다.

  • 루트 노드 ( Root Node ): 트리의 시작점으로, 부모 노드가 존재하지 않고 이진트리에서 하나만 존재합니다.
  • 자식 노드 ( Child Node ): 부모 노드에 속한 노드로써 Left Child와 Right Child로 구분.
  • 잎 노드 (Leaf Node): 트리의 마지막에 존재하는 노드로써 자식이 존재하지 않는 노드를 의미합니다.

위와 같이 3가지로 분류할 수 있지만 때에 따라 하나의 노드가 의미하는 바가 겹칠 수 있습니다.


이진 트리 유형

이진 트리는 속한 노드들의 상태에 따라 아래와 같은 유형들로 분류할 수 있습니다.

균형 이진 트리 ( Balanced Binary Tree )

모든 자식 노드들의 Level차이가 최대 1 이하 차이나는 트리입니다.

 

즉, 한 노드에 쏠림 현상이 발생하지 않고 아래와 같이 규형 잡힌 상태를 의미합니다.

균형 이진 트리

포화 이진 트리 ( Full Binary Tree )

모든 자식 노드가 0개 혹은 2개씩 채워진 상태를 의미합니다. 

포화 이진 트리

완전 이진 트리 ( Complete Binary Tree )

완전 이진트리는 마지막 레벨을 제외 모든 노드들이 채워져 있는 상태를 의미합니다.

이때 자식 노드는 왼쪽부터 차 있어야 합니다.

완전 이진트리


이진 트리 표현

이진 트리는 배열과 링크드 리스트를 통해 표현할 수 있습니다.

Array 표현

배열을 통해 이진트리를 표현할 경우 아래와 같은 규칙을 통해 표현할 수 있습니다.

  • 루트는 배열의 0번째 인덱스 혹은 1번째 인덱스에 존재한다.
  • 왼쪽 자식인덱스 = 루트 인덱스 == 0 ? 부모 인덱스 * 2 + 1 : 부모 인덱스 * 2;
  • 오른쪽 자식 인덱스 = 루트 인덱스 == 0 ? 부모인덱스 * 2 + 2; 부모 인덱스 * 2 + 1;

위와 같은 규칙을 가지고 이진트리를 표현하였을 때 단점이 존재하는데,

이는 배열로 구현하다 보니 초기 이진트리의 크기가 특정된다는 점이고, 이는 이진트리 내 데이터가 골고루 분포되지 않다면 메모리 자원을 비효율 적으로 사용한다는 단점이 존재합니다.

배열로 표현한 이진트리

public static void main(String[] args) {
        int[] arr = new int[7];
        // Root
        arr[0] = 1;
        // Level 1
        arr[1] = 2;
        arr[2] = 3;
        // Level 2
        arr[3] = 4;
        // Level 3
        arr[5] = 6;
        arr[6] = 7;
        printTree(arr);
}

코드로 작성하면 위와 같이 작성을 할 수 있고, Terminal에 출력할 경우 위와 같이 출력해 볼 수 있습니다.

더보기

Termial 출력 코드

public static void printTree(int[] arr) {
        int level = 0;
        int index = 0;
        while (index < arr.length) {
            int nodesInLevel = (int) Math.pow(2, level); // 해당 레벨의 노드 개수
            for (int i = 0; i < nodesInLevel && index < arr.length; i++) {
                System.out.print(arr[index] + " ");
                index++;
            }
            System.out.println(); // 각 레벨 출력 후 줄바꿈
            level++;
        }
 }

Linked List 표현

Linked List로 표현할 경우 좀더 효율적으로 생성이 가능하다는 장점이 있지만, 균형 이진 트리인 경우 배열로 구현하는 것이 더욱 효율적이라 생각합니다.

 

우선 우리가 알고 있는 java.util.LinkedList 구현체로 구현하는 것은 아닙니다.

LinkedList의 구현체 중 데이터 저장 클래스인 Node를 확인해보면 Node안은 아래 필드들로 구현되어있습니다.

  • E item
  • Node<E> next
  • Node<E> prev

위와 같이 이전과 이후에 대한 주소를 얻을 수 있지만 우리가 원하는 이진트리를 구현하기 위해서는 next가 leftNext와 rightNext로 나눠져야합니다.

 

그리하여 직접 구현한다면 아래와 같이 구현할 수 있습니다.

public static class TreeNode<E> {
        private E item;
        private TreeNode<E> left;
        private TreeNode<E> right;

        public TreeNode(E item) {
            this.item = item;
        }

    }

    public static class BinaryTree {
        TreeNode<Integer> root;

        public void add(Integer item) {
            root = addRecursive(root, item);
        }
        private TreeNode<Integer> addRecursive(TreeNode<Integer> current, Integer item) {
            if (current == null) {
                return new TreeNode<>(item);
            }

            if(current.item > item) {
                current.left = addRecursive(current.left, item);
            } else {
                current.right = addRecursive(current.right, item);
            }

            return current;
        }
}

root를 기준으로 왼쪽은 작은값, 오른쪽은 큰값으로 셋팅되게 구현하였습니다.

 

검색의 성능을 높이기 위해 최소 힙, 최대 힙, 탐색 이진 트리 등등 다양한 방법으로 구성될 수 있으며,

해당 내용들은 다음 글에 작성하도록 하겠습니다.

Reference

https://www.w3schools.com/dsa/dsa_data_binarysearchtrees.php

 

W3Schools.com

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

728x90

'알고리즘 문제풀이 > 자료구조' 카테고리의 다른 글

[ Tree ] 이진 탐색 트리 삽입, 삭제  (0) 2024.11.16
[Tree] 이진 트리 순회  (1) 2024.11.08
[자료구조] 스택(Stack)  (0) 2024.10.21
728x90

개요

우리들에게 URL은 매일 수십 번을 적고, 복사하고, 붙여 넣는 혹은 알게 모르게 사용하고 있는 자원입니다.

 

이렇듯 우리가 여러 사이트들을 접근하기 위해서는 URL을 사용하여 접속을 해야 하는데,

어떻게 접근할 수 있는 것인지 그리고, URL자원들은 어떤 의미를 가지는지를 알아보도록 하겠습니다.


URL

우리가 URL를 통해 어떻게 사이트를 호출할 수 있는지를 알기 위해서는 URL이 뭔지 그리고 내부 요소들은 무엇이 존재하고, 어떤 의미를 가지는지를 먼저 이해하는 게 중요합니다.

 

URL은 인터넷에서 고유한 식별 주소입니다.

여기서 중요한 게 URIURL를 혼동할 수 있는데,

URI식별자이고, URL은 식별자 + 프로토콜 + 행위 등을 전부 포함한 것입니다.

 

그럼 식별자는 무엇이고, 프로토콜, 행위들이 URL의 어떤 부분인지 알아보도록 하겠습니다.


URL해부

Naver의 환율 계산하는 사이트의 주소를 해부해 보도록 하겠습니다.

https://finance.naver.com/marketindex/?tabSel=exchange#tab_section 

Schema

https://finance.naver.com/marketindex/?tabSel=exchange#tab_section 

처음 나오는 부분은 Schema입니다.

 

이는 브라우저가 요청을 처리할 시 사용할 프로토콜을 나타내게 됩니다.


Authority

https://finance.naver.com/marketindex/?tabSel=exchange#tab_section 

권한 부분입니다.

 

이는 많은 URL마다 다른 부분인데 현재 색칠되어 있는 부분 finance.naver.com은 도메인이라고 부릅니다.

 

원래 IP와 Port가 명시되어 있어야 하지만 사이트마다 다 다른 IP와 Port를 외우는 것은 너무 힘들겠죠??

그리하여 DNS라는 도메인과 IP정보를 가지고 있는 서버를 사용하여 도메인을 명시할 경우 서버에게 해당 도메인의 IP를 받아와서 호출하고자 하는 서버를 호출하는 방식으로 통신을 하고 있습니다.


Path to resource

https://finance.naver.com/marketindex/?tabSel=exchange#tab_section 

경로입니다.

이는 앞서 도메인을 통해 서버를 호출하였다면, 해당 경로 정보를 통해 원하는 콘텐츠를 제공하는 API 혹은 페이지를 접근할 수 있도록 즉 웹서버에서 해당 요청을 처리할 수 있는 위치를 특정하는 경로입니다.


Parameters

https://finance.naver.com/marketindex/?tabSel=exchange#tab_section 

매개 변수입니다.

콘텐츠를 제공받기 위해 필요한 정보를 나타내며,

시작지점은 /가 아닌?로 표기하게 되고 이 뒤에 붙는 자원들은 Key Value형태로 명시되게 됩니다.

 

여러 개를 요청할 경우 &를 붙여 동일하게 Key Value형태로 명시하게 됩니다.

 

이는 Path to resource를 통해 콘텐츠를 제공해 줄 수 있는 서버 내 위치를 특정하고,

콘텐츠를 제공받기 위해 받아야 하는 정보들을 Parameters로 제공을 받게 됩니다.


Anchor

https://finance.naver.com/marketindex/?tabSel=exchange#tab_section 

해당 페이지 내 행위입니다.

 

이는 페이지 내에서 원하는 행위를 표기하게 됩니다.

이는 무엇인가??

 

우리는 앞서 원하는 콘텐츠를 제공받기 위해 서버의 위치를 특정하고, Parameters로 원하는 정보까지 넘겨줬습니다.

하지만 우리가 받은 콘텐츠에서 어떤 행위를 의도하고 싶으면 어떻게 해야 할까요??

 

아리송한 말이죠? 우리가 네이버에 접속했다고 생각해 봅시다.

만약 우리가 네이버에만 접속하는 게 아닌, 접속했을 때 달력이 펼쳐져 있길 원한다면 어떻게 해야 할까요?

 

이렇듯 서버로부터 제공받은 콘텐츠에서 어떤 행위가 발생한 시점으로 처음부터 가기를 원한다면 Anchor를 통해 명시한다로 보면 될 것 같습니다.

 

콘텐츠를 서버로부터 받고 난 이후 처리하는 자원이기 때문에 당연히 서버에서는 사용하지 않는 자원입니다.


Reference

https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL

 

What is a URL? - Learn web development | MDN

A URL (Uniform Resource Locator) is the address of a unique resource on the internet. It is one of the key mechanisms used by browsers to retrieve published resources, such as HTML pages, CSS documents, images, and so on.

developer.mozilla.org

 

728x90
728x90

개요

Web Developoer로써 활동을 하고 있으면서 평소 당연하게 생각한 것들에 대해 다시 돌아보는 와중

WebServer와 WAS에 대해 설명할 수 있나 라는 생각이 들었습니다.

 

이에 기본기를 돌아보며 정리를 해보려 합니다.


WebServer란?

WebServer는 주로 HTTP통신을 처리하는 서버입니다.

 

클라이언트가 웹브라우저에서 URL를 통해 서버로 요청을 보내면, 서버는 해당 요청을 받아 일치하는 정적 콘텐츠( HTML, CSS, Javascript, image,... )를 제공하게 됩니다.

 

WebServer의 특징으로는 동일한 요청에 대해 동일한 파일을 반환합니다.

 

즉 WebServer는 웹 브라우저의 화면을 구성하는 요소들을 반환한다고 보면 좋을 것 같습니다.

주로 Apache Server, Nginx 등을 사용하고 있습니다.


WAS(Web Application Server)란?

WAS란 Web Server와 Web Container를 가지는 서버입니다.

 

주로 사용자의 요청을 받아 비즈니스 로직을 통해 처리하며, 데이터베이스 연동이 이뤄지는 작업이 있을 때 사용하는 서버로, 동적 콘텐츠를 제공받게 됩니다.

 

WebServer와 다르게 화면을 구성하는 요소들을 반환하는 것이 아닌,

화면을 구성하는데 필요한 데이터들을 연산, 데이터베이스 연동을 통한 CRUD 등의 작업들을 수행하고 반환하는 역할을 담당합니다.

주로 Tomcat 등을 사용하고 있습니다.


사용처

위에서 소개했듯이 용도에 따라 사용하는 서버가 다른데요,

페이지를 만들 때는 WebServer, 데이터를 필요로 할 때는 WAS를 사용하면 될 것으로 보입니다.

 

그럼 실제로는 어떻게 사용하고 있을 까요??

 

실제로는 두 개를 병합하여 사용하고 있습니다.

 

Tomcat의 경우 Apache Tomcat으로도 부르는데 이는 2008년부터 Tomcat에 Apache 즉 WebServer가 추가되어 있기 때문입니다.

 

그럼 Tomcat은 WAS이니 Tomcat만 쓰면 되겠네요???

제가 경험한 개발은 상황에 따라 다르게 사용하는 점이 많다는 것인데요.

 

Apache Tomcat만 사용해도 제작은 가능합니다.

 

하지만 Web Browser에서 WebServer로 요청을 보내고, WebServer에서 데이터를 얻기 위해 WAS에 요청을 보내게 됩니다.

 

이렇듯 서비스가 제대로 동작하려면 다수의 요청을 수용가능해야 하는데요,

WAS로 띄워버린다면 하나의 서버가 너무 많은 요청을 처리해야 하게 되는데 이는 좋지 않은 상황을 발생할 수 있습니다.

 

또한 WebServer는 요청에 대한 처리가 정적 콘텐츠를 제공하는 만큼 많은 자원을 필요로 하지는 않지만,

WAS의 경우 다양한 비즈니스로직 처리, DB와의 연동을 통해 데이터를 주고받으려면 어쩔 수 없이 많은 자원을 소모할 수밖에 없습니다.

 

그럼 많은 요청을 처리하기 위해서는 어떻게 해야 할까요??

 

만약 Apache Tomcat으로 모든 것을 처리할 경우 WAS만 여러 대 띄우게 될 것입니다.

 

이는 WebServer도 같이 여러 대 뜬다는 것인데요, 이는 불필요하게 서버 자원을 소모하는 요인으로 만약 이렇게 한다면 서버를 띄우는 컴퓨터 혹은 클라우드의 비용이 들게 됩니다.

 

그렇기에 WebServer와 WAS를 분리하여 가져가며 WebServer는 하나, WAS는 여러 개를 띄움으로써 많은 요청들을 효율적이게 처리하게 하는 방법을 현재 많이 사용하고 있습니다.

 

하지만 사용자가 많지 않고 WAS로 띄운 서버 성능도 별로 좋지 않아도 된다 하면,

Apache Tomcat에 한 번에 개발하는 것이 번거롭지 않고 편하겠죠.

 

즉 상황에 맞게 적용하시는 것이 좋을 것으로 보입니다.


References

https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_web_server

 

What is a web server? - Learn web development | MDN

The term web server can refer to hardware or software, or both of them working together.

developer.mozilla.org

https://www.ibm.com/topics/web-server-application-server

 

Web Server Versus Application Server | IBM

By strict definition, a web server is a common subset of an application server.

www.ibm.com

 

728x90
728x90

Stack이란?

Stack은 한쪽 끝에서만 자료를 넣고 뺄 수 있는 LIFO( Last In First Out ) 자료구조입니다.

추상자료형( abstract data type, ADT )이며, 이는 논리적인 기능을 명시해 놓은 것이라 볼 수 있고, 이는 구현체가 이를 상속받아 구현하는 방식으로 자료형은 Stack이지만 구현은 여러 방법으로 구현할 수 있음을 의미합니다.

 

Stack은 Vector를 상속받아 구현을 하고 있는데 이는 Vector를 구현되 LIFO방식으로 데이터를 다룰 수 있게 다루기 위해 Stack이란 자료형이 존재한다 생각해 볼 수 있습니다.

 

우선 기본적인 기능을 확인해 보겠습니다.

Stack은 Vector를 상속한 ADT이기 때문에 기본적으로 Object []인 elementData로 데이터를 관리하고 있습니다.

 

배열의 크기는  기본적으로 10의 크기로 생성하고 있으며, resizing을 통해 많은 데이터가 들어오더라도 수용할 수 있습니다. 이렇듯 대부분의 기능은 동일하지만, 외부에서 접근할 수 있는 pop메서드를 확인해 보면 elemtnData의 Size를 확인하여 맨 마지막 데이터만을 추출할 수 있도록 구현되어 있는 것을 확인할 수 있습니다.

public synchronized E pop() {
    E       obj;
    int     len = size();

    obj = peek();
    removeElementAt(len - 1);

    return obj;
}

 

제공하는 기능

Method 기능
push item을 추가합니다.
pop 최근 들어온 Element를 추출합니다.
peek 최근 들어온 Element를 확인합니다.
search element를 조회합니다. ( Index를 반환합니다. )

사용처

그럼 Stack은 어떤 곳에서 사용하게 될까요??

웹브라우저 History 즉 뒤로가기 앞으로가기 와 같은 기능처럼 history를 쌓아두고 이를 최신 것부터 접근하는 상황이 있다면 이럴때는 Stack을 사용함으로써 편하게 관리할 수 있습니다.

 

또한 재귀함수와 같이 연산이 루프를 도는 방향의 알고리즘을 구현을 한다면,

최근 접근 한 것들을 접근 할 수 있는 Stack을 통해 쉽게 최근에 넣은 데이터를 꺼내 연산을 하면서 손쉽게 구현할 수 있습니다. 백준 코딩 테스트

References

https://docs.oracle.com/javase/7/docs/api/java/util/Stack.html

728x90
728x90

스펙

OS: MAC OS

VM Tool: UTM

설치 OS: Ubuntu-22.04.3

설치

UTM 설정

 1. Virtualize와 Emulate중 선택

2. 설치할 OS선택

3. 설치할 Linux image 선택 및 적용

4. 스펙 설정

5. 저장공간 설정

6. VM이름 설정 및 Setting

7. VM설치시 처음 접하는 화면 

Try or Install Ubuntu Server 선택 이후 기다리면 다시시작된다.

8. 언어 설정

9.

10. 언어설정

11.

12. 네트워크 설정

13. 프록시 설정

14. storage layout설정

15. Storage 설정

16. Ubuntu Pro 업그레이드 설정

17. SSH 설정

18. 인기많은 서버 Snaps 설정

19. Ubuntu 기본 셋팅 

기다리다 Reboot now 버튼이 생기면 Reboot now버튼을 클릭하여 리붓

20. 설치완료

이후 다시 접속 시 아래와 같이 UbuntuOs VM완성 초기 설정한 ID와 Password로 접속 가능하다.

728x90
728x90

주제

금주 테코테코의 주제는 Stack입니다.

 

Stack 자료구조를 활용하거나 Stack자료구조의 작동원리를 이해하여 아래 문제를 해결하였습니다.

 

문제

1.  올바른 괄호

'(' 또는 ')' 로만 이루어진 문자열 s가 주어졌을 때, 문자열 s가 올바른 괄호이면 true를 return 하고, 올바르지 않은 괄호이면 false를 return 하는 solution 함수를 완성해 주세요.

 

코드

public class 올바른_괄호 {

    boolean solution(String s) {

        Node first = null;
        Node last = null;

        int len = s.length();
        for(int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if(c == '(') {
                if(first == null) {
                    first = new Node();
                    last = first;
                    continue;
                }
                last = last.newNextNode();
                continue;
            }
            if(first == null) return false;
            Node prev = last.removeNowNode();
            if(prev == null) {
                first = null;
            }
            last = prev;
        }

        return last == null;
    }

    public static class Node {
        public Node prevNode;
        public Node nextNode;

        public Node() {
        }

        public Node(Node prevNode) {
            this.prevNode = prevNode;
        }

        public static Node from (Node prev) {
            return new Node(prev);
        }

        public Node newNextNode() {
            this.nextNode = Node.from(this);
            return this.nextNode;
        }

        public Node removeNowNode() {
            Node prev = this.prevNode;
            if(prev == null) return null;
            prev.nextNode = null;
            return prev;
        }
    }
}

 

2.  괄호_회전하기

대괄호, 중괄호, 그리고 소괄호로 이루어진 문자열 s가 매개변수로 주어집니다. 이 s를 왼쪽으로 x (0 ≤ x < (s의 길이)) 칸만큼 회전시켰을 때 s가 올바른 괄호 문자열이 되게 하는 x의 개수를 return 하도록 solution 함수를 완성해주세요.

 

코드

package com.codingTest.programers.level2;

import java.util.*;

public class 괄호_회전하기 {
    Map<Character, Integer> map;

    public 괄호_회전하기() {
        map = new HashMap<>();
        map.put('(', 0);
        map.put('[', 1);
        map.put('{', 2);
        map.put(')', 3);
        map.put(']', 4);
        map.put('}', 5);
    }

    public int solution(String s) {
        int len = s.length();

        int answer = 0;

        boolean flag;
        Stack<Character> checkPoint;

        int point = 0;
        for(int i = 0; i < len; i++){
            flag = false;
            checkPoint = new Stack<>();
            for(int j = 0; j < len; j++){
                char c = s.charAt((j + point)%len);
                int index = map.get(c);
                if(index < 3) {
                    checkPoint.push(c);
                } else {
                    if(checkPoint.isEmpty()) {
                        flag = true;
                        break;
                    }
                    char check = checkPoint.pop();
                    int prevIndex = map.get(check);
                    if(prevIndex != index%3) {
                        flag = true;
                        break;
                    }
                }
            }
            if(checkPoint.isEmpty() && !flag) {
                answer++;
            }
            point++;
        }

        return answer;
    }
}
728x90
728x90

길다면 길고 짧다면 짧은 항해 플러스 백엔드 10주 차 과정을 마무리하였습니다.

 

돌이켜 보면 그 10주 동안 참 많은 일들이 있었다 느껴지네요.

 

첫 발제를 듣던 토요일,

어떻게 해야 할지 고민하는 일요일,

출근하면서 과제해야 하는구나 절망의 월요일,

멘토링 시간이 얼마 안 남았구나 화요일,

과제 제출일이 얼마 안 남았구나 수요일,

내일 제출이니 오늘은 밤새어서 끝내야지 목요일,

제출 완료했으니 정리 좀 해볼까? 금요일

 

일주일이란 시간 동안 참 알차게 보냈다 생각이 드네요.

 

많이 힘들었던 거 같네요.

지금 생각해 봐도 개인적으로 힘든 시간이었고, 출근하면서 과제를 수행하는 것은 또 하나의 고행이라고 느껴지기도 했습니다.

 

끝난 지 거의 1주가 다 되어가는 지금도 힘듬의 여파가 남아 있는 거 같네요.

 

근데!

후회는 전혀 없고, 오히려 앞날이 기대가 되기 시작했어요.

 

오늘 새로운 혹은 다른 분들이 어떻게 쓰는지 알 수 있겠구나 기대된다 토요일,

감사한 피드백을 토대로 이해하며 리펙토링 해보자 일요일,

우리 모두 같이해봐요 으쌰으쌰 월요일,

모르던걸 물어볼 기회가 생겼어!! 이참에 열심히 물어보고 고민해 봐야겠다! 화요일과 수요일,

오늘은 드디어 마지막 날이다! 파이팅 해서 마무리해 봐야지!! 패스받고 말겠어! 목요일,

오늘 잘 제출한 거 같아요 드디어 해방이다~~ 금요일

 

너무 재밌고 소중했던 일주일 시간이 보이네요.

너무나도 소중한 1조, 분위기 메이커가 많았던 3조, 4조, 5조.

 

그곳에만 가면 마음이 편해진다 10조. ( 자주 방문한 조들입니다.. 모두 사랑해요. )

너무나도 소중한 동료들과 같이 힘내서 일주일 동안 열심히 과제를 수행한 일주일 총 10주였습니다.

물론 힘들었죠, 하지만 믿고 따라갈 수 있는 코치님들과 동료들이 있으니 저는 너무나도 재밌는 과정이었던 거 같네요.

 

이런 과정을 수행한 저는 올해로 3년 차 백엔드 개발자 이건입니다.

 

저는 한 회사에서 현재 3년 차로 근무 중이고, 실제 업무는 풀스택을 수행하고 있습니다...

 

업무를 진행하면서 회사에서 사용하는 아키텍처, 개발문화, 코딩 방법, 소통방법 등등 저는 이 모든 것들이 외부 즉 지금 있는 환경 밖에서 어떻게 보일지 너무 궁금한 개구리였습니다.

 

회사의 바쁜 업무로 인해 아침 출근 새벽 퇴근을 밥먹듯이 하는 저는 성장이 너무나도 고픈 개발자였습니다.

 

하지만 이때 고민이 생겼습니다.

 

과연 내가 공부만 해서 계속 쌓여만 가는 연차를 커버할 수 있는 개발자인가??

또 좋은 리더급으로 성장하고 있는 중인가??

나는 열심히  공부하고, 소통하고, 개발문화를 만들어보고 싶어 노력을 하는데 이게 맞는 걸까??

 

여러분은 어떠신가요?

저와 같은 고민을 하시진 않더라도 이 글을 읽고 계신다면 비슷한 고민을 한 번쯤은 해보셨을 것이라 생각이 듭니다.

 

이런 고민을 해결할 수 있는 방법을 찾던 중 저는 아래와 같은 방법들을 고민해 봅니다.

  1. 부트캠프를 들어가 본다.
  2. 다른 개발자에게 커피챗을 신청해 본다.

너무 적은 선택지를 고민해 본 거겠죠?

 

하지만 개발자 세상을 잘 모르는 저는 위 두 방법밖에는 몰랐습니다.

 

그래서 보면 첫 번째 방법 "부트캠프를 들어가 본다." 흠...

경력이 쌓이면서 부트캠프를 반복적으로 들어가 6개월을 낭비해야 하나??

그게 바로 물경력 아닌가??라는 생각을 해서 기각하였습니다.

 

두 번째 방법 "다른 개발자에게 커피챗을 신청해 본다." 흠...

어떻게 해야 하지?? 괜히 바쁘신데 민폐 아닌가??

괜히 이야기하는데 내가 너무 준비 안되어있으면 어떡하지??

 

위와 같이 저는 두 가지 방법 전부 제대로 소화해 낼 자신이 없었습니다.

부트캠프는 퇴사를 해야 하고, 커피챗은 너무 부담이 되고...

 

그렇게 고민하던 중 항해 99를 보게 되었습니다.

항해 99는 원래 알고 있던 곳이었습니다.( 신입으로 들어오신 분들이 잘하셔서 )

 

그래서 항해 99에서 뭐 할 거 있나?? 호기심에 들어가 봤더니 거기에 재직자 대상으로 진행하는 코스가 있던 것이었습니다!

 

커리큘럼도 보니 TDD, 클린 아키텍처, Kafka, Redis, Mysql 와우!

평소 사용하고 있지만 제대로 이해하지 못한 것들, 그리고 배우고 싶은 내용들이 있었던 것이었습니다.

 

또한 돈을 내고 배우는 것이다 보니 무료 부트캠프처럼 아무나 오는 것이 아닌 성장에 목이 마른 분들이 오실 것이라 생각하고 바로 신청을 해버렸습니다.

 

들어와 보니 단계별로 실력을 인증한다네??

심지어 블랙 배지는 이뻤습니다.

 

그렇게 이쁜 블랙 배지를 얻고, 기본기도 얻고 다 얻어가기 위해 시작한 항해였습니다.

 

막상 시작하니 토요일마다 그 주 과제를 발제해 주는데 와 토론하고, 고민하고, 설득하고, 발제 듣고 와 저는 천국인 줄 알았습니다.왜 이렇게 재밌는 건지 요즘 개발이 재미없다 생각했는데 와 너무 재밌는 거예요?

 

이렇게 많은 개발자가 모이니 너무 재밌게 커뮤니케이션을 하게 된다라는 걸 처음 느껴본 거 같아요.

 

너무너무 재밌게 하고, 과제를 수행하는데 시니어 코치진의 멘토링 시간이 다가왔습니다.

이때는 제가 한 일, 고민한 흔적, 질문 등을 피드백받을 수 있는 시간인데요, 코치님들도 정말 어쩜 이렇게 다 다르신지...

 

선호하는 개발 스타일이 다른 게 그렇게 헷갈릴 줄 몰랐지 뭐예요.

하지만 단하나는 공통 사항이 있었는데, 그것은 개발에 진심인 분들이라는 것이었습니다.

 

단순히 나 개발 잘해가 아니고 눈앞에서 같이 고민해 보며 문제를 처리하는 과정을 보여주시고, 우리는 그것을 보면서 다른 방법은 없을까 고민을 해보고 정말 순환이 참 잘되는 구조였다고 생각합니다.

 

이렇게 좋은 코치님들과 동료들과 같이하니 시간도 후딱 가버리고...

첫째, 중간, 마지막에 술도 거하게 마셔주고...( 전 안 마셨어요 )

 

이런 시간이 지나고 나니 저에게 많은 변화가 있는 것을 느꼈습니다.

 

주변에 드디어 같이 고민을 나누고 같이 개발을 할 수 있는 소중한 동료분들이 생긴 것이 가장 최우선 첫 빠따이고,

점점 지쳐가는 개발자 인생에 또다시 어마어마한 활력소를 넣어준 것이 두 번째이요,

내가 해온 개발 기술들에 대해 설명할 수 있는 기본기를 갖추게 된 게 세 번째입니다.

 

거짓말 아니냐고요? 해보고 말씀하시기 바랍니다.

 

처음에는 꽤 비싸다고 생각하기도 했어요 거의 200에 가까운 돈이 나가니... 근데 첫 번째 동료들이 생겼다 전 여기서 100 이상의 값어치를 했다고 생각합니다.

 

그리고 코치님들과의 인연!! 이 인연 또한 제 앞날에 빛이 되어줄 인연이지 않을까요??

 

마지막으로 사람들 앞에 자신을 노출할 수 있는 자신감!!!!

물론 아직도 많이 떨리죠... 하지만 이를 극복하고 사람들 앞에 나서는 사람으로 발전되도록 노력할 수 있는 용기가 생긴 거 같아요.

 

또한 감사하게도 10주간 과제 통과율이 90프로가 넘어 블랙 배지를 얻을 수 있었습니다.

블랙 뱃지

 

여기까지 보시고도 항해 플러스 백엔드 교육과정을 듣는 것에 있어 고민 중이신가요??

 

바로 들어봐라!! 하고 싶은 마음도 있기는 하지만 신중하게 선택하시기 바랍니다.

 

저의 인생 터닝포인트가 이 지점이었지만, 여러분들의 터닝 포인트는 다른 곳에 있을 수도 있고, 세상은 넓고 할 수 있는 것도 많기 때문이죠!

 

많은 고민을 하고 선택하시기를 바라면서 여기서 글을 마무리 짓도록 하겠습니다.

 

읽어주셔서 감사합니다.

 

 


항해를 선택하기로 한 당신!!

  • 결제페이지 > 할인 코드 > 수료생 할인 코드 입력 시 20만 원 할인 적용 

위와 같이 하면 할인된다 하니 코드 필요하시면 쓰세요.


추천인 코드: VmJf7w

728x90

'항해99' 카테고리의 다른 글

[5주차 회고] 5주차를 마무리하며  (4) 2024.07.20
[1주차] 1주차 마무리...  (0) 2024.06.27
[플러스 백엔드 5기] 시작에 앞서  (2) 2024.06.15
728x90

소프트웨어는 다양한 상황에서 장애가 발생할 수 있습니다. 이러한 장애에 효과적으로 대응하기 위해서는 다음 세 가지 원칙을 반드시 기억하고 실천해야 합니다:

  1. 예방 가능한 장애를 방지할 것
    사전에 발생할 수 있는 문제를 예방하는 것이 최우선입니다.
  2. 장애 발생 시 신속하게 전파하고 해결할 것
    문제가 발생했다면 즉시 공유하고 빠르게 해결해야 합니다.
  3. 재발 방지 대책을 마련할 것
    동일한 장애가 반복되지 않도록 원인을 분석하고 조치를 취해야 합니다.

장애 발생에 대비하기 위해서는 사전 테스트를 통해 발생 가능한 상황을 시뮬레이션할 필요가 있습니다. 이를 위해 부하 테스트를 활용할 수 있습니다.

부하 테스트는 소프트웨어가 특정 시나리오에서 안정적으로 동작하는지를 검증하는 과정으로, 주요 목적은 다음과 같습니다:

  1. 예상 TPS(Transaction Per Second) 검증
    예상되는 초당 트랜잭션 처리량을 시스템이 제대로 처리할 수 있는지 확인합니다.
  2. 응답 시간 검증
    평균, 중앙값, 최대 응답 시간을 측정하여 성능을 평가합니다.
  3. 동시성 이슈 검증
    다량의 트래픽이 유입될 때 동시 처리에 문제가 없는지 확인합니다.

부하 테스트는 테스트 시간과 트래픽 양에 따라 다음 네 가지 유형으로 구분됩니다:

  1. Load Test (부하 테스트)
    • 시스템이 예상 부하를 정상적으로 처리할 수 있는지 평가합니다.
    • 특정 부하를 일정 시간 동안 가해 이상 여부를 파악합니다.
    • 목표 부하를 설정하고, 이에 맞는 애플리케이션 배포 스펙을 고려할 수 있습니다.
  2. Endurance Test (내구성 테스트)
    • 시스템이 장기간 안정적으로 운영될 수 있는지 평가합니다.
    • 장기간 부하가 가해졌을 때 발생할 수 있는 문제를 파악합니다.
    • 메모리 누수, 느린 쿼리 등 장기 운영 시 나타날 수 있는 숨겨진 문제를 발견합니다.
  3. Stress Test (스트레스 테스트)
    • 시스템이 증가하는 부하를 얼마나 잘 처리할 수 있는지 평가합니다.
    • 부하가 점진적으로 증가할 때 발생하는 문제를 파악합니다.
    • 장기적인 운영 계획과 확장성을 검토할 수 있습니다.
  4. Peak Test (최고 부하 테스트)
    • 일시적으로 많은 부하가 가해졌을 때 시스템이 잘 처리하는지 평가합니다.
    • 임계 부하를 순간적으로 제공했을 때 정상적으로 동작하는지 파악합니다.
    • 선착순 이벤트 등에서 정상적인 서비스 제공 가능 여부를 평가할 수 있습니다.

이번 부하 테스트에서는 일반적으로 많이 사용하는 Load Test와 Peak Test를 통해 시스템의 성능을 검증해 보도록 하겠습니다.

부하 테스트

시나리오 작성

부하 테스트를 진행하기 전 우선 서비스 특성에 맞춰 테스트를 진행할 상황을 먼저 생각해보아야 합니다.

  • 트래픽이 많이 몰릴 것인가?
  • 서비스 입장 토큰을 사용하지 않고 호출하는 API인가?

위 내용들로 생각을 해보면 현재 서비스에서 제공하는 시나리오들을 확인을 해보겠습니다.

1. 대기열 참여 및 토큰 조회

대기열은 콘서트를 임시예약 및 결제를 하기 위해서는 필수적으로 거쳐야 하는 API입니다.

 

입장인원을 2000명으로 통제하고 있지만 대기열 진입하는 요청은 무제한으로 들어올 수 있기 때문에 대기열 참여, 대기순위 및 토큰 조회 API는 부하 테스트를 진행해야 한다 생각하였습니다.

Waitng Scenario Script ( Load Test )

import http from 'k6/http';
import {check, sleep} from 'k6';
import {randomIntBetween} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';

export let options = {
    scenarios: {
        waiting_scenario: {
            executor: 'ramping-vus',
            startVUs: 100,
            stages: [
                { duration: '10s', target: 200 },  
                { duration: '10s', target: 300 },  
                { duration: '10s', target: 400 },  
                { duration: '10s', target: 500 },  
                { duration: '10s', target: 0 }  
            ],
            exec: 'waiting_scenario'
        }
    }
};

export function waiting_scenario() {

    let userId = randomIntBetween(1, 100000);
    let seriesId = randomIntBetween(1, 10000);
    waiting(userId, seriesId);
	sleep(1);
    waitingCheck(userId, seriesId);
}

function waiting(userId, seriesId) {
        
    let res = http.post(
                    'http://localhost:8080/waiting-queue/join',
                    JSON.stringify({ userId, seriesId }),
                    {
                        headers: {'Content-Type': 'application/json'},
                    }
                )
    check(res, {
        'status was 200': (r) => r.status === 200
    })
}

function waitingCheck(userId, seriesId) {
    let res = http.get('http://localhost:8080/waiting-queue/waiting-count?userId=' + userId +'&seriesId=' + seriesId);
    check(res, {
        'status was 200': (r) => r.status === 200
    })
}

성능 측정 ( Load Test ) 

 

위 성능 측정 표를 확인해 보면 1건의 실패 케이스가 나온 것을 확인할 수 있습니다.

 

하지만 이는 성능 문제가 아닌 UserId와 SeriesId로 대기열에 입장하는데 해당 아이디들이 중복으로 들어갈 경우 실패하는 것이라 올바른 상황으로 볼 수 있습니다.

 

아래 stage로 Peak Test를 진행해 보았습니다.

 

극단적으로 5000 Target으로 올렸지만 성능을 확인해 보았을 때 정상적으로 처리하고 있는 것으로 보았습니다.

export let options = {
    scenarios: {
        waiting_scenario: {
            executor: 'ramping-vus',
            startVUs: 100,
            stages: [
                { duration: '10s', target: 2000 },  
                { duration: '10s', target: 3000 },  
                { duration: '10s', target: 4000 },  
                { duration: '10s', target: 5000 },  
                { duration: '10s', target: 0 }  
            ],
            exec: 'waiting_scenario'
        }
    }
};

성능 측정( Peak Test )

2. 콘서트 결제

콘서트 결제의 경우 앞에서 했던 대기열 입장 및 서비스 입장, 토큰 발급, 콘서트 시리즈 및 좌석 조회, 포인트 충전, 임시예약, 결제 순으로 시나리오가 이루어집니다.

Concert Payment Scenario Script ( Load Test ) 

import http from 'k6/http';
import {check, sleep} from 'k6';
import {randomIntBetween, randomItem} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';

let concertId = '0006740c-54d6-11ef-8c39-0242ac110002'

export let options = {
    scenarios: {
        payment_scenario: {
            executor: 'ramping-vus',
            startVUs: 100,
            stages: [
                { duration: '10s', target: 200 },  
                { duration: '10s', target: 300 },  
                { duration: '10s', target: 400 },  
                { duration: '10s', target: 500 }
            ],
            exec: 'payment_scenario'
        }
    }
};

export function payment_scenario() {

    let userId = randomIntBetween(1, 100000);
    let seriesId = getSeriesId();

    chargePoint(userId);
    waiting(userId, seriesId);
	sleep(1);
    let tokenId = getTokenId(userId, seriesId);

    let seat = getSeat(tokenId, seriesId)
    let temporaryReservationId = temporaryReservation(tokenId, userId, seriesId, seat.seatId)
}

// ConcertSeries조회
function getSeriesId() {
    let res = http.get('http://localhost:8080/concert/series/' + concertId);
    check(res, {
        'status was 200': (r) => r.status === 200
    })
    let concertSeriesList = res.json().seriesList;
    return concertSeriesList[0].seriesId;
}

// ConcertSeries조회
function getSeat(tokenId, seriesId) {
    let res = http.get('http://localhost:8080/concert/seat/' + seriesId, {
        headers: {
            tokenId
        }
    });
    check(res, {
        'status was 200': (r) => r.status === 200
    })
    let concertSeatList = res.json().seatList;
    return randomItem(concertSeatList);
}

//포인트 충전
function chargePoint(userId) {
        
    let res = http.patch(
                    'http://localhost:8080/point/charge',
                    JSON.stringify({ userId, amount: 10000 }),
                    {
                        headers: {'Content-Type': 'application/json'},
                    }
                )
    check(res, {
        'status was 200': (r) => r.status === 200
    })
}

// 대기열 진입
function waiting(userId, seriesId) {
        
    let res = http.post(
                    'http://localhost:8080/waiting-queue/join',
                    JSON.stringify({ userId, seriesId }),
                    {
                        headers: {'Content-Type': 'application/json'},
                    }
                )
    check(res, {
        'status was 200': (r) => r.status === 200
    })
}

// 토큰아이디 조회
function getTokenId(userId, seriesId) {
    let tokenId = '';
    while(tokenId === '') {
        let res = http.get('http://localhost:8080/waiting-queue/waiting-count?userId=' + userId +'&seriesId=' + seriesId);
        check(res, {
            'status was 200': (r) => r.status === 200
        })
        let waitingData = res.json();
        tokenId = waitingData.tokenId;
        if(tokenId === '') sleep(1)
    }
    
    return tokenId;
}

// 임시예약
function temporaryReservation(tokenId, userId, seriesId, seatId) {
        
    let res = http.post(
                    'http://localhost:8080/temporary-reservation',
                    JSON.stringify({ 
                        userId,
                        concertId,
                        seriesId,
                        seatId
                    }),
                    {
                        headers: {
                            'Content-Type': 'application/json',
                            tokenId
                        },
                    }
                )
    check(res, {
        'status was 200': (r) => r.status === 200
    })

    if(res.status === 200) {
        temporaryReservationPayment(tokenId, userId, temporaryReservationId);
    }
    return res.body;
}

// 임시예약 결제
function temporaryReservationPayment(tokenId, userId, temporaryReservationId) {
    let res = http.post(
                    'http://localhost:8080/payment',
                    JSON.stringify({ 
                        userId,
                        temporaryReservationId
                    }),
                    {
                        headers: {
                            'Content-Type': 'application/json',
                            tokenId
                        },
                    }
                )
    check(res, {
        'status was 200': (r) => r.status === 200
    })
    return res.body;
}

성능 측정( Load Test )

 

테스트 결과를 보면 14%의 실패 케이스가 존재합니다.

이때 테스트 중 발생할 수 있는 케이스는 아래와 같습니다.

  • seat가 이미 결제된 경우 - 동시성 처리로 인해 발생하는 오류 정상케이스
  • 임시예약이 이미 결제된 경우 - 중복 결제처리 방지로 인해 발생하는 오류 정상케이스

이리하여 제공 중인 API들의 성능에는 문제가 없다 생각합니다.

Peak Test를 진행하였을 때는 아래와 같습니다.

성능 측정( Peak Test )

 

2000, 3000, 4000, 5000이 각 10s동안 요청이 가도록 설정하였고, 성능적으로 생각보다 느린 것을 확인할 수 있습니다.

다만 이번에도 실패 케이스는 위에서 말한 동시성 처리로 인한 의도적 오류 발생이므로 문제는 없다 생각합니다.

 

시나리오를 설정하고 테스트를 진행해 보니 따로 문제가 발생하는 점은 발견하지 못하였습니다.

 

다만, 테스트 중 성느억 문제가 있다 판단되는 API들이 존재하여 이를 개선해보고자 합니다.


문제 되는 API 개선 사항

Concert

1. Concert 목록 조회

개인적으로 가장 많은 TPS가 몰리는 API라 생각합니다.

그 이유는 대기열에 입장하지 않더라도 조회 가능한 API이고, 해당 서비스에서 가장 먼저 조회되는 데이터이기 때문입니다.

 

즉, 모든 유저들은 해당 서비스를 사용하려면 해당 API를 호출해야 하는 상황인 것입니다.

그러므로 TPS 100 이상이 나와주는 것이 적절한 성능이라 생각합니다.

기존 로직으로 조회했을 때의 성능입니다.

 

현재 성능으로 보았을 때 TPS는 1m 30s동안 1550건의 트래픽을 처리하였으므로 1550/90 = 17.2... 약 17 TPS의 성능을 가지고 있습니다.

 

이는 매우 심각한 상황으로 개선의 필요성이 많이 있다고 볼 수 있습니다.

 

여기서 성능을 개선하는 방향으로 Paging을 적용해 보았습니다.

Paiging을 적용 후 TPS를 측정해 보았을 때 1m 동안 5900건의 트래픽을 처리하였으므로 5900/60 = 98.3... 약 98 TPS의 성능이 나왔습니다. 즉, 5배 이상 되는 성능이 향상된 것을 확인해 볼 수 있었습니다.

2. ConcertSeries 목록 조회

콘서트 시리즈목록 조회의 경우 concertId로 조회가 되므로 페이징과 캐시를 제거하였음에 더 아래와 같이 5657/90 = 62.85... 로써

63 TPS의 성능을 제공할 수 있습니다.

 

3. ConcertSeat 목록 조회

ConcertSeat목록의 경우 ConcertSeriesId에 의해 적은 양의 데이터가 조회되고, Seat조회부터는 토큰을 가지고 접근하는 API이기 때문에 부하 테스트를 실행하지 않아도 된다 판단하였습니다.

 

이와 같이 서버의 장애를 대응하는 것은 매우 중요한 사항이라 볼 수 있습니다.

 

현재 상황의 경우 특정 API의 조회 성능 문제가 있었는데, 조회가 느린 게 왜 장애인가?? 할 수 있지만 서비스 자체의 성능을 저하시키는 요인으로 페이징 처리를 하지 않아 데이터가 많아질수록 성능 저하가 발행하는 엄연히 장애라고 판단할 수 있습니다.

 

 


Repository

https://github.com/KrongDev/hhplus-concert

 

GitHub - KrongDev/hhplus-concert: 콘서트 예약 서비스입니다.

콘서트 예약 서비스입니다. Contribute to KrongDev/hhplus-concert development by creating an account on GitHub.

github.com

 

항해를 마무리하면서 최종 발표자료

https://docs.google.com/presentation/d/14Tpj91PwHInYeVTrdihKlyE1dNZWB2djiMpayMHz7aY/edit?usp=sharing

728x90

+ Recent posts