https://school.programmers.co.kr/learn/courses/30/lessons/250136

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

최근에 PCCP를 JAVA 언어로 응시했다가 큰 충격을 받아 풀기 시작했다.

 

문제 설명

문제는 간단하다. 

열 기준으로 막대기를 꽂았을 때 석유에 해당하는 블록을 만난다면 근처 오일들을 전부 합한 것만큼 시추하면된다. 

(근처 오일의 판단 기준은 상하좌우로 맟닿아있는 것이며 대각선은 해당되지 않는다.)

 

중요한 점은 효율성이 평가 항목에 포함되어있다는 점이다.

 

풀이 계획

DFS & Memorization

1. DFS를 이용해 특정 지점이 석유라면 근처를 전부 돌아다니며 해당 석유의 덩어리 크기를 구해준다. 

2. 구한 덩어리 크기만큼을 다시 돌면서 덩어리 안의 값들을 1이 아닌 석유 덩어리 크기로 저장해준다. 

3. 덩어리들마다 index값을 활용해 시추를 할 때 중복된 덩어리들은 신경 안 쓰고 내려갈 수 있도록 한다.

 

풀이 과정

import java.util.*;

class Solution {
    private static int N;
    private static int M;
    private static int[][] delta = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    
    public int solution(int[][] land) {
        int answer = 0;
        
        N = land.length;
        M = land[0].length;
        
        boolean[][] visited = new boolean[N][M];
        int[][] updateVisited = new int[N][M];
        int idx = 1;
        for (int r = 0; r < N; r++) {
            for (int c = 0; c < M; c++) {
                if (!visited[r][c] && land[r][c] == 1) {
                    dfsUpdate(land, updateVisited, r, c, dfs(land, visited, r, c), idx++);
                }
            }
        }
        
        for (int c = 0; c < M; c++) {
            boolean[] check = new boolean[idx];
            int sum = 0;
            for (int r = 0; r < N; r++){
                if (land[r][c] > 0 && !check[updateVisited[r][c]]) {
                    sum += land[r][c];
                    check[updateVisited[r][c]] = true;
                }
            }
            answer = Math.max(answer, sum);
        }
        
        return answer;
    }
    
    private static int dfs(int[][] land, boolean[][] visited, int r, int c) {
        Deque<Info> stack = new ArrayDeque<>();
        stack.push(new Info(r, c));
        visited[r][c] = true;
        int count = 1;
        while(!stack.isEmpty()){
            Info info = stack.pollLast();
            r = info.r;
            c = info.c;
            for (int i = 0; i < 4; i++) {
                int nr = r + delta[i][0];
                int nc = c + delta[i][1];

                if (isin(nr, nc) && !visited[nr][nc] && land[nr][nc] == 1) {
                    stack.push(new Info(nr, nc));
                    visited[nr][nc] = true;
                    count++;
                }

            }
        }
        
        return count;
    }
    
    private static void dfsUpdate(int[][] land, int[][] visited, int r, int c, int size, int idx) {
        land[r][c] = size;
        visited[r][c] = idx;
        Deque<Info> stack = new ArrayDeque<>();
        stack.push(new Info(r, c));
        
        while(!stack.isEmpty()){
            Info info = stack.pollLast();
            r = info.r;
            c = info.c;
            for (int i = 0; i < 4; i++) {
                int nr = r + delta[i][0];
                int nc = c + delta[i][1];

                if (isin(nr, nc) && visited[nr][nc] == 0 && land[nr][nc] != 0) {
                    land[nr][nc] = size;
                    visited[nr][nc] = idx;
                    stack.push(new Info(nr, nc));
                }
            }
        }
    }
    
    private static boolean isin(int r, int c) {
        return r >= 0 && r < N && c >= 0 && c < M;
    }
    
    private static class Info{
        int r;
        int c;
        
        public Info(int r, int c) {
            this.r = r;
            this.c = c;
        }
    }
}

 

주석이 없어 일일히 설명하자면

DFS 문제 풀이를 위한 방향을 설정할 delta, 배열 크기를 담을 N, M, 그리고 배열 밖으로 나가 ArrayIndexOutOfBound를 보지 않게 해줄 isIn 함수를 작성해준다. 

이후, 계획을 세웠던 것처럼 전체 배열을 돌면서 각 지점에서 dfs를 두 번 들어가준다. 한 번은 전체 덩어리 크기 탐지용, 한 번은 탐지된 크기를 바탕으로 업데이트 용도.

그것만 마무리 하면 열마다 시추를 꽂아보면서 확인하면 된다. 

 

갔다온지 오래 되었지만 바쁘고 귀찮다는 핑계로 미뤄두다가 다시 작성하는 호주 여행 후기

포트스테판 모래썰매는 줌줌투어 혹은 노랑풍선 사이트에서 예약을 진행했습니다. 

(투어는 돌핀크루즈 -> 포트스테판 -> 와인 시음 순서)

내가 예약 안하고 시간도 오래 되서 기억 안 나니 그냥 이런 투어구나만 보세요.

우선, 얼마나 오래 전인지를 이야기하기 위해 보통 투어는 6시에서 7시 사이에는 버스에 타야해서 일찍 나가야한다.

그리고 그 날은 아이폰 16 1차 발매날이었다.  

호주 시드니 애플 매장

아침부터 버스를 타고 멀리멀리 이동하면 Nelson Bay라는 곳에 도착한다.

이 곳에서 크루즈를 탈 수 있는데 여러 여행사에서 와서 자리가 굉장히 빡빡하다. 

운이 좋게도 우리는 가장 먼저 도착해 좋은 자리를 선점할 수가 있었다.

Nelson Bay Dolphin Cruise

당연히 허름한 왼쪽 배일줄 알고 그 배만 중점으로 열심히 찍었지만 놀랍게도 오른쪽 배를 탑승했다. 

뒤에 그물망 위에서 바닷물에 몸을 담구고 놀 수 있는 공간도 있었는데 호주 기준 봄이라 아직은 추워 포기했다.

나가다보면 돌고래를 볼 수 있는 곳에서 정차하고 볼 수 있는데 미리 자리를 잡지 않으면 잘 보이지도 않고 잘 나오지도 않아 생각보다 꼼꼼한 사람들이 가서 미리 대기하면 좋은 투어였던 것 같다.

크루즈에서 내리면 점심 식사를 진행하는데 만원 정도를 더 내고 비빔밥을 먹을 수 있다

각자 식사도 가능하지만 시간이 촉박해 나는 비빔밥을 먹는 걸 추천한다.

밥을 먹고 조금 이동하면 포트스테판에 도착할 수 있는데 진짜 사막이다.

Port Stephens

스타크래프트에서 유닛 드랍한 것과 같이 엄청나게 많은 사람들이 사막에서 샌드보드를 들고 걸어다닌다.

한국의 눈썰매장처럼 보드를 들고 직접 걸어올라가야하기 때문에 체력이 약한 사람은 모래 언덕을 한 두번 오르면 체력의 한계로 더 이상 탈 수 없다는 현실을 마주할 수 있다.

 

마지막으로, 시음회에 가면 사각형 구조로 된 길을 걸으며 바에서 자그마한 잔 한잔을 들고 맛 보면서 기차놀이를 하면 다시 바에 가서 한 잔 더 시음을 하는 식으로 진행이 되었다. 진짜 한 모금 마실 분량만 준다.

와인 시음회

이 날은 전날 피로도가 극한이었는데 오늘 하루종일 돌아다니느랴 피곤해서 사진을 많이 찍지는 못했다. 

호주 지역 맥주

마지막으로 호주 liquor shop에서 주인 아저씨께 추천 받은 맥주 중 가장 맛있었던 맥주를 추천하며 끝

서론

본 포스팅은 MAC os 기준으로 작성되었습니다.

 

문득 Intellij 딸깍 실행이 아니라 터미널에서 프로젝트를 빌드하고 실행시켜보고 싶다는 생각이 들었다.

CORS 정책을 이것저것 실습해보려고 과거에 했던 Vue.js 프론트엔드 프로젝트와 Spring 프로젝트를 꺼냈는데 프론트엔드는 익숙하게 npm run dev를 치고 있었지만 정작 백엔드는 터미널에서 실행해본 적도 없었다는걸 깨달았다. 

그래서 심심풀이로 시작했다.

 

Maven?

우선, maven이 무엇이냐하면

Apache Maven 공식 홈페이지

maven은 소프트웨어 프로젝트 관리 도구로 프로젝트 빌드, 패키징, 기타 관리 등을 지원해준다.

설치 방법은 brew를 사용해서 진행했다. 설치하는데 시간은 1분 넘게 걸렸던 것 같다.

% brew install mvn
% mvn -v

maven version 확인

 

본론

% mvn clean install

이렇게 쉬운걸 왜 적어놓냐면 당연히 한 번에 되지 않아서이다. 

발생 에러

그러면 이 에러는 왜 발생한걸까?

 

1. 자바 버전 확인

첫 번째로 의심해본 것은 자바 버전 문제이다. maven에서 설치된 java는 23 버전인데 pom.xml에는 17버전을 사용하는 것으로 작성되어있었다. 

 

++ 자바 버전 바꾸는 것도 꽤나 귀찮아서 정리해두려한다. 

zsh 명령어 +  양식이다.

java --version
/usr/libexec/java_home -V
nano ~/.zshrc
source ~/.zshrc
export JAVA_HOME=$(/usr/libexec/java_home -v 23.0.1)
export PATH=$JAVA_HOME/bin:$PATH

 

2. lombok 의존성 확인

자바 버전을 바꿔줬는데도 계속해서 오류가 발생했다. 

프로젝트 코드를 보니 lombok 어노테이션을 사용하는 DTO 객체들이 만들어지지 않아 오류가 발생하는 듯 했다.

그래서 Lombok 의존성을 pom.xml에 추가해주었더니 해당 부분은 해결이 완료되었다. 

mvn compile 성공

삽질 끝에 컴파일까지는 성공했다. 

하지만 mvn lifecycle에 따르면 default lifecycle을 위해 해야 할 일은 아직 남아있다.

mvn repository default lifecycle

나머지는 문제 없이 잘 지나갔고 마지막으로 아래 명령어만 입력한다면 

mvn spring-boot:run

로컬 어플리케이션 실행 성공

짜잔 아무튼 성공했다.

 

결론은 그냥 IDE 쓰세요. 백수니까 해본거지 좋은 거 냅두고 돌아가는 선택을 하지마세요.

'백엔드' 카테고리의 다른 글

[Spring] Spring Security 어떻게 작동하는가  (3) 2024.12.17

서론

백엔드 프로젝트에 참여할 때마다 Spring Security를 적용하였지만 한 번도 동작 과정에 대해 공부해 본 적이 없어 이번 기회에 구경해봤다. 

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
출처: https://spring.io/projects/spring-security#overview


우선,  Spring Security가 무엇인지 쉽게 얘기하면 Java 어플리케이션에 대해 인증/인가를 제공해주는 강력한 framework라고 자신들을 소개하고 있다. 보안을 위해 최소한 나무 울타리 정도는 치는 느낌이라고 생각하면 될 것 같다.

 

Spring 3.4.0 버전에 Spring Security는 6.4.2 버전이 가장 최신 버전인 시점에 구경하면서 글을 작성했다.

Security를 dependency에 추가해준 뒤 아무것도 하지 않고 Run을 하면 localhost:8080 치면 아래와 같은 화면을 볼 수가 있다. 

Spring Security 적용 초기 모습


이 글에서 중점으로 다룰 내용은 '왜' 이런 화면이 나오게 되는 것인가 + ID, Password가 '어떻게' 제공되는가이다.

 

본론

ID, Password 생성은?

첫 째로, User 생성은 SecurityProperties 클래스에 들어가면 확인할 수 있다. 

아래와 같이 SecurityProperties는 Default User를 만들어내는 것을 볼 수 있는데 User 클래스를 들여다보면 name은 user, password는 랜덤한 UUID를 제공하는 것을 알 수 있다. 

여기까지 보면 어떻게 제공하는지에 대한 궁금증은 해결된다. 

	private final Filter filter = new Filter();

	private final User user = new User();

	public User getUser() {
		return this.user;
	}

	public Filter getFilter() {
		return this.filter;
	}

	public static class Filter {

		/**
		 * Security filter chain order for Servlet-based web applications.
		 */
		private int order = DEFAULT_FILTER_ORDER;

		/**
		 * Security filter chain dispatcher types for Servlet-based web applications.
		 */
		private Set<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);

		public int getOrder() {
			return this.order;
		}

		public void setOrder(int order) {
			this.order = order;
		}

		public Set<DispatcherType> getDispatcherTypes() {
			return this.dispatcherTypes;
		}

		public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) {
			this.dispatcherTypes = dispatcherTypes;
		}

	}

	public static class User {

		/**
		 * Default user name.
		 */
		private String name = "user";

		/**
		 * Password for the default user name.
		 */
		private String password = UUID.randomUUID().toString();

		/**
		 * Granted roles for the default user name.
		 */
		private List<String> roles = new ArrayList<>();

		private boolean passwordGenerated = true;

		public String getName() {
			return this.name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			if (!StringUtils.hasLength(password)) {
				return;
			}
			this.passwordGenerated = false;
			this.password = password;
		}

 

SecurityConfig

다음은 화면이 어떻게 나오는가에 대한 이야기로 넘어가보자.

보통 Spring Security를 프로젝트에 적용하는 사람들이라면 별도의 SecurityConfig 파일을 만든 후 아래와 같이 작성해서 프로젝트에 적용할 것이다.

SecurityConfig.java

물론 커스터마이징을 위해서 필요한 작업이고 블로그들은 비슷하게 작성해서 적용하지만(참고로 위 코드처럼 한다면 해당 화면은 나오지 않는다) 이걸 추가하지 않아도 default로 어디서 동작하는지를 알아보고싶었다.

결론적으로 SpringBootWebSecurityConfiguration 클래스 파일을 가보면 Default 클래스로 Method를 확인할 수 있다.

SpringBootWebSecurityConfiguration.java

여담으로, 주석을 보면 User가 따로 SecurityFilterChain Bean을 생성하면 해당 Method는 실행되지 않는다라고 적혀있다. 

 

SecurityFilterChain은 HttpSecurity 클래스에 의해 만들어지며 HttpSecurity 클래스 내 주석을 확인해보면 http 요청에 대해 이것저것 확인하는 역할을 수행하는 이름 그대로의 역할을 맡고 있는 것을 확인할 수 있다. 

HttpSecurity.java

궁금증은 해결되었다. authorizeHttpRequests는 들어오는 Request에 대해 인증 작업을 수행하겠다는 것을 의미하고 formLogin의 경우는 login.jsp 파일을 제공해서 브라우저 내에서 login form을 보여주어 사용자가 로그인을 수행할 수 있도록 만들어준다고 적혀있다. 

근데 httpBasic의 경우 없어도 그만 있어도 그만인 느낌이다. 

Configures HTTP Basic authentication.

 

Class 내 주석도 이게 전부이다. 

GPT-4의 말에 따르면 없어도 된다고 한다. 

GPT-4의 의견

그래도 궁금하니 FormLogin을 없애고 실험해보았다. 

결과는 아래와 같이 굉장히 밋밋한 페이지를 확인할 수 있었다. 

FormLogin 제거

WebSecurity

마지막으로 들어온 김에 조금만 더 구경해보자.

HttpSecurity는 Http 요청에 대해 도맡아서 처리를 하는 것이고 SecurityFilterChain을 만들어준다. 

SecurityFilterChain을 활용하는 것은 WebSecurity 클래스로 이동하면 알 수가 있다고 한다.

WebSecurity.java

들어가면 친절한 개발자들이 DelegatingFilterProxy에 SpringSecurityFilterChain을 만들어준다는 것을 명시해주어 더 이상 파지않아도 된다고 얘기해준다. 

DelegatingFilterProxy에 대해 모르는 사람들을 위해 짧게 설명하자면,
Spring Security 공식 Docs 출처
Filter에서 Spring Container 내 Bean에 접근할 수 있도록 만들어주는 녀석이라는 것으로 알고있다. 

 

'백엔드' 카테고리의 다른 글

[Spring] Maven Project 터미널 빌드  (0) 2024.12.23

아침 일찍부터 시드니 대학교에 방문했는데 관광 시간은 대략 2시간 정도면 충분히 둘러보고도 시간이 남는다. 
우리는 426번 버스를 타고 Martin place에서 한 번에 시드니 대학교까지 이동했고 내리자마자 대학생들을 따라 들어왔다.

시드니 대학교의 건물들은 되게 고풍스럽다는 느낌이 든다. 물론 날씨도 한 몫 했겠지만 이런 곳에서 공부하면 어떤 느낌일까 하고 찾아봤더니 무려 세계 20위권 내 대학에 포함되는 곳이었다. 
(어릴 때 오고싶다고 생각해도 오기는 힘들었을 것 같다. 물론 운동장 보고 놀 생각부터 했으니 갔어도 글렀을 것 같다.)

기숙사 건물(추정)

 
해당 건물은 내 짧은 영어 지식으로 들었을 때 기숙사라고 하는 것 같았다.
굉장히 넓고 뭔가 우리나라 대학교 기숙사랑은 느낌이 굉장히 다르다. 앞에서 토스트와 커피를 파는 작은 상점이 있는걸로 봐서는 기숙사가 맞을 것 같다.
그리고 사진에서도 볼 수 있듯 오늘도 날씨는 굉장했다. 

호주 호그와트

호그와트 촬영지랑 비슷해서 이름이 그렇게 불리는건지 모르겠지만 진짜 호그와트 정원 장면이랑 똑같이 생긴 장소였다. 
솔직히 호그와트 촬영지가 맞다고 해도 믿었겠지만 해리포터는 영국에서 찍었지않을까란 생각에 귀찮아서 사실 확인은 해보지 않았다.
(아무나 아는 분 있다면 댓글로 알려주세요. ^^7)
뭐 아무튼 시드니 대학교 내부는 돌아다니면 진짜 이뻐서 볼거리도 많고, 학생들 구경하는 재미도 쏠쏠하다. 
하지만 단호하게 2시간 이상의 투어는 어려울 것 같다. 
그렇게 나온 후 시드니 대학교 갔을 때 블로그 피셜 필수 코스라는 Campos Coffee를 조금만 걸으면 갈 수 있다. 
정문 기준으로 대략 10분 정도 걸었던 것 같다. 

Campos Coffee

블로그에 후기가 하도 많길래 커피를 좋아하는 마음에 가봤는데 이건 뭐 한국 커피숍과 다를 바가 없다. 맛은 있다. 다만 고소한 커피 좋아하면 별로일거라고 예상한다.
이미 안에 있던 사람도, 함께 들어간 사람도, 나중에 오는 사람도 모두 한국인이었다.
역시 블로그에 나오는 곳들은 다들 찾아보고 오는 듯 하다. 
여기가 본점이라는 것 같지만 사실 돌아다니다보면 Waterloo에도 있고 CityHall 쪽 China Town 근처에도 지점처럼 조그마하게 있다.
시간이 없거나 귀찮은 사람들은 거기서 원두만 사도 나쁘지 않을 것 같다. (물론 난 매번 다 마셨고 원두는 해당 지점에서 샀다.)
원두만 살 사람들을 위해 보여주면 내부는 아래와 같이 바 테이블이 있고 안 쪽에 커피 테이블 5개 정도?있다. 2층은 화장실만 있는 듯 했다. 
원두 종류가 많아서 추천을 부탁드렸는데 취향 껏 얘기하면 직원분이 친절하게 추천해주시니 겁 먹지말고 추천을 부탁드리도록 하자!

Campos Coffee 내부

그렇게 나와서 걷다보니 Victoria Park에 도착했고 한 바퀴 돌면서 구경했다. 
여기는 시드니 대학교 뒤편이라 그런지 굉장히 많은 청춘들이 잔디에 누워 즐기고있었다. 
팁은 공원 내에 짚라인이 길게 있는데 아무도 타지 않기 때문에 환장하는 사람들은 꼭 즐기도록 하자. 
문득 공원을 보니 바다가 보고싶어졌고 가장 가까운 바다를 볼 수 있는 Pyrmont로 걸어가기로 했다. 
열심히 열심히 걸어가며 Wentworth Park를 지나가다가 Seafood Market을 발견했다.

Seafood Market

배고프기도 하고 구경할 겸 들어갔는데 굉장히 많은 사람들이 오는 걸로 봐서 유명한 곳이었던 것 같다. 
구경도 하고 가격 비교도 해보면서 돌아다녀보니 이름은 기억나지 않지만 가게 한 곳이 가장 내 취향에 맞았다. 
(혹시나해서 남기면 복도를 T자형으로 생각했을 때 꺾이는 위치에 있는 가게이다.)

Seafood Market 점심

사실 새우꼬치랑 문어가 탐나서 들어갔지만 연어를 사랑해서 지나칠 수 없었고 연어 스테이크도 샀다.
연어 스테이크 사면 면과 밥 중 선택이 가능한데 면은 어떨지 모르지만 새우꼬치와 문어가 짜기 때문에 밥이 신의 한수였다. 
참고로 내부는 우리나라로 치면 가락시장이나 노량진에 회를 사서 양념집에 가지 않고 바로 앞에서 노상으로 먹는다는 느낌이다.
밖은 부둣가에서 경치를 보며 먹을 수 있다. 

Seafood Market Outside Table

밥도 먹었겠다 우리는 또 걸었고 Waterfront Park에서 산책을 즐겼다. 여기는 부자동네인지 사람들은 없고 그냥 조그마한 공원이었다. 
반대편에 조선소가 보이는 뷰에 좌측에는 다리도 있고 여유가 있으면 가서 한적한 동네를 즐겨보는 것도 좋을 것 같다.
계속 걸어서 Pirrama Park도 가보고 Ballaarat Park도 가봤다. 그냥 공원이어서 그냥 쭉 돌면서 한적하게 돌아다녔던 것 같다. 
특이한 점은 걷다보니 점심에 맥북을 들고 야외에서 미팅을 하는 사람들이 많았고 대체 저 사람들은 어떤 회사의 개발자일까라는 생각에 건물을 구경해보니 Google이었다. Google Sydney를 보고싶다면 Ballaarat Park쪽으로 구경을 가보면 된다.

Google Sydney 도촬

뭐 아무튼 Sydney 대학부터 Pyrmont까지 잘 즐기고 Star Station에서 기차를 타고 돌아왔다. 
확실하지는 않지만 Star Station은 카지노인 것 같다.
(참고로 이렇게 돌아다니면서 허약한 나의 몸이 버티지 못해 Star Station 화장실에서 세수 중 코피가 터지고 말았다..)
아래는 더 이상 걷지 못해 기차를 타고 환승을 하면서 봤던 CityHall Station이다. 

CityHall Station

아무튼 2일차는 열심히 걸었다. 

Intro

안정 해시는 수평적 확장을 고려할 때 각 서버에 균등하게 데이터를 나누기 위해 사용하는 방법.

해싱(Hashing)이란 무엇인가?

해싱이란 다양한 길이의 입력값들에 대해서 고정된 길이의 출력값을 생성해주는 과정을 뜻한다. 이 출력값을 우리는 해시값(hash value)라고 부른다. 그리고 이 과정을 수행하기 위해 진행하는 과정에 쓰이는 것을 해시 함수(hash function)이라고 한다.

원인

해시 키 재배치(refresh) 문제

- 총 4개의 서버

해시 서버
key0 18358617 1
key1 26143584 0
key2 18131146 2
key3 34085809 1
key4 35863496 0
key5 27581703 3
key6 38164978 2

 

- 총 3개의 서버(1개 서버 다운)

해시 서버
key0 18358617 0
key1 26143584 0
key2 18131146 1
key3 34085809 2
key4 35863496 1
key5 27581703 0
key6 38164978 1
  • 위 예제처럼 해시 값을 서버의 개수로 모듈러 연산을 진행하는 해시 함수를 사용해 키가 저장된 서버의 위치를 특정 짓는다고 가정해보자.
  • 그러면 1개의 서버가 다운될 경우 모듈러 연산을 하는 기준이 4개에서 3개로 변경되면서 각 키에 해당하는 서버 Index는 모두 꼬이게 된다.
  • 결론은 대규모 cache miss가 발생하게 되고 특정 서버에 몰리게 되면 과부하가 걸리며 서비스는 혼돈에 빠질 것이다..

해결 방안

안정 해시(Consistent Hash)

  • 작동 과정
    1. 해시 함수의 결과값에 대한 저장 공간 범위를 정한다. 이를 해시 공간(hash space)라 부른다.
    2. 이 공간을 해시 링으로 만든다.
    3. 모든 서버들을 해싱하고 동일하게 해시 링에 매핑 시킨다.
    4. 모든 키들도 3번과 동일하게 작업한다.
    5. 키들은 시계 방향으로 서버를 탐색한다.

Hash ring

HashRing

회색 글씨로 쓰인 점들을 Server가 매핑된 곳, 파란색 글씨로 쓰인 점들을 key값이라고 가정했을 때,

해시 서버
key-0 15000 1
key-1 25678 2
key-2 29536 2
key-3 33357 2
key-4 36458 3
key-5 39600 3
key-6 58765 0

위 표와 같이 서버에 key 값이 분배되는 것을 확인할 수 있다.

HashRing Server Down

  • 위 그림과 차이점은 S3 서버가 다운되어 없어졌다는 점이다. - hash ring을 사용한 안정 해시에서는 S2부터 S0까지의 키 값들만 재배치를 하면 된다.
해시 서버
key-0 15000 1
key-1 25678 2
key-2 29536 2
key-3 33357 2
key-4 36458 0
key-5 39600 0
key-6 58765 0
  • key-4와 key-5만 재배치가 완료되자 정상적으로 배정이 된 것을 확인할 수 있다.

안정 해시 문제점과 해결 방안

문제점

  1. 파티션 크기의 균등 유지
    • 서버가 삭제되거나 추가됐을 때 키의 재분배를 빠르게 처리가 가능하다는 장점이 있지만 해시 링 내에서 각 서버에 할당되는 해시 공간의 크기가 일정하지 않다는 문제점이 발생한다.
    • 그림을 참고하면 S3 서버가 삭제되면서 S2와 S0 서버 사이의 해시 공간의 범위가 늘어난 것을 확인할 수 있다.
  2. 균등 분포의 문제점
    • 위 그림 예시처럼 하나의 서버에 많은 키값이 저장될 수 있는 것에 더해서 특정 서버에는 어떠한 키값도 저장되지 않는 문제점이 발생할 수도 있다.

해결 방안

  • 가상 노드(virtual node)의 활용
    • 가상 노드를 각 서버마다 n개 생성해서 각 서버를 가르키도록 설정하고 해시 링 내에 매핑 시킨다.
    • 키 값들은 시계 방향 탐색을 진행하며 가상 노드를 마주칠 경우 해당 서버에 키 값을 저장시킨다.
  • 특징
    • 가상 노드의 개수가 많아질 수록 키의 분포는 더욱 균등해진다.
    • 하지만, 가상 노드의 개수가 많아질 수록 해당 가상 노드의 데이터를 저장할 공간 또한 필요하므로 메모리 쪽 효율은 떨어질 수 밖에 없음에 주의하자.

마무리

안정 해시가 필요한 이유는 핫스팟(hotspot) 키 문제를 줄일 수 있다는 점이다. 데이터베이스 내 특정 샤드에 접근이 지나치게 많이 이루어진다면 과부하가 발생할 수 밖에 없기 때문에 해당 문제점을 해결하는데 탁월하다.

코로나가 끝난 이후 첫 해외여행으로 다녀온 호주 포스팅

총 11박 13일 여행으로 시드니에서만 있었습니다~~

마티나 라운지

인천 공항에서 체크인을 한 이후 이용할 수 있는 라운지들 중 마티나 라운지를 다녀왔다.

'신한 트레블 체크카드' 사용자 기준 연 2회 공항 라운지를 무료로 이용할 수 있고 동반인 1인에 한하여 30% 정도의 할인을 받을 수 있다.

솔직하게 말한다면 내 돈 주고 들어가서 먹는 사람들은 진짜 돈이 많은 사람들이겠구나 싶다. 

시드니 행 비행기

여행은 왕복 아시아나 항공을 이용하였으며 개인적으로 10시간 비행은 저가 항공으로는 버티지 못할 것이라는 확신을 얻을 수 있었다. 

인천 - 시드니는 저녁 비행기밖에 없었고 시드니 - 인천은 오전 비행기밖에 없었다.

(기내식은 두 번 씩 제공이 되었다. 단, 오전 비행기를 타고 귀국할 때는 중간에 자그마한 샌드위치를 간식으로 주셨다.)

 

호주는 규제가 굉장히 심하다는 글들을 많이 봐서 굉장히 쫄아서 입국했고 개인 약부터 흙이 묻은 신발을 따로 챙겨왔는지, 음식을 챙겨온 것이 있는지 전부 체크를 해야된다.

만약 가지고 있는 것이 있는데 체크를 안할 경우 벌금을 어마무시하게 받을 수 있기 때문에 솔직하게 적는 것이 좋다고 한다. 

(참고로 쫄아서 다 체크하고 입국 심사를 받으러갔는데 개인 약인지만 구두로 확인하고 심사는 바로 종료되었다.)

 

또한, 여권을 이용해서 셀프 체크인 시스템이 있는데 내려서 바로 있는 체크인 기계의 줄이 길다면 입국 면세점을 지나서 하나 더 있다는 점을 참고하도록 하자. 

시드니 공항 도착

공항 내에서 Train을 이용할 수 있고 해당 Train을 타고 환승하지 않아도 시내까지 한 번에 갈 수 있다. 

호주는 기본적으로 버스, Train, Tram, Metro을 이동수단으로 활용한다. 복잡해서 웬만하면 구글맵을 믿는걸 추천한다.

St.James역 입구

사진에서도 알 수 있듯이 호주의 날씨는 햇빛이 진짜 강하다.

여행 기간 동안 선크림을 한 통 넘게 쓸 정도로 발라줘야지 피부가 타지 않고 벗겨지지도 않을 수 있었다. 

 

첫 숙소는 시드니 St.James역 근처 풀러튼 호텔에서 7박을 묵었다.

아시아나를 이용한다면 오전 8시에 호주에 도착하기 때문에 호텔에 도착해도 체크인은 불가능하다. 

그래도 풀러튼 호텔은 짐을 맡아주었기 때문에 편하게 시간을 보낼 수 있었다.

(물론 신기해서 여기저기 돌아다녔는데 그냥 호텔 로비에서 쉬는게 좋은 것 같다..)

 

Martin place bank

편의점은 가격이 비싸긴 하지만 바닐라 코크(?)도 팔고 한국 과자들도 팔고 신기한 것들을 많이 볼 수 있다. 

다니다보면 피자나 타코, 샌드위치 집들이 꽤 있는데 일찍 닫으니까 먹으려면 일찍부터 먹어야되는 점을 주의해야한다.

안 그러면 저녁에 Coles행을 갈 수 밖에 없다..

아무튼 시드니 도시에 심취해서 3km정도 걸으니 Circular Quay에 도착할 수 있었다. 

크루즈 사진

많은 사람들이 러닝을 뛰거나 걸어다닌다. Circular Quay는 여행하면서 느꼈지만 돌아다니다보면 하루에 한 번은 방문하게 되는 곳 같다.

입구에 COPENHAGEN이라는 젤라또 집이 있는데 개인적으로 여기 젤라또가 그곳에서는 가장 맛있었다.

Opera House

날씨가 굉장히 좋아서 계속 걸었는데 오페라하우스 앞 쪽 언덕길로 해서 걷다보면 Royal Botanic Garden 입구가 나온다. 

여기는 힘드니까 지나치고 더 걷다보니 굉장한 건물을 마주할 수 있었다.

뉴사우스웨일스 주립도서관

2층에는 미술관도 있었고 1층은 통째로 도서관이었다.

규모가 놀라웠고 백수답게 취직을 준비하기 위해 노트북도 가져갔겠다 할 일을 해볼까 했지만 아쉽게도 호텔에 두고 나와서 포기했다 ^^7

그렇게 10시간 비행 후 5시간 가량을 돌아다니니 몸이 지쳤고 그냥 냅다 호텔 근처 멕시칸 레스토랑으로 들어갔다. 

(참고로 여행 계획을 세우지 않는 사람의 포스팅이기 때문에 즉흥적이고 몸으로 때우는 일들이 많습니다.ㅎㅎ)

Mejico Restaurant

손바닥보다 작은 타코 사이즈이고 사진 속 음식과 술 두 잔을 시키니 무려 80불이 나왔다..

호주 레스토랑이 한국의 1.5배 정도 가격이고 꽤 비싸다고 들었지만 무지함의 대가는 상당했던 것 같다.

The Fullerton Hotel 객실 내부

 

호텔 객실 내부는 굉장히 넓고 옷방까지 따로 있었으며 서비스가 너무 만족스러웠다. 어메니티는 Balmain 제품으로 제공이 되었다. 

네스프레소 캡슐 커피가 제공이 되는데 매일 마셔도 컵과 캡슐 모두 무료로 새로 제공해준다. 

보통 사람같다면 여기서 하루를 끝냈겠지만 여행을 왔으면 즐겨야된다는 생각에 조금 쉬고 다시 나갔다.

레고 월드

돌아다니다보니 레고월드가 2층짜리 건물로 크게 있길래 구경했는데 2층에서 보면 이런식으로 구조물을 만들어놨다. 

Habor bridge를 표현한 것 같다. 

Hyde park & Saint Mary's Cathedral

열심히 걸어서 St.James 역 위에 있는 Hydepark의 야경을 보러갔다.

물론 계획하고 보러간건 아니고 정처없이 걷다보니 불빛이 화려하고 이뻐서 보게됐다. 아이폰 13 Mini로 사진을 찍은게 아쉬운 느낌

Hungry Jack

걷다보니 배가 고파서 이번에는 호주의 버거킹 Hungry Jack에 들렀다. 

똑같은 브랜드지만 호주에서 먹으니 굉장히 고기가 맛있다는 느낌이 든달까? 만족스러웠다 아무튼.

그렇게 또 나와서 돌고돌아 City Hall 역까지 가서 야경을 즐겼다.

City Hall

11박을 호주에서 돌아다니면서 알아낸 건 첫날 돌아다닌 거리가 굉장히 넓다는 점이었고, 무릎이 아팠던 이유는 저 모든 곳을 돌아다니면서 대중교통을 하나도 타지 않았다는 점이었다.ㅎ

Fullerton Hotel Welcome Chocolate

지친 몸을 이끌고 돌아오니 초콜릿이 준비되어있었다는 사실을 마지막으로 자랑하며 시드니의 첫날이 이렇게 끝났다. 

어댑터 패턴

호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 하기 위한 패턴

어댑터 패턴 특징

예시

  • 예를 들어, 해외여행을 가면 우리가 가진 장비들은 220V로 충전을 해야하지만 국가 별로 지원하는 전압은 다를 수 있다.
  • 이 때, 어댑터를 사용해서 호환되지 않는 것들끼리도 사용할 수 있도록 하는 것과 동일한 방식이다.
  • 동일하게 프로그램을 보면 이미 사용하고 있는 써드파티 라이브러리가 있지만 새로운 기능을 추가하기 위해 새로운 라이브러리를 적용하고 싶다고 가정해보자.
  • 그냥 끼울 수 있다면 최고겠지만 그렇지 못하다면 우리는 노가다를 통해 어거지로 새로운 라이브러리 기능을 우겨넣어야한다.
  • 미련하게 우겨넣지 말고 지성인답게 해결할 수 있는 방법이 바로 어댑터 패턴의 적용이다!

해결책

  1. 어댑터는 기존에 있던 객체와 호환되는 인터페이스를 받는다.
  2. 해당 인터페이스를 사용해서 기존 객체에서 어댑터 내에 새로운 메서드를 가져온다.
  3. 호출을 수신하면 기존 객체에 어댑터가 변환해서 해당 객체에 전달해준다.

어댑터 패턴 종류

  • 어댑터 패턴은 2가지로 나뉜다. 객체와 클래스 어댑터.
  • 주요 차이는 객체를 새롭게 만드느냐 혹은 만들지 않고 상속을 활용해서 메소드를 구현하느냐이다.

    객체


// client 메소드
public class Client {
    public static void main(String[] args) {
        Adapter adapter = new Adapter(new Service());

        adapter.method("https://dev-qhyun.tistory.com/");
    }
}

// client와 작업하기 위해 클래스들이 따라야하는 프로토콜
public interface ClientInterface {
    void method(String data);
}

/*
 클라이언트와 서비스 양쪽에서 동작할 수 있는 코드
 새로운 메서드를 클라이언트에서 사용할 수 있도록 도와준다.
 */
public class Adapter implements ClientInterface{
    Service adaptee; // 사용하고 싶은 객체를 선언

    public Adapter(Service adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void method(String data) {
        adaptee.specialMethod(data);  // adapter를 통해 Service 호출
    }
}

// 기존 시스템을 어댑터를 이용해 들어가려는 쪽
public class Service {
    void specialMethod(String data){
        System.out.print("남의 블로그 홍보 " + data);
    }
}

클래스


public class Client {
    public static void main(String[] args) {
        Adapter adapter = new Adapter();  // 객체는 adapter만 새로 생성한다.

        adapter.method("https://dev-qhyun.tistory.com/");
    }
}

public class Adapter extends Service implements ClientInterface {
    @Override
    public void method(String data) {
        specialMethod(data);
    }
}
  • 클라이언트 호출 부분과 어댑터 코드의 변경만 있었을 뿐이다.
  • 책에서 해당 방식은 다중 상속이 가능한 프로그래밍 언어에서 사용이 가능하다고 설명이 되어있고 클래스로 구현되어있었으나, 비교를 위해서 interface를 활용해서 코드를 작성했다.
  • 객체 생성을 따로 하지 않아도 된다는 점이 메모리 상 약간의 이점이 있는 것 같다.

패턴 적용

시기

  • 기존 클래스를 활용하고싶지만 다른 코드들과 호환되지 않는 경우
  • 부모 클래스에 추가할 수 없는 여러 기존 자식 클래스를 재사용하기 위해 사용

장단점

  1. 단일 책임 원칙 준수
  2. 개방/폐쇄의 원칙 준수

추상 팩토리 패턴

추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있도록 하는 생성패턴이다.

추상 팩토리 패턴 예시 (삼성 & 애플)

문제

  • 비교하기 쉽게 삼성과 애플을 예로 들어보도록 하자
  • 해당 제품들은 노트북, 핸드폰, 패드, 워치, 이어폰 등과 같이 관련 제품들로 형성된 제품군이 존재한다.
  • 맥북 생태계라는 말이 존재하듯 새로운 개별 제품 객체를 생성했을 때, 이 객체들이 기존의 같은 패밀리 내에 있는 다른 제품 객제들과 일치하는 스타일을 가지도록 해야한다.
  • 아이폰을 쓰면서 버즈를 쓰거나, 갤럭시를 쓰면서 애플워치를 찬다면 굉장히 어지러울 것이기 대문이다.

해결책

  1. 개별적인 인터페이스를 명시적으로 선언하는 방법의 사용
  • 위와 같이 같은 객체의 변형을 구현하였다면, 다음은 추상 팩토리 패턴을 적용하는 것이다.
  1. 제품 패밀리 내의 모든 개별 제품들의 생성 메서드들을 목록화시키기
  • 위와 같이 추상 팩토리 인터페이스를 기반으로 별도의 팩토리 클래스를 생성하여 각 팩토리는 특정 종류의 제품을 반환하게만 한다.
  • 삼성 공장에서는 삼성 제품만, 애플 공장에서는 애플 제품만 생성할 수 있듯이 말이다.

주의할 점

  • 클라이언트 코드는 추상 인터페이스를 통해 팩토리들과 제품들 모두와 함께 작동할 수 있도록 해야한다.
  • 그래서 클라이언트 코드는 변형되지 않고 자유자재로 변경할 수 있어야한다.

예시 마무리

  1. 책을 기준으로 추상 제품들은 Watch interface로 개별 연관 제품들의 집합에 대한 인터페이스를 뜻한다.
  2. 구상 제품들은 AppleWatch와 GalaxyWatch와 같이 추상 제품들의 다양한 구현들이다. 각 추상 제품은 주어진 모든 변형에 구현 되어야 한다.
  3. 추상 팩토리 인터페이스는 가각의 추상 제품들을 생성하기 위한 메서드 집합으로 ElectricFactory가 이에 해당한다.
  4. 구상 팩토리들은 AppleFactory와 SamsungFactory가 해당하며 해당 구상 팩토리에 생성 매서드들은 추상 제품을 반환해야한다.(Watch를 반환하는 것 처럼)
    이렇게 설계한다면 클라이언트 코드가 팩토리에서 받은 제품의 특정 변형(구상 제품들)과 결합되지 않고 어떤 팩토리나 제품과 작업할 수 있는 것이다.

간단한 전체 코드

코드는 아무런 로직 없이 껍데기만 대충 만들어봤다.


// 추상 제품
public interface Watch {
    void display();
    void strap();
}

// 애플 워치
public class AppleWatch implements Watch{
  @Override
  public void display() {

  }

  @Override
  public void strap() {

  }
}

// 갤럭시 워치
public class GalaxyWatch implements Watch{
  @Override
  public void display() {

  }

  @Override
  public void strap() {

  }
}

// 추상 팩토리
public interface ElectricFactory {
  void createPhone();
  void createDesktop();
  Watch createWatch();
  void createEarphone();
  void createPad();
}

// 애플 팩토리
public class AppleFactory implements ElectricFactory{
  @Override
  public void createPhone() {

  }

  @Override
  public void createDesktop() {

  }

  @Override
  public Watch createWatch() {
    return new AppleWatch();
  }

  @Override
  public void createEarphone() {

  }

  @Override
  public void createPad() {

  }
}

// 삼성 팩토리
public class SamsungFactory implements ElectricFactory{
  @Override
  public void createPhone() {

  }

  @Override
  public void createDesktop() {

  }

  @Override
  public Watch createWatch() {
    return new GalaxyWatch();
  }

  @Override
  public void createEarphone() {

  }

  @Override
  public void createPad() {

  }
}

2일차 관광

오전 일찍부터 나와서 9시 반부터 케이블카를 탑승하러 이동했다.

여수케이블카 요금표

일반과 크리스탈의 차이는 바닥이 유리로 되어있어 아래를 볼 수 있는가 아닌가이다. 
4인 가족 기준 크리스탈케빈을 왕복으로 탑승했지만 96,000원이라는 어마무시한 가격을 낼 경험은 아니었다.
경관은 볼만해서 탈 생각이면 일반케빈을 타는게 나을 것 같다. (참고로 우리를 제외한 모든 사람이 일반 케빈밖에 타지 않는 것을 봤다..)

경관은 보다시피 유명한 해상케이블 답게 진짜 이쁘다. 
돌산쪽으로 넘어가서 구경하려했는데 날씨도 덥고 움직이기 귀찮아서 카페 잠시 들렀다가 다시 타고 돌아왔다.
놀랍게도 서울에서 줄 엄청 서서 겨우 그렸던 도토리 캐리커쳐가 있어서 바로 들어가서 4인 가족에 강아지까지 부탁드렸다. 

도토리 캐리커처

그리고 바로 사람이 많아지기 전에 게장집으로 이동

사람이 얼마나 많은지 본관, 별관, 대기실 등등 온세상이 명동게장이었던 골목

명품게장 2번세트

2번 세트를 시켰는데 구성이 알차고 꽃게로 주어진다. 돌게는 3번까지 리필이 가능하다는 점 참고하도록 하자.

모카힐 카페

디저트와 커피 배는 따로 있으니까 뷰가 좋고 맛있다는 리뷰를 봤던 카페까지 다녀왔다. 
가나슈는 혈관이 막히는듯했지만 막혀도 행복했을 것 같은 맛이었다.
 
배 터지게 먹은 이후 생각보다 여수 시내에서는 할 일이 없길래 그냥 호텔로 돌아와서 호캉스를 즐기기로했다. 
우리가 묵은 호텔은 여수 베네치아 호텔로 루프탑 풀을 보유 중이었다.

루프탑 풀

정확하게 금액이 어떻게 되는지 기억은 나지 않지만 입장료가 15,000원이었고 수영복 대여가 가능해서 빌렸는데 그게 7,000원정도 했던 것 같다. 아무튼 2인 기준으로 44,000원 금액을 내고 이용이 가능했다. (카드가 없어도 룸으로 달아놓으면 체크아웃 시 결제가 가능하기 때문에 진짜 몸만 올라가도 상관없다.)
신나게 놀고 조그마하게 준비되어있던 건식사우나도 이용하고 내려와서 쉬다보니 저녁이 되어 이제 먹부림을 시작하기 위해 움직였다.
 

첫날 가봤지만 막회밖에 남지않아 모듬회를 못 먹었던게 아쉬워서 다시 여수찬에 일찍 방문했고 먼바다모듬회를 주문했다. 
그리고 사장님께 추천을 받아 바다김밥을 포장하기 위해 움직였는데 가는 길에 "염전의 봄"이라는 제빵 명인이 굽는 소금빵 맛집을 발견하고 그것까지 구매를 했다. 
바다김밥은 그냥 추천을 받아서 갔는데 엄청 유명한 맛집이었는지 앞에 30팀이나 대기 중이라서 한참을 기다렸다. 

지느러미, 광어, 우럭, 참돔, 농어, 강도다리

다시 방문하길 잘했던 맛집으로 솔직히 다시 여수를 가도 이 집은 다시 갈 것이다.
 

3일차 관광

향일암 초입

셋째 날은 시간도 없기도해서 향일암 한 곳만 가는 것을 목표로 했다. 
안 쪽 도로가 폭이 엄청 좁고 주차 타워 한 곳에만 주차가 가능해서 사람이 많을 경우 바깥에 주차를 해놓고 걸어들어가거나 택시를 이용해야한다고 한다.
물론 우리는 평일이기 때문에 사람이 없어 향일암 바로 밑 주차장에 주차가 가능했다.
올라가보고싶어 일찍 움직여서 갔지만 입구에서 사진을 찍은 이후 발목 상태와 예정된 컨설팅 시간 전에 준비를 하기는 힘들 것 같은 경사에 포기하고 부모님만 올려보내드렸다ㅎ
굉장히 만족하셨던 걸로 보아 경관이 굉장히 이쁜 것으로 추정된다.

맥북자랑

혼자서 카페를 가서 컨설팅을 받는 것으로 이번 여행 관광은 끝이었다.
(집으로 오는 열차를 타기 전 바다김밥에 다시 방문해서 이번에는 모듬을 사서 돌아왔다. 여기도 맛집인정..)
 
아무튼 먹고 자기만 했던 원초적인 생활의 관광 여행은 이렇게 끝이 났다. 
다음에 또 가서 회 먹어야지 ^^7

+ Recent posts