https://softeer.ai/practice/6250

 

Softeer - 현대자동차그룹 SW인재확보플랫폼

 

softeer.ai

 

1차 코드 - 시간 초과 / 반복 중첩, count 등

n = int(input())
scores = []
total_score = [0] * n

for _ in range(3):
    cur = list(map(int, input().split()))
    scores.append(cur)
    for i in range(n):
        total_score[i] += cur[i]
scores.append(total_score)

for score in scores:
    result = []
    tmp = sorted(score, reverse=True)

    for s in score:
        count = 1
        for i in range(len(tmp)):
            if s < tmp[i]:
                count += 1
        result.append(count)
    print(*result)

 

2차 코드 - 통과 / Counter, dict, hash 활용

from collections import Counter

n = int(input())
scores = []
total_score = [0] * n

# 대회 결과 저장
for _ in range(3):
    cur = list(map(int, input().split()))
    scores.append(cur)
    for i in range(n):
        total_score[i] += cur[i]
scores.append(total_score)

# 솔루션
for score in scores:
    score_counter = Counter(score)
    score_set = list(set(score))
    score_set.sort(reverse=True)
    score_dic = dict()
    
    token = 1
    for s in score_set:
        score_dic[s] = token
        token += score_counter[s]
        
    result = []
    for i in score:
        result.append(score_dic[i])
    print(*result)

 

Key Point

- 문제에 나와있는대로,  각 사람마다 “나보다 점수가 큰 사람”의 수를 세어 1을 더한 것이 자신의 등수가 된다. 를 구현하면 풀리는 문제.

복잡하게 생각하고, 어렵게 구현할 필요가 없다.

Counter를 통해, 같은 점수의 개수를 구해주고, 있을 때 마다 순위가 1씩 밀리게 됨.

https://softeer.ai/practice/6255

 

Softeer - 현대자동차그룹 SW인재확보플랫폼

 

softeer.ai

message = input()
key = input()
maps = []
data = []

# 암호키 테이블 만들기 (가로축 데이터 맵)
for i in range(len(key)):
    if key[i] not in maps:
        maps.append(key[i])
            
for i in range(65, 92):
    if chr(i) not in maps and chr(i) != 'J':
        maps.append(chr(i))
maps = maps[:25]
tmp_maps = maps # 일차원 데이터 맵

for _ in range(5):
    data.append(maps[:5])
    maps = maps[5:]

# 세로축 데이터 맵
col_data = []
for i in range(5):
    tmp = ""
    for j in range(5):
        tmp += data[j][i]
    col_data.append(tmp)


# 메시지 나누기
token = 0
tmp_message = message


while True:
    flag = True
    for i in range(0, len(tmp_message), 2):
        m = tmp_message[i:i + 2]
        if len(m) == 2 and m[0] == m[1]:
            flag = False
            if m[0] != 'X':
                tmp_message = tmp_message[:i + 1] + 'X' + tmp_message[i + 1:]
            else:
                tmp_message = tmp_message[:i + 1] + 'Q' + tmp_message[i + 1:]
            break
    if flag:
        break
        
if len(tmp_message) % 2 != 0:
    tmp_message += 'X'
    
message = []
for i in range(len(tmp_message) // 2):
    message.append(tmp_message[:2])
    tmp_message = tmp_message[2:]


# 메시지 암호화
def row_check(token):
    for i in range(5):
        tmp = ""
        if token[0] in data[i] and token[1] in data[i]:
            l_idx, r_idx = data[i].index(token[0]), data[i].index(token[1])
            if l_idx == 4:
                tmp += data[i][0]
            else:
                tmp += data[i][l_idx + 1]
                
            if r_idx == 4:
                tmp += data[i][0]
            else:
                tmp += data[i][r_idx + 1]
            return tmp
                    

def col_check(token):
    for i in range(5):
        tmp = ""
        if token[0] in col_data[i] and token[1] in col_data[i]:
            l_idx, r_idx = col_data[i].index(token[0]), col_data[i].index(token[1])
            if l_idx == 4:
                tmp += col_data[i][0]
            else:
                tmp += col_data[i][l_idx + 1]
                
            if r_idx == 4:
                tmp += col_data[i][0]
            else:
                tmp += col_data[i][r_idx + 1]
            return tmp


def cross_check(token):
    l_idx = tmp_maps.index(token[0])
    r_idx = tmp_maps.index(token[1])
    l_i, l_j = l_idx // 5, l_idx % 5
    r_i, r_j = r_idx // 5, r_idx % 5
    return data[l_i][r_j] + data[r_i][l_j]


result = ""
for m in message:
    cur = row_check(m)
    if not cur:
        cur = col_check(m)
    if not cur:
        cur = cross_check(m)
    result += cur
print(result)

 

단순 구현문제이나, 2차원 배열의 인덱싱, "반복 구간에서 특정 변동 변수를 다루는 방법 (인덱스 에러 조치)"이 중요하여 lv3 인듯,

암호화할 문자를 2개씩 끊어서 저장하는 방식에서 시간 잡아먹음. 

https://softeer.ai/practice/6266

 

Softeer - 현대자동차그룹 SW인재확보플랫폼

 

softeer.ai

n, k = map(int, input().split())
room_list = dict()

for _ in range(n):
    room_list[input()] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]

for _ in range(k):
    name, start, end = input().split()
    for i in range(int(start), int(end)):
        room_list[name][i] = 0

name_list = list(room_list.keys())
name_list.sort()

for j in range(len(name_list)):
    name = name_list[j]
    print(f"Room {name}:")
    
    if sum(room_list[name]) == 0:
        print("Not available")
        
    else:
        result = []
        start = -1
        for i in range(9, 18):
            if room_list[name][i] == 1:
                if start == -1:
                    start = i
            elif room_list[name][i] == 0:
                if start != -1:
                    result.append(str(start).zfill(2) + "-" + str(i).zfill(2))
                    start = -1
        if start != -1:
            result.append(str(start).zfill(2) + "-" + str(i + 1))
        print(f"{len(result)} available:")
        print("\n".join(result))
    
    if j + 1 != len(name_list):
        print("-----")

 

key point:

- zfill 함수로 숫자 계수 맞춰줄 수 있다 (2, 4, 5 -> 02, 04, 05) 등

- "\n".join(리스트) 를 통해 리스트 내 요소들을 띄워쓰기로 표시할 수 있다.

 

그림으로 공부하는 마이크로서비스 구조 | 다루사와 히로유키 - 교보문고

그림으로 공부하는 마이크로서비스 구조 | 디지털 전환(DX) 실현을 위한 기초 기술 ‘마이크로서비스’의 핵심을 쉽고 빠르게 습득하자!이제는 마이크로서비스 방식으로 애플리케이션을 개발하

product.kyobobook.co.kr

 

5장, 컨테이너, 쿠버네티스, 서버리스


Key Point / 도커  & 쿠버네티스

- 도커(Docker)에서 설명하는 컨테이너에 대한 설명은 다음과 같다

컨테이너란 코드와 모든 의존 관계를 패키지화한 소프트웨어의 표준적 단위로, 애플리케이션이 실행되는 기존 컴퓨팅 환경은 물론 다른 컴퓨팅 환경에서도 신속하고 확실하게 실행되게 해준다.

- 즉 컨테이너랑 의존 관계를 포함하는 '패키지'의 일종으로, 다양한 OS에서 실행할 수 있게 해주는 것이다.

- 환경에 의존하지 않고 안정적으로 실행할 수 있고, 하나의 서버 리소스를 효율적으로 사용하며, 컨테이너 구성 파일을 배포 작업의 일부로 사용할 수 있다는 큰 장점이 존재한다.

- 리눅스 네임스페이스 (Linux Namespace) : 프로세스 간에 전역 시스템 리소스를 분리하는 커널의 기능, 시스템 리소스를 각각의 네임스페이스로 나누는 역할을 한다.

- 컨테이너 이미지 : 애플리케이션을 실행할 때 필요한 모든 것이 담긴 소프트웨어 패키지. 하지만 일반적인 패키지처럼 하나의 바이너리 파일로 존재하는 것이 아니다.

- 도커 이미지는 여러 개 읽기 전용 레이어로 구성되며, 개발 레이어는 재사용할 수 있다. 이미지 레이어와 스토리지 드라이버(overlay2 권장)로 실제 수정 작업과 레이어 관리가 진행된다.

- 도커 이미지는 Dockerfile 내에 이미지 작성 순서를 기록하는 것이 일반적

- 유니온 파일 시스템 (Union File System, UnionFS) : 다른 파일 시스템을 겹쳐서 하나의 파일 시스템으로 보여 주는 커널 기술, OverlayFs에 대한 스토리지 드라이버가 바로 Docker에서 권장하는 overlay2

- 컨테이너느 기본적으로 '머신 한 대'의 리소스를 효율적으로 관리하는 도구이다. MSA의 규모가 커지고 복잡해지면, 각 서비스를 구현하는 컨테이너의 수도 늘어난다. 언젠가 '머신 한 대'로 모든 컨테이너를 운영하기 어려운 지점이 오는 것이다. 이 경우 컨테이너 오케스트레이션 (Container Orchestration)을 통해 리소스를 효율적으로 관리할 수 있따.

- 컨테이너 오케스트레이션 (Container Orchestration) : 규모가 크고 동적인 황경에서의 컨테이너 생명 주기를 관리. 가용성, 이중화가 갖춰진 컨테이너 작성과 배포, 부하에 따른 스케일 아웃/인, 분산, 호스트(서버) 및 컨테이너의 상태를 확인할 수 있다.

- 컨테이너 오케스트레이션 도구로 쿠버네티스 (Kubernetes, k8s), 도커 스웜(Docker Swarm), 메소스(Mesos), 노매드(Nomad)가 있다. 현재 가장 많이 사용되는 것은 쿠버네티스이다.

- 쿠버네티스는 오픈소스 플랫폼으로 YAML을 통해 선언적 설정으로 복잡한 컨테이너 서비스의 배포를 쉽게 자동화할 수 있다. 또한 해당 생태계에서 제공되는 오픈소스나 사용 버전의 다양한 툴과 서비스를 활용할 수 있다.

- 쿠버네티스의 주요 기능 : 서비스 검색, 부하 분산, 저장소 오케스트레이션, 자동 롤아웃/롤백, 리소스 관리 및 일정 제어, 자가 복구, 시크릿과 설정 관리 (Password, Token, SSH ...)

- 쿠버네티스 아키텍처 : 제어 플레인(Control Plane) 컴포넌트와 실제 pod를 호스트하는 (워커) 노드 컴포넌트로 구성, 최소 배포 단위로 하나 이상의 컨테이너가 구성된다.

- 제어 플레인 컴포넌트 (Control Plane Component) : 클러스터 내 노드와 포드를 관리. 클라우드 벤더의 k8s 서비스를 사용하면 벤더 측에서 제어 플레인을 관리하므로 사용자는 이 기능을 인식할 필요가 없다.

- 노드 컴포넌트 (Node Component) : k8s 클러스터에는 적어도 하나의 노드가 존재하며, 모든 노드를 실행하고, 실행 중 포드를 유지하고, 실행 환경을 제공한다.

- k8s 클러스터와 클라이언트는 모두 쿠버네티스 API를 사용해 커뮤니케이션한다. 제어 플레인의 API 서버가 JSON 형식의 RESTFul API를 받아 클러스터와 클라이언트를 연결하는 단일 인터페이스 역할 수행

- Pod, Deployment, Service 등 다수의 API 객체 존재

- 포드 (Pod) : 쿠버네티스로 작성 및 관리할 수 있는 최소 배포 단위, 포드 내 컨테이너는 IP 주소와 저장소 볼륨을 공유하며, 보통 디플로이먼트나 잡처럼 별도의 API 객체가 간접적으로 생성한다. 원자성 처리로, 포드를 완전히 생성하거나 말거나 둘 중 하나로 어중간한 상태는 존재하지 않는다.

- 디플로이먼트 (Deployment) : k8s의 중요 역할 중 하나가 포드를 선언된 대로 적절하게 지속 운영하는 것. 이 때 필요 포드 수를 관리하는 것이 레플리카셋 (ReplicaSet) 이라는 API 객체. 디플로이먼트는 레플리카셋의 롤아웃을 관리하여 선언적 업데이트를 제공

- 서비스 : 일련의 포드를 네트워크 경유로 접속하게 공개하는 API

- 컨피그맵 : 타 API 객체가 사용할 때 필요한 설정을 저장하는 API, key-value 형식

- 컨테이너 오케스트레이션의 특징 : 인프라를 사용자가 관리하고, 특정 제공사에 종속되는 벤더 종속을 피하고, 이동성이나 재사용성을 높이고 싶은 경우 장점을 가지나, 보안 관리 및 인프라 운용이 필요하다는 단점이 있다.

Key Point / 서버리스

-  서버를 관리할 필요가 없는 애플리케이션을 구축해서 실행, 서버가 불필요한 것이 아닌 서버 구축, 유지, 관리, 업데이트, 확장, 용량 관리에 시간과 리소스를 투입할 필요가 없다는 의미. 개발자는 비즈니스 로직 작성에 집중할 수 있고, 운영 엔지니어는 더 중요한 일을 할 수 있다.

- 서버리스에선 로직 일부에 외부 서비스를 이용, 정적 플로와 동적 콘텐츠 생성이 이루어지고, 클라이언트를 이를 호출해 다양한 서비스 간 상호 작용을 조정할 수 있다.

- BaaS(Backend-as-a-Service) : 애플리케이션 핵심 기능의 일부를 외부 API 서비스로 교체하는 것, 이용자는 확장 및 운영을 고려하지 않고 이용할 수 있음, 전형적으로 SPA(Single Page Application)이나 Rich 클라이언트를 사용. BaaS로 구글 파이어베이스나 Auth0등이 있다.

- FaaS(Functions-as-a-Service) : 이벤트 주도 (event-driven) 컴퓨팅을 제공하는 서비스, 이벤트나 HTTP 요청에 의해 실행되는 함수를 사용하여 코드를 실행 및 관리. 작은 코드 단위를 배포하여 필요에 따라 개별 처리로 실행. 서버나 인프라를 관리할 필요가 없고 확장도 자동, AWS Lambda, Azure Functions, Google Cloud Functions 등이 잇다.

- 서버리스가 유효한 경우

    - 비동기, 병행 또는 독립된 작업 단위로 병렬화하기 쉬운 작업

    - 요구 빈도가 낮으면서 산발적인 작업

    - 확장 요건의 범위가 넓고, 예측 불가능한 작업

    - 스테이트리스(Stateless)이고, 생명 주기가 짧은 작업

    - 비즈니스 요건이 빈번하게 변화하고, 개발도 그에 맞추어 빠르게 진행해야 하는 작업

- 서버리스의 장점 : 비용 절감, 쉬운 유지 및 관리

- 서버리스의 단점 : 상태 관리 번잡, 지연 시간 발생, 로컬 테스트 어려움, 콜드 스타트, 툴/실행 환경 제약, 벤더 종속

- 서버리스의 특징 : 인프라 관리나 범용 서비스를 플랫폼에 위임하여 이벤트 주도 함수에 집중 개발할 수 있음, 배포 후에도 코드가 실제 실행되는 시간만 과됨, 그러나 현재 플랫폼이나 커뮤니티가 미성숙하며 기존 서비스를 마이그레이션 하기엔 난이도가 높다는 단점이 있다.

 

6장, 서비스 메시


Key Point

- 다수의 서비스로 구성되는 MSA는 초분산 시스템이므로 서비스를 잘 관리하지 않는다면 필요한 리소스가 너무 커져 감당할 수 없을 수도 있다. 이 때 사용되는 방법이 바로 서비스 메시 (Service Mash) 이다.

- MSA는 컨테이너나 서버리스처럼 런타임상에서 실행되는 경우가 많고, 플랫폼상에서는 서비스 배포나 재시작 떄마다 IP 주소가 수시로 변경된다. 따라서 호출단에서는 항상 호출을 원하는 서비스 IP 주소를 파악할 수 있어야 한다.

- IP 주소 뿐만 아니라, 다양한 버전의 동시 운영시와 같이 목표 서비스의 상세 정보에 대해 파악할 수 있어야 한다.

- 관찰 가능성 (Obeservability) : 서비스 출력을 통해 내부 상태를 파악할 수 있는 것으로, 서비스가 정상적으로 동작하는지 파악하기 위해 중요한 역할을 한다.

- 장애의 분리 (Fault Isolation) : 특정 서비스에서 발생한 장애가 시스템 전체에 영향을 끼치지 않고, 특정 처리로 영향이 제한되도록 하는 것

서비스 메시 (Service Mash) : 서비스 간 상호 통신이 그물 형태 (Mash)로 연결되어 있어 MSA간 통신을 관리하는 구조

- 서비스 간 모든 통신이 통과하는 계층으로 서비스에 부속되는 경량 프록시를 둔다. 이 프록시는 서비스 검색이나 트래픽 제어, 관찰 가능성, 장애의 분리, 보안 등을 관리하는 기능을 한다.

- 서비스 메시는 제어 플레인과 데이터 플레인이라는 두 가지 컴포넌트로 구성된다.

- 제어 플레인 (control plane) : 서비스 메시를 관리, 서비스 검색 등 관리에 필요한 정보를 보관하거나 구성 변경 등의 관리 명령을 내림

- 데이터 플레인 (data plane) : 제어 플레인에서 내린 지시를 받아 서비스 통신을 제어하거나 관리에 필요한 정보를 제어 플레인에 전송

- 데이터 플레인은 서비스 구현 시 내장되는 것이 아니라 사이드카 패턴으로 각 서비스에 부속되는 형태로 존재한다.

사이드카 패턴 (Sidecar Pattern) : 보조적 기능을 서비스와 분리하여 별도의 컴포넌트로 배포하는 분산 시스템의 설계 패턴 중 하나

- 서비스 메시에서는 사이드카로 프록시를 사용하고, 구현 언어에 의존하지 않으며 서비스와 관련된 모든 통신을 제어 가능하다, 서비스 메시릴 실현하기 위해 서비스를 변경하지 않아도 되고, 서비스 개발팀은 비즈니스 로직 개발에 전념이 가능하다.

- 정라하자면 서비스 메시를 사용하여 MSA를 관리할 때 서비스 검색과 부하 분산, 트래픽 제어, 서킷 브레이커, 분산 추적을 위한 원격 측정 데이터 수집, 보안 기능을 누릴 수 있다.

- 서킷 브레이커 (Circuit Braker) : 특정 서비스에 장애가 발생했을 때 영향 범위를 가능한 최소화하기 위한 구조. 특정 서비스에 장애가 발생하면 그 서비스를 호출하는 서비스에도 장애가 발생하며 연쇄적으로 이어진다. 결국 시스템 전체에 장애가 이어지게 된다. 장애 종류에 따라 오류가 즉각 전해지는게 아닌 타임아웃을 기다려야 하는 경우도 있는데, 연결된 모든 서비스에서 타임아웃을 기다린다면 시스템 전체에 지연이 발생된다. 따라서 장애 발생 감지시 바로 서비스 접근을 차단하고 오류를 반환하여 시스템 전체 지연을 방지하고, 서비스 회복 감지시 서비스 접근을 정상화시킨다.

- 트레이스 (trace) : 각 서비스가 어떤 순서로 호출되고, 어떻게 처리되어 다른 서비스로 연결되는지 파악할 수 있는 것, 복잡하게 연계된 서비스 내에서 문제가 발생한 위치를 찾을 때 도움이 된다. 데이터의 측정 시간과 양을 적절히 조절하여 DB 저장소의 부하나 필요한 로그의 양을 필요에 맞게 조정해야 한다.

 

7장, 마이크로서비스의 개발과 운영


Key Point

- 대규모 분산 시스템의 과제

1. 해석 툴이 있지만, 객체 간 의존 관계 파악이 어려워 서비스 및 시스템의 전체 구조 파악이 어렵다.

2. 새 기능을 추가/변경하여 릴리스할 때 버그를 발견하면 해당 부분을 고칠 때 까지 전체 릴리스를 중지해야 한다. 수정에 의한 상호 시스템간 의존 관계를 엄격하게 확인해야 하는 부담이 있다.

3. 개발자가 배포시 불안에 떨어야 하고 (ㅠ), 다수 관계자들에게도 부담이 된다. 의존 관계가 크고 개발팀간의 커뮤니케이션 관계 측면에도 문제가 많다.

4. 규모가 커질수록 전체 빌드와 결합에 시간이 많이 걸린다.

5. 시간이 많이 걸린 성과물은 많은 공간이 필요하고, 이는 이식성의 저하를 불러일으킨다. 컨테이너에겐 치명적인 문제이며, PaaS서비스에서는 어쩔 수 없이 가상 머신을 사용해야 하는 등 서비스 선택 폭이 저하된다.

 

- 대규모 분산 시스템을 MSA로 구성한 경우의 장점

1. 서비스 단위로 배포가 가능하여 버그 수정이나 기능 릴리스가 쉽다.

2. DDD (도메인 주도 개발) 설계를 활용하므로 책임 범위나 서비스 간 의존 관계를 명시적으로 나눌 수 있다.

3. 각 서비스 담당 범위에서 책임 권한이 확실하다면 자유 개발 후 배포가 가능하다. 팀의 기술 수준이나 언어, 프레임워크 등 기술력에 맞게 개발을 진행할 수 있고, 어떤 서비스는 Python, 어떤 서비스는 Java와 같이 작성할수도 있다.

4. 서킷 브레이커를 구현한다면 장애가 발생해도 파급되는 영향을 최소화할 수 있다.

6. 약한 결합이 주는 장점으로, 릴리스한 서비스가 전체에 영향을 주고 있다면 즉시 분리하여 원상태로 되돌릴 수 있다.

 

- 대규모 분산 시스템을 MSA로 구성한 경우의 주의점

1. 각 MS 자체는 단순하지만 전체적으로 시스템 동적 부품이 늘어나므로 파악이 어려워진다.

2. 서비스가 나누어지므로 서비스간 통신 구현이 필요, 이로인해 네트워크 폭주나 대기 시간이 발생할 수 있다.

 

- 가장 효과적인 데브옵스를 실천하기 위해서는 조직 미 팀마다 성숙한 문화를 가지고 있어야 한다. OKR, KPI, KGI를 정하고 이를 기준으로 팀 및 개인의 목표를 위해 전 팀이 협력하여 서비스나 제품 품질을 높이고 성공 경험을 많이 쌓는 것이 중요하다.

- MSA는 빠른 릴리스 주기를 주 장점으로 들 수 있지만, 운영팀에서 얻은 피드백과 메트릭스 과제, 제품팀에서의 추가 기능 제안에 대해 유연한 대응을 장점으로 들 수 있다.

- 데브옵스가 무조건 옳고 좋은 만능 해결책은 아니다. 각 조직 문화나 비즈니스 속도감, 데브옵스 이해도, 툴을 포함한 기술 숙련도에 맞추어 커스터마이징 하여 적용하는 것이 좋다. 중요한것은 상향식이냐 하향식이냐가 아니라, 데브옵스를 실현하고 싶다는 의지를 잃지 않는 것이다.

 

정리


이곳 저곳에서 MSA, 쿠버네티스, 도커, 분산 환경, 퍼블릭 클라우드에 대한 관심과 트렌드가 집중되고 있다. 지금까지 개인적으로 개발 공부를 하고, 협업도 해보고, 프로젝트를 진행하며 MSA의 조각 조각들을 구현하고 운영한 경험은 있으나, 이론적으로 봤을때 전체 서비스가 어떻게 구성되고, 이를 실무에서 어떻게 유관부서와의 긴밀한 협력을 통해 런칭하는지는 미숙하였다. 이 책을 통해 큰 틀에서의 MSA 구조를 이해하고, 세부적인 서비스 관리 방법을 알 수 있게 되었다.

+ Recent posts