레디스(Redis)는 인메모리 데이터 저장소로서 key-value 기반의 NoSQL입니다. 이것은 모든 데이터를 메모리에 저장하고 조회하는 방식으로 작동합니다. 디스크 저장 방식의 데이터베이스가 필요하지 않은 상황에서 사용되며, 매우 빠른 성능을 제공합니다. 레디스는 다양한 자료 구조를 지원하며, 캐싱, 메시지 브로커 등 다양한 용도로 활용됩니다
-----------------------------------
2.redis 데이터 타입
2-1. Strings (문자열) 최대 512MB까지의 문자열 값을 저장할 수 있습니다. 이진 데이터나 JPEG 이미지와 같은 바이너리 데이터도 저장 가능합니다. 증감 연산에 적합하며, 문자열 간 매핑도 가능합니다.
SET --- 값을 저장하는 명령어 SETNX --- 값이 없는 경우에는 저장하는 명령어 GET --- 데이터 조회를 위한 명령어 MGET --- 여러개의 데이터를 한번에 조회할 수 있는 명령어 INCR --- 원자적 숫자 증가 명령어 DECR --- 원자적 숫자 감소 명령어
2-1.Lists (리스트) 순서가 있는 데이터 집합으로, 중복 허용됩니다. 왼쪽 또는 오른쪽에서 요소를 추가하거나 제거할 수 있습니다.
이러한 특성으로 주로 큐(Queue)와 스택(Stack)으로 사용됩니다.
LPUSH(LEFT PUSH) -- 모든 값을 리스트의 처음에 넣는 명령어 RPUSH(RIGHT PUSH) -- 모든 값을 리스트의 마지막에 넣는 명령어 LPOP(LEFT POP) -- 리스트의 맨 처음 값을 뽑아내는 명령어 RPOP(RIGHT POP) -- 리스트의 맨 마지막 값을 뽑아내는 명령어 LLEN -- 리스트의 길이를 알아낼 수 있는 명령어 LTRIM -- 특정 인덱스까지 리스트를 잘라내는 명령어 LRANGE -- 특정 인덱스까지 값을 알아내는 명령어 BLPOP( Block Left pop ) -- 리스트에 값이 있을 경우에만 리스트의 LEFT 값을 리턴 BRPOP( Block right pop ) -- 리스트에 값이 있을 경우에만 리스트의 RIGHT 값을 리턴 LREM -- 특정 값을 지정한 횟수만큼 제거합니다. LINDEX -- 특정 인덱스의 값을 조회하거나 변경할 수 있습니다. LSET -- 특정 인덱스의 값을 변경합니다.
LPOS -- 특정 값의 인덱스를 조회할 수 있습니다;.
예를 들어
특정 redis key의 List 형태 value의 "특정 데이터"만 삭제하는 로직을 생각해보자.
LREM 'listKey' 0 value1
(모든 'value1' 값을 삭제)
2-3.Hashes (해시) 필드와 값으로 구성되며, 하나의 키에 여러 개의 필드를 저장할 수 있습니다. 주로 객체를 표현하는 데 사용됩니다.
(즉, value가 key value 형태로 이루어져 있습니다.)
HSET - hash 데이터 저장 HGET - hash 데이터 조회 HGETALL - hash 데이터 key value 모두 가져오기 HMGET - hash 데이터 value들만 가져오기 HDEL - hash 데이터 삭제 HINCRBY - hash 데이터 원자값 증가
2-4. Sets (집합) 중복된 데이터를 허용하지 않는 집합입니다. 여러 개의 값으로 구성되며, 하나의 키에 여러 번 추가해도 중복되지 않습니다.
SADD key member [member ...]: 하나 이상의 멤버를 Sets에 추가합니다.
예시:SADD myset 1 2 3(결과: 3, 중복 제거)
SCARD key: Sets의 멤버 수를 반환합니다.
예시:SCARD myset(결과: 3)
SMEMBERS key: Sets의 모든 멤버를 조회합니다.
예시:SMEMBERS myset(결과: [1, 2, 3])
SISMEMBER key member: 주어진 멤버가 Sets에 속하는지 확인합니다.
예시:SISMEMBER myset 2(결과: 1, 멤버 존재)
SREM key member [member ...]: Sets에서 멤버를 제거합니다.
예시:SREM myset 3(결과: 1, 멤버 제거)
2-5. Sorted Sets (정렬된 집합) Set에 'score’라는 필드가 추가된 데이터 타입입니다. 데이터 값을 score로 정렬하며, score가 같다면 데이터 값으로 정렬됩니다. 정렬된 데이터가 필요한 경우 사용합니다.
ZADD - Sorted Set 데이터추가 ZREM - Sorted Set 데이터삭제 ZRANGE - Sorted Set 조회 ZCARD - 해당 key에 몇개의 sorted set이 있는지 리턴 ZRANK / ZREVRANK - 순위 리턴 / 역순 순위 리턴 ZINCRBY - 증가 또는 감소된 score를 리턴
2-6. Bitmaps (비트맵) 비트 단위로 데이터를 저장하는 자료구조입니다. 주로 비트 연산을 수행할 때 활용됩니다.
(비트맵 데이터 타입은 메모리를 적게 사용하여 대량의 데이터 저장에 유리합니다.)
SETBIT : 비트값 저장 GETBIT : 비트 조회 BITCOUNT : 값이 1인 비트의 개수를 세는 데 사용
2-7. Geospatial
Geospatial 은 2차원 지도상 위.경도 좌표를 의미합니다.
좌표를 저장할때 유용한 데이터타입이고, 이 좌표와 관련된 여려 명령어들을 제공합니다.
GEOADD : 좌표 저장 GEOSEARCH : 특정 좌표기준으로 주변에 저장된 좌표가 있는지 조회 GEODIST : 위치간 거리정보 조회기능 GEOPOS : 저장된 좌표 정보 조회
2-8. HyperLogLogs (하이퍼로그로그) 고유한 요소의 개수를 추정하는 자료구조입니다. 중복을 제거하고 고유한 요소의 개수를 추적할 때 사용됩니다.
1.RDBMS 인덱스란? 인덱스 (Index)는 관계형 데이터베이스 관리 시스템 (RDBMS)에서 테이블에 대한 검색 속도를 높여주는 자료 구조입니다. 이를 통해 특정 테이블의 레코드를 전부 다 확인하는 것이 아니라 인덱싱된 자료 구조를 통해 검색되기 때문에 검색 속도가 빨라집니다.
인덱스에는 여러 가지 유형이 있지만, 가장 많이 사용되는 구조는 B-TREE 구조입니다. B-TREE는 데이터베이스에서 메모리에 일정 공간을 사용하여 저장됩니다.
2.기본 용어 정리
-블록 : 디스크 I/O를 진행하는 최소 단위
-페이지 : 블록이 메모리에 올라온 것
3.인덱스를 사용하는 이유?
3-1.예시 쿼리
-> SELECT * FROM user WHERE user_name = 'minwoo'
3-2.풀스캔의 경우
-> 모든 데이터를 다 읽으므로 단순 생각했을 때 시간복잡도는 O(N)
3-3.index가 사용되는 경우
-> 보통 인덱스의 자료구조인 B-Tree 기준으로 단순 생각했을 때 시간복잡도는 O(logN)
-> 결국 index는 조건을 만족하는 row를 빠르게 찾기 위해 사용함.
4.단일 인덱스
4-1.예시 쿼리
-인덱스 생성 쿼리 : CREATE INDEX user_PK_idx ON user (PK)
-조회 쿼리 : SELECT * FROM userWHERE PK = '3'
4-2.인덱스를 통해 조회 예시
5.결합 인덱스 (멀티 컬럼 인덱스) 5-1.예시 쿼리
-인덱스 생성 쿼리 :
-조회 쿼리 :
5-2.생성된 인덱스 모습
6.커버링 인덱스
6-1.예시 쿼리
-인덱스 생성 쿼리 :
-조회 쿼리 :
6-2.내용
6.실무에선 어떻게 활용할 수 있을까?
6-1. 인덱스 생성 여부 파악
가령 "성별"과 같은 컬럼은 "남성" 또는 "여성" 데이터가 약 50%씩 저장 되리라는 것을 예상할 수 있습니다.
그렇다면, "성별" 컬럼의 경우 인덱스를 생성하더라도, 전체 레코드(row) 中 약 절반의 데이터를 조회해야 하므로 이 경우엔 인덱스를 사용하는 것 보다 풀스캔이 나으리라는 것을 예상할 수 있습니다.
이러한 판단을 통해 인덱스를 사용해야할 컬럼에만 인덱스 생성을 잘 적용할 수 있을 것으로 보입니다.
※ 참고
옵티마이저는 논클러스터 인덱스를 통해 레코드 1건을 읽는 것이 테이블을 통해 직접 읽는 것 보다 4~5배 정도 비용이 더 많이 드는 것으로 예측한다. (왜냐하면 위의 논클러스터 인덱스 개념처럼 2단계로 나눠서 작업하기 때문)
하지만 DBMS는 우리가 원하는 레코드가 어디있는지 모르므로, 모든 테이블을 뒤져서 레코드를 찾아야한다. 이는 엄청난 디스크 읽기 작업이 필요하므로 상당히 느리다.
하지만 인덱스를 사용한다면 인덱스를 통해 PK를 찾고, PK를 통해 레코드를 저장된 위치에서 바로 가져올 수 있으므로 디스크 읽기가 줄어들게 된다. 그렇기 때문에 레코드를 찾는 속도가 훨씬 빠르며, 이것이 인덱스를 사용하는 이유이다.
반면에 인덱스를 타지 않는 것이 효율적일 수도 있다. 인덱스를 통해 레코드 1건을 읽는 것이 4~5배 정도 비싸기 때문에, 읽어야 할 레코드의 건수가 전체 테이블 레코드의 5~30%를 넘어서면 인덱스를 이용하지 않는 것이 효율적이다. 이런 경우 옵티마이저는 인덱스를 이용하지 않고 테이블 전체를 읽어서 처리한다.
1.이진 트리 (Binary Tree) 모든 노드가 최대 2개의 서브 트리를 가지는 트리입니다. 가장 많이 사용되며, 서브 트리도 모두 이진 트리여야 합니다. 이진 트리의 서브 트리는 공집합일 수 있습니다. 예시: 이진 탐색 트리 (Binary Search Tree) 등
2. 포화 이진 트리 (Full Binary Tree) 각 레벨에 노드가 꽉 차 있는 이진 트리입니다. 노드에 레벨 단위로 번호를 붙일 수 있으며, 번호는 항상 일정합니다.
3. 완전 이진 트리 (Complete Binary Tree) 높이가 k일 때, 레벨 1부터 k-1까지는 노드가 채워져 있고, 마지막 레벨 k에서는 왼쪽부터 오른쪽으로 노드가 순서대로 채워진 이진 트리입니다. 마지막 레벨에서는 노드가 꽉 차 있지 않아도 되지만, 중간에 빈 곳이 있으면 안 됩니다.
예시 : java Heap(힙)
4. B-트리 (B-Tree) 데이터베이스와 파일 시스템에서 사용되는 트리 구조입니다. 다양한 차수를 가지며, 데이터를 효율적으로 저장하고 검색하는 데 사용됩니다.
B-Tree는 자식 2개 만을 갖는 이진 트리(Binary Tree)를 확장하여 N개의 자식을 가질 수 있도록 고안된 것입니다. 그리고 좌우 자식 간의 균형이 맞지 않을 경우에는 매우 비효율적이라, 항상 균형을 맞춘다는 의미에서 균형 트리(Balanced Tree)라고 불립니다.
B-Tree는 최상위에 단 하나의 노드 만이 존재하는데, 이를 루트 노드(Root Node)라고 합니다. 그리고 중간 노드를 브랜치 노드(Branch Node), 최하위 노드를 리프 노드(Leaf Node)라고 합니다.
5. B+ 트리 (B+ Tree) B-트리의 변형으로, 데이터베이스 인덱스에서 주로 사용됩니다. 리프 노드에만 데이터가 저장되며, 리프 노드는 연결 리스트로 연결되어 있습니다.
1.웹플럭스 (WebFlux) 스프링 웹플럭스는 리액티브 스택 웹 프레임워크로, 완전한 논블로킹 (non-blocking) 방식으로 동작합니다. Reactive Streams back pressure를 지원하여 자원을 효율적으로 사용할 수 있습니다. 기존의 스프링 웹 MVC는 서블릿 API와 서블릿 컨테이너를 위해 개발되었지만, 웹플럭스는 리액티브 스트림 라이브러리를 활용하여 비동기 처리를 간단하게 구현할 수 있습니다. 웹플럭스를 사용하면 비동기 프로그래밍을 효율적으로 처리할 수 있으며, 리액터를 활용하여 데이터를 다루는 방법을 익히면 더욱 효과적으로 개발할 수 있습니다!
-------------------------------------------
2.리액터 (Reactor) 리액터는 리액티브 프로그래밍을 지원하는 라이브러리입니다. ReactiveX의 연산자 어휘와 풍부한 연산자 세트를 통해 데이터를 처리할 수 있는 Mono와 Flux API 유형을 제공합니다. Mono는 0 또는 1개의 요소를 처리하며, Flux는 0개 이상의 요소를 처리합니다.
-------------------------------------------
3.명령형 프로그래밍 vs 반응형 프로그래밍 (with 선언형 프로그래밍) 무슨 차이인지?
명령형 프로그래밍은개발자에 의해서 작성된 코드가 정해진 순서대로 실행되는 방식의 프로그래밍입니다.
코드가 순서대로 실행되므로 디버거가 코드의 명령문을 명확하게 가리킬 수 있고 다음 코드라인이 무엇인지 명확하게 알 수 있습니다.
반응형 프로그래밍은 주변 환경과 끊임없이 상호작용을 하는 프로그래밍입니다.
프로그램이 주도하는 것이 아니라 환경 (예: 데이터)이 변하면 이벤트를 받아 동작하도록 만드는 프로그래밍 기법입니다.
예를 들어, SQL 쿼리에서 "어떤 정보를 가져올지"보다 "어떻게 가져올지"에 더 관심이 있습니다.
선언형 프로그래밍은 함수형, 논리형, 제한형 프로그래밍 언어 등으로 작성되며, SQL, HTML 등이 해당됩니다.
-------------------------------------------
4.반응형 스트림(Reactive Stream)
- Publisher : 발행자 데이터를 생성하고, Subscriber에게 전송합니다. - Subscriber : 구독자 Publisher로부터 데이터를 받아들이고, 소비합니다. - Subscription : 구독 Subscriber가 처리할 데이터의 양을 정의합니다.
-------------------------------------------
5.Mono (0~1) vs Flux (0~n) Mono : 1개만 반응 Flux : n개 모두 반응 무슨 차이인지?
5-1.Flux Flux는 Reactive Stream의 Publisher에 해당하는 객체입니다. Publisher는 데이터를 방출하는 역할을 합니다. Flux는 0개부터 N개까지의 T 타입의 원소를 방출합니다. onNext 이벤트가 발생하면 원소를 방출하게 됩니다. onComplete 이벤트가 발생하면 스트림이 완료됩니다. onError 이벤트가 발생하면 에러를 발생시킵니다. 이러한 터미널 이벤트가 발생하지 않으면 Flux는 무한히 유지됩니다. Flux는 다양한 Operator를 가지고 있어 데이터를 변형하거나 조작할 수 있습니다.
5-2.Mono Mono도 Flux와 마찬가지로 Reactive Stream의 Publisher를 상속받은 객체입니다. 하지만 Mono는 0개부터 1개까지의 T 타입의 원소를 방출합니다. Mono는 단일 값을 처리하는데 사용됩니다. onNext 신호를 통해 최대 하나의 아이템을 방출하고, onComplete 신호를 통해 스트림이 종료되었다는 것을 표현합니다. onError를 통해 실패를 나타낼 수 있습니다. Mono는 주로 단일 값 처리에 사용되고, Flux는 여러 개의 값을 처리할 때 사용됩니다. 이 두 가지를 적절하게 활용하여 비동기 프로그래밍을 설계할 수 있습니다.123
-------------------------------------------
6. Publisher, Subscriber life Cycle
6-1.Publisher Life Cycle - 데이터 생성: Publisher는 데이터를 생성합니다. 이 데이터는 스트림으로 흐르게 됩니다.
- 데이터 흐름: 생성된 데이터는 Subscriber에게 전달됩니다. 이때 데이터는 비동기적으로 흐르며, back pressure를 고려하여 전송됩니다.
※ back pressure : 백 프레셔는 비동기 컴포넌트 사이에서 데이터 흐름을 제어하는 메커니즘입니다. 발행자 (Publisher)가 데이터를 얼마나 빠르게 또는 느리게 생산할지를 구독자 (Subscriber)에게 제어할 수 있도록 합니다. 리액티브 스트림에서 백 프레셔는 비동기적인 상호 작용을 정의하는 작은 스펙입니다
- 데이터 완료: Publisher가 모든 데이터를 생성하면 완료 신호를 보냅니다.
6-2.Subscriber Life Cycle - 구독 요청: Subscriber는 Publisher에게 구독을 요청합니다.
- 데이터 수신: Publisher는 데이터를 생성하고 Subscriber에게 전달합니다.
- 데이터 처리: Subscriber는 받은 데이터를 처리합니다. 이때 back pressure를 통해 데이터 흐름을 제어합니다.
- 완료 또는 에러 처리: 모든 데이터를 처리한 후 완료 또는 에러 상태로 전환됩니다. 이렇게 Publisher와 Subscriber가 상호작용하여 리액티브 스트림을 구성합니다. 이를 통해 논블로킹하게 데이터를 처리하고, back pressure를 통해 안정적인 동작을 보장할 수 있습니다.
-------------------------------------------
7.기본적인 명령어
7-1. Mono.just() Mono.just()는 단일 값을 갖는 Mono를 생성합니다. 이 값은 한 번 방출되고, 그 후에 Mono가 완료됩니다 예를 들어, Mono.just("Hello, World!")는 "Hello, World!"라는 값을 가지는 Mono를 생성합니다.
7-2. Mono.fromCallable() Mono.fromCallable()은 Callable 객체를 사용하여 Mono를 생성합니다. Callable이 실행되고 결과 값이 Mono로 방출됩니다. 예를 들어, Mono.fromCallable(() -> someExpensiveOperation())는 비용이 많이 드는 작업의 결과를 Mono로 감싸 반환합니다.
7-3. Flux.fromIterable() Flux.fromIterable()은 Iterable(예: List, Set)의 요소를 Flux로 변환합니다. 각 요소는 순서대로 방출됩니다 예를 들어, Flux.fromIterable(Arrays.asList("A", "B", "C"))는 “A”, “B”, "C"를 순서대로 방출하는 Flux를 생성합니다.
7-4. Flux.just() Flux.just()는 여러 값을 가지는 Flux를 생성합니다. 인자로 전달된 값들이 순서대로 방출되고, 그 후에 Flux가 완료됩니다 예를 들어, Flux.just(1, 2, 3)는 1, 2, 3을 순서대로 방출하는 Flux를 생성합니다.
7-5. doOnNext(), doOnComplete(), doOnError() 이들은 각각 요소가 방출될 때, Flux나 Mono가 완료될 때, 또는 에러가 발생했을 때 실행되는 콜백입니다. 예를 들어, flux.doOnNext(item -> log.info("Received item: " + item))는 요소가 방출될 때 로그를 남깁니다.
7-6. map map은 Flux나 Mono의 각 요소를 변환합니다. 함수를 적용하여 요소를 변환하고 새로운 Flux나 Mono를 생성합니다. 예를 들어, flux.map(item -> item * 2)는 각 요소를 2배로 변환한 Flux를 생성합니다.
7-7. filter filter는 조건을 만족하는 요소만을 방출하는 연산자입니다. 예를 들어, flux.filter(item -> item > 0)는 양수인 요소만을 방출하는 Flux를 생성합니다.
7-8. flatMap flatMap은 각 요소를 다른 Flux나 Mono로 변환하고, 그 결과를 하나의 Flux로 합칩니다. 비동기 작업에 유용합니다 예를 들어, flux.flatMap(item -> getRelatedItems(item))는 각 요소를 관련된 항목으로 변환한 후 합쳐진 Flux를 생성합니다.
7-9. then then은 이전 작업이 완료된 후 다음 작업을 실행합니다. 결과를 반환하지 않습니다. 예를 들어, flux.then(Mono.just("Done!"))는 Flux가 완료된 후 "Done!"을 방출하는 Mono를 생성합니다.
7-10. onErrorResume 에러가 발생했을 때 대체 Mono나 Flux를 반환합니다. 예를 들어, flux.onErrorResume(e -> Mono.just("Fallback"))는 에러가 발생하면 "Fallback"을 방출하는 Mono를 생성합니다.
-------------------------------------------
8.콜드스트림, 핫스트림 차이
콜드 스트림 (Cold Stream): 콜드 스트림은 데이터를 생성하는 시점에서 구독자가 없는 상태로 시작합니다. 이는 데이터를 발행하는 측에서 구독자가 생기기 전까지 데이터를 생성하지 않는 특성을 가지고 있습니다. 콜드 스트림은 일반적으로 컬렉션, Sequence, RxJava의 Observable 등과 같은 데이터 소스에서 나옵니다. 콜드 스트림은 구독자가 생긴 후에야 데이터를 생성하며, 각 구독자는 독립적으로 데이터를 받습니다.
핫 스트림 (Hot Stream): 핫 스트림은 데이터를 생성하는 시점과 상관없이 항상 데이터를 발행합니다. 핫 스트림은 이미 데이터를 생성하고 있으며, 구독자가 생기면 해당 데이터를 구독자에게 전달합니다. 웹플럭스(Flux)에서는 핫 스트림을 사용하여 이벤트 기반 프로그래밍을 지원합니다. ( 구독자가 생기든 말든 상관없이 계속해서 데이터를 발행합니다)
예를 들어, 웹플럭스에서는 Flux 클래스를 사용하여 핫 스트림을 생성할 수 있습니다. 이 스트림은 이미 데이터를 발행하고 있으며, 구독자가 생기면 해당 데이터를 구독자에게 전달합니다. 반면에 콜드 스트림은 구독자가 생기기 전까지 데이터를 생성하지 않습니다.
이제 웹플럭스에서 콜드 스트림과 핫 스트림을 어떻게 다루는지 살펴보겠습니다. 웹플럭스는 리액티브 프로그래밍을 위한 라이브러리로, 코틀린 코루틴과 함께 사용됩니다. 웹플럭스에서는 콜드 스트림을 핫 스트림으로 변경하는 방법으로 shareIn 연산자를 제공합니다. 이 연산자를 사용하면 콜드 스트림을 주어진 코루틴 스코프 내에서 시작되는 핫 스트림으로 변경할 수 있습니다. 또한, replay 매개변수를 통해 최근 값들을 새로운 구독자에게 다시 발행할 수 있습니다.
※ 코루틴 : 코루틴은 프로그래밍에서 루틴의 일종으로서, 협동 루틴이라 할 수 있습니다. "코루틴"이라는 단어에서 "Co"는 “with” 또는 "together"를 의미합니다. 즉, 코루틴은 함께 수행되는 함수입니다 코루틴은 서브 루틴과는 다른 특징을 가지고 있습니다. 서브 루틴은 무조건 순차적으로 수행되어야 하지만, 코루틴은 함께 수행되며 서로 무제한 양보를 할 수 있습니다. 이는 스레드의 자원을 최대한 활용할 수 있게 해줍니다. 간단히 말하면, 코루틴은 실행의 지연과 재개를 허용함으로써 비선점적 멀티태스킹을 위한 서브 루틴을 일반화한 컴퓨터 프로그램 구성 요소입니다. 코루틴은 비동기 작업을 효율적으로 처리하는 데 사용되며, Kotlin과 같은 언어에서 지원됩니다
예를 들어, 다음과 같은 코드에서 shareIn 연산자를 사용하여 콜드 스트림을 핫 스트림으로 변경할 수 있습니다:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
private suspend fun initHeavyLogic() {
delay(1000) // 1초 동안 대기한 다음
println("initHeavyLogic") // "initHeavyLogic"을 출력하는 비동기 작업을 수행
}
// 코루틴은 특정 범위 내에서 실행되며, 해당 범위를 지정하여 코루틴이 어떤 스레드에서 실행되는지, 어떤 라이프사이클에 종속되는지 등을 결정함.
// Dispatchers.IO는 I/O 작업에 최적화된 디스패처(dispatcher)를 의미합니다.
// 따라서 이 코루틴 범위 내에서 실행되는 코루틴은 I/O 작업을 처리하기에 적합합니다.
// 코루틴 범위를 지정함으로써 코루틴이 어떤 스레드에서 실행되는지 명시적으로 설정할 수 있습니다
val coroutineScope = CoroutineScope(Dispatchers.IO) // 스레드에서 코루틴을 실행할 수 있는 범위를 정의합니다.
val counter: Flow<Int> = flow { // Flow는 비동기적인 데이터 스트림을 처리하기 위한 기능을 제공, 블록 내에서 데이터를 생성합니다.
initHeavyLogic() // 함수를 호출하여 초기 로직을 실행합니다.
var count = 0
while (true) { // 무한 루프를 통해 count 값을 생성하고, 200ms마다 데이터를 발행합니다.
emit(count++) // 값을 발행
delay(200)
}
// shareIn()을 사용하여 플로우를 공유하고, Eagerly 모드로 시작합니다.
// 공유 플로우는 여러 구독자가 동시에 값을 수신할 수 있으며, 각 구독자는 독립적으로 데이터를 받습니다.
}.shareIn(
scope = coroutineScope, // 코루틴 범위를 정의하는 객체. 현재 코루틴이 coroutineScope 내에서 실행됨을 의미
started = SharingStarted.Eagerly // SharingStarted.Eagerly는 즉시 감지를 의미. 코루틴이 시작되자마자 값을 방출하기 시작.
)
fun main(args: Array<String>) {
println("Started") // "Started"를 출력합니다.
runBlocking { // runBlocking 블록 내에서 두 개의 코루틴을 실행합니다.
launch { // 첫 번째 코루틴
println("#1 Launched") // "#1 Launched"를 출력하고,
counter // counter 플로우에서 처음 10개의 값을 수집하여 출력합니다.
.take(10)
.collect { println("#1:$it") }
}
launch { // 두번째 코루틴
delay(2000)
println("#2 Launched") // 2초 후에 "#2 Launched"를 출력하고,
counter // counter 플로우에서 다음 10개의 값을 수집하여 출력합니다.
.take(10)
.collect { println("#2:$it") }
}
}
println("Finished") // "Finished"를 출력합니다.
}
shareIn 모드 中 핫스트림
Eagerly 모드:
구독자가 처음 나타나기 전에도 상위 플로우가 시작됩니다.
이 경우replay매개변수로 지정된 최신 값 이후의 모든 값은 즉시 삭제됩니다.
즉, 구독자가 생기기 전에도 데이터를 발행하며, 최신 값 이후의 데이터는 삭제되지 않습니다.
이는 핫 스트림과 유사한 특성입니다.
shareIn 모드 中 콜드스트림
Lazily 모드:
구독자가 처음 나타난 후에 상위 플로우가 시작됩니다.
이로 인해 첫 번째 구독자는 모든 발행된 값을 받지만, 이후 구독자는 최신 replay 값만 보장받습니다.
즉, 구독자가 생기기 전까지 데이터를 생성하지 않으며, 구독자가 생긴 후에야 데이터를 생성합니다. 이는 콜드 스트림과 유사한 특성입니다.
WhileSubscribed 모드:
구독자가 처음 나타날 때 상위 플로우가 시작되고, 마지막 구독자가 사라질 때 즉시 중단됩니다.
이 경우 replay 값은 구독자 없이도 계속 캐시됩니다.
즉, 구독자가 있을 때만 데이터를 생성하며, 모든 구독자가 사라지면 중단됩니다. 이 역시 콜드 스트림과 유사한 특성입니다.
-------------------------------------------
9.리액티브 시스템
리액티브 시스템은 리액티브한 원리와 원칙을 적용하여 개발된 소프트웨어 시스템을 의미합니다. 이러한 시스템은 주로 빠른 응답성과 내결함성을 강조하며, 비동기적인 처리, 메시지 기반 아키텍처, 이벤트 주도 구조 등을 활용하여 확장성과 탄력성을 갖출 수 있습니다.
우리는응답이 잘 되고(클라이언트의 요청에 즉각적으로 응답하고), 탄력적이며 유연하고 메시지 기반으로 동작하는 반응을 잘하는 시스템을 기대합니다. 우리는 이것을리액티브 시스템(Reactive Systems)라고 부릅니다.
9-1. 응답성(Responsive)
시스템이 가능한 한 즉각적으로 응답하는 것을 응답성이 있다고 합니다.
응답성 있는 시스템은 신속하고 일관성 있는 응답 시간을 제공하고, 신뢰할 수 있는 상한선을 설정하여 일관된 서비스 품질을 제공합니다. 이러한 일관된 동작은 오류 처리를 단순화하고, 일반 사용자에게 신뢰를 조성하고, 새로운 상호작용을 촉진합니다.
9-2. 탄력성(Resilient)
시스템이 장애 에 직면하더라도 응답성을 유지 하는 것을 탄력성이 있다고 합니다.
탄력성은 복제, 봉쇄, 격리, 위임에 의해 실현됩니다. 장애는 각각의 구성 요소 에 포함되며 구성 요소들은 서로 분리되어 있기 때문에 이는 시스템이 부분적으로 고장이 나더라도, 전체 시스템을 위험하게 하지 않고 복구 할 수 있도록 보장합니다.
9-3. 유연성(Elastic)
시스템이 작업량이 변화하더라도 응답성을 유지하는 것을 유연성이라고 합니다. 리액티브 시스템은 입력 속도의 변화에 따라 이러한 입력에 할당된 자원을 증가시키거나 감소키면서 변화에 대응합니다.
9-4. 메시지 주도(Message Driven)
리액티브 시스템은 비동기 메시지 전달 에 의존하여 구성 요소 사이에서 느슨한 결합, 격리, 위치 투명성 을 보장하는 경계를 형성합니다.
이 경계는 장애 를 메시지로 지정하는 수단을 제공합니다. 명시적인 메시지 전달은 시스템에 메시지 큐를 생성하고, 모니터링하며 필요시 배압 을 적용함으로써 유연성을 부여하고, 부하 관리와 흐름제어를 가능하게 합니다.
논블로킹 통신은 수신자가 활성화가 되어 있을 때만 자원 을 소비할 수 있기 때문에 시스템 부하를 억제할 수 있습니다.
이벤트가 발생하거나 요청이 들어오면 해당 메시지를 큐에 넣고, 수신 컴포넌트들은 이 메시지를 비동기적으로 받아 처리합니다. 이러한 방식으로 컴포넌트 간의 결합도를 낮추고 확장성과 유연성을 강화할 수 있습니다.
리액티브 시스템은 데이터나 이벤트가 발생하면 즉시 반응하여 처리하는 것이 중요합니다. 이를 위해 변화 전파를 하는 과정에서push방식을 사용합니다.
-------------------------------------------
10.리액티브 스트림즈
리액티브 프로그래밍을 구현한 라이브러리들이 정해진 표준없이 구현되기 시작했습니다. 이에 하나의 규칙을 정해서, 여러 리액티브 프로그래밍 구현체들이 상호 변환 가능하도록 만들자는 목소리가 나오기 시작했습니다.
그렇게 만들어진 비동기 스트림 처리를 위한 표준(규칙)이리액티브 스트림즈입니다.
이 표준은 데이터 스트림을 Non-Blocking이면서 비동기적인 방식으로 처리하기 위해 만들어졌습니다. 리액티브 스트림즈는 다양한 라이브러리에서 구현되어 있으며, 리액티브한 코드 작성과 구성을 용이하게 해 줍니다.
리액티브 스트림즈의 핵심 컴포넌트는 다음과 같습니다:
Publisher: 데이터를 생성하고 통지(발행, 게시, 방출)하는 역할을 합니다. 구독자(Subscriber)가 데이터를 받을 수 있도록 데이터를 제공합니다.
Subscriber: 구독한 Publisher로부터 통지된 데이터를 전달받아서 처리합니다. Publisher로부터 데이터를 받기 위해 구독(subscribe)합니다.
Subscription: Publisher에 요청할 데이터의 개수를 지정하고, 데이터의 구독을 취소하는 역할을 합니다. 데이터의 요청 개수를 지정하여 데이터 흐름을 조절합니다.
Processor: Publisher와 Subscriber의 기능을 모두 가지고 있습니다. 다른 Publisher를 구독하거나, 다른 Subscriber가 구독할 수 있습니다. 리액티브 스트림즈를 통해 구현된 라이브러리로는 RxJava, Reactor, Akka Streams, Java 9 Flow API 등이 있습니다. 이 중에서 Spring Framework와 가장 궁합이 잘 맞는 구현체는 Reactor입니다. 이를 활용하여 비동기적이고 효율적인 코드를 작성할 수 있습니다.
발행자와 구독자 간의 데이터 흐름을 조절하고 백 프레셔( backPressure )를 지원합니다. 예를 들어, 데이터 저장소가 HTTP 서버에 응답에 사용할 데이터를 생성할 때 백 프레셔를 통해 데이터 흐름을 제어할 수 있습니다
예산을 세우셨는데,고정비나 생활비가 너무 많아"선저축"할 금액이 너무 낮다면,소비 금액에서 얼마나 줄여볼 수 있는지 검토해보셔야 합니다.
다만,어느 정도가 적정한 소비인지 판단하기 어려우신 독자 여러분들이 계실 것 같아 대략적인 가이드라인을 잡아드리고자 합니다.
크게 아래와 같은 내용에 대해 가이드를 잡아드려보겠습니다.
1.주거비 2.보장성 보험료 3.차량 유지비 4.문화/여가/레저비
1.주거비
주거비의 경우 부모님 집에서 함꼐 생활하신다면0원으로 가장 좋겠지만,독립하신 경우라면 세입자로 주거하는 경우와 자가에 실거주 하는2가지 경우가 있습니다.
1-1..세입자인 경우 월 소득의15%이하 권장 / 20% 이하 필수
전세인 경우 전세 대출 이자와 관리비가 월 소득의15%이하가 되길 권장드립니다.
월세인 경우에도 전세와 마찬가지로 월세와 관리비가 월 소득의15%이하가 되길 권장드립니다.
사회초년생 때는 소득이 높지 않아,월 소득15%이하의 방을 구하기 어려우실 수 있습니다.
이 경우 저축 금액을 높이고 싶으시다면,주거환경을 많이 낮추실 수 밖에 없습니다.
필자의 경우 대학교와 회사를 병행할 때 세후 약160 ~ 170만원 정도의 월 급여를 받았습니다.
이 때는 경기도 안양에서 보증금 없이 월26만원의 고시원에서 생활했습니다.
대학교 졸업 후에는 세후 약200만원이 되는 직장으로 이직하였는데요.
이 때는 경기도 성남에서 전세7,500만원의 반지하 원룸에 살았었습니다.
필자는 전세가의80%인6,000만원 대출을 받았었고, 이 당시 금리가 저렴했기 때문에 월 이자와 원룸 관리비가 크게 비싸지 않았습니다.
이렇게 거주 환경을 낮춰 저축한 돈으로 필자는 만 29살에 경기도 용인,분당선 초역세권에25평 아파트를 계약할 수 있었습니다.
이 집은 현재도 현금 흐름이 필요할 때는 월세로,목돈이 필요할 때는 전세를 받으며 활용하고 있습니다.
만약 사회초년생으로 소득이 높지 않으실 때,저축을 많이 하고 싶으시다면
제가 가이드 드린 월소득 기준 거주비15%이하를 실행해보시길 권장드리며,
아무리 못해도 거주비가 20%를 넘지 않길 당부드립니다.
더 많은 저축을 원하신다면 필자처럼 고시원이나 반지하 등 어느 정도 현재의 삶을 희생하셔야 합니다.
고통 없이는 미래가 바뀌지 않을 가능성이 큽니다.
1-2.자가인 경우 월 소득의30%이하 권장 / 40% 이하 필수
만약 자가에서 생활하시는 경우엔 전세와 달리 만기 일시 상환 대출이 아니기 때문에"대출 원금"과 "이자"를 함께 갚으셔야 합니다.
그렇기 때문에 주거비가 더 올라갈 수 밖에 없으나, 이는 자산을 취득하기 위한 소비이므로 "대출원리금"과 "관리비"가 월소득의30%이하가 되길 권장드립니다.
자가인 경우라도 거주비가 월 소득의 40%를 넘지 않길 당부드리며, 거주비가 40%가 넘어간다면, 생활비나 저축 등 다른 부분에 애로사항이 생길 수 밖에 없습니다.
이런 경우 해당 집은 잠깐 전세나 월세를 주시고,좀 더 저렴한 거주지를 찾아보시길 권장드립니다.
주거비로 너무 많은 돈이 나가게 되면,다른 저축이나 투자를 하기 힘들기 때문에 노후에 다른 자산 없이 부동산1채만 남아 현금 흐름 없는 힘든 노후를 보내실 수 있습니다.
2.보장성 보험료
보장성 보험이란 질병,상해,사망 등 내용으로 보험료를 지급받는 보험을 말합니다.
여기에는 보험료의 일부 금액을"저축"해서 만기에 환급해준다던지, "투자"를 대신 해 준다던지 이런 보험료는 포함하지 않고, 보장성 보험료에 대해서만 이야기 하도록 하겠습니다.
보험과 관련되어 자세한 이야기는 뒤에 부록에서 이야기 드리도록 하겠습니다.
보장성 보험료의 경우 크게 자녀가 없거나 미성년 자녀가 있는2가지 경우로 나눠보겠습니다.
2-1.자녀가 없는 경우 월 소득의3%이하 권장
자녀가 없어 본인이 아플 때만 책임지면 되는 경우 보장성 보험료는 월 소득의3%이하가 적정하다고 보입니다.
물론 아플 때를 대비하여 많은 보험을 들어놓으면 좋습니다.
하지만,보험은10년~ 30년 장기간 납부 해야하는 상품으로 보험을10년 이상 유지하기 위해서는 본인 소득 대비 무리한 금액을 들어놓으면 안됩니다.
보험은 중도 해약할 경우 내가 낸 금액에 비해 해약환급금이 적은 것이 보통이기 때문에 보험에서 가장 중요한 것은 이 보험을"유지"할 수 있느냐 입니다.
만약 보험을 너무 많이 들어놓아 당장의 생활비나 저축금액이 줄어들어"현재의 나"와"미래의 나"모두에게 좋지 않은 결과가 나오고,중간에 해약하여 본인이 냈던 돈도100%받지 못한다면,얼마나 억울한 일이겠습니까?
따라서 현재 나의 소득을 기반으로 적정한 보험료로 보장 받는 것이 유의미하며,필자는 자녀가 없거나 이미 자녀가 성인이라면,월 소득의3%이하가 적정하다고 보여집니다.
(예시:세후 월200직장인의 경우 월6만원 이하,세후 월300직장인의 경우 월9만원 이하)
2-2.미성년 자녀가 있는 경우 월 소득의3~5%이하 권장
자녀가 미성년이신 경우엔 월 소득의3~5%이하가 적정하다고 보여집니다.
여기서 추가하실 보험은"정기 보험"으로 간단히 말씀드리면,특정 나이까지만 보장되는 사망보험입니다.예를 들어 자녀가 성인까지 성장할 경우 그 이후에는 부모님의 사망보험금 중요도와 필요성이 상대적으로 낮아지기 때문에 만약을 대비하여 자녀가 성인이 될 때까지 들어 놓는 부모님의 사망 보험입니다.
"정기 보험"에 대해서는 뒤에 부록에서 좀 더 상세히 다뤄드리도록 하겠습니다.
본론으로 돌아와 미성년 자녀가 있으신데,부부 중 한 분이 사망하실 경우 자녀를 양육하는데 있어 큰 문제가 생기실거라는건 독자 여러분들께서도 충분히 예상 되실거라 생각됩니다.
이러한 큰 리스크를 방어하고자,자녀가 성인이 되기 까지 생활이 가능한 사망보험금이 나올 수 있도록 부부 모두 정기 보험을 들어놓는 것을 추천드리고 싶습니다.
만약 사회초년생 떄 보다 월 급여가 많이 늘어나 정기보험을 포함해도 월 소득의3%가 되지 않는 상황이시라면 더 좋습니다.
다만,어쨌든“정기 보험”이라는 것을 추가해야하므로,상황에 따라 지출 보험료가 좀 더 올라 월 소득의5%까지만 권장드립니다.
자신의 적정한 저축금액을 알기 위해서는 먼저 월 고정비나 생활비와 같이 1달에 돈을 얼마나 쓰는지 파악이 필요합니다. 그러기 위해 간단한 가계부를 작성할 필요가 있습니다.
가계부를 통해 월 생활비 금액이 파악 되었다면, 1달 예산을 산정해야합니다.
만약 월 생활비가 너무 많아 저축할 금액이 적다면, 예산 조정도 필요합니다.
이 장에서는 예산을 정하실 때 소비 금액에 대한 간단한 가이드도 드릴 예정입니다.
매월 예산을 정하셨다면, 매월 저축할 금액이 산정되었을텐데요. 저축은 생활비를 다 쓰고 저축하는 형태가 아닌, 월 소득을 받자마자 저축하는 "선저축"이 중요합니다.
그리고 예산에 맞춰 체크카드를 사용하신다면, 매월 적절한 소비와 저축이 가능하실거라 생각합니다.
이번 장을 참고해주시어 독자 여러분들만의 월 예산을 세워보시길 바라겠습니다.
2-1.나의 소비 금액을 파악해보자
소비 금액을 파악해보기 위한 방법은 여러 가지가 있습니다.
주로 카드를 많이 사용하시는 분이라면 지난 카드 값을 정리해보시며,매 월 어느 정도 사용하는지 체크해보실 수 있습니다.
지난달까지 소비 금액 파악이 어렵다면,가계부를 써보시는 것도 방법입니다.
제 경우 소비가 많이 없기 때문에 크게 아래와 같이"고정비"와"일상 생활비", "예비비"처럼3가지 분류를 나눠 소비 금액을 분류해보는 편입니다.
각 항목에 대한 정의는 아래와 같습니다 -고정비:매월 정해진“고정”금액이 지출되는 금액을 정리합니다.보통OTT와 같은 구독료,갱신이 일어나지 않으면 금액이 유지되는 보험료,관리비 등이 있습니다. -일상 생활비:매 월 동일 금액은 아니지만 비슷하게 소비하는 금액입니다.식비나 커피,교통비 등이 있습니다. -예비비:매 월 소비하지 않고,특정 이벤트에 지출되는 금액입니다.보통 경조사나 여행,겨울 옷 구매 등이 있습니다.
소비가 많으신 경우"식비", "문화생활,레져비", "주거비", "차량유지비"와 같이 좀 더 세세하게 독자 여러분들의 입맛에 맞춰 소비 금액을 분류해보셔도 좋습니다.
아래는 독자 여러분들의 이해를 돕기 위해 소비 금액을 어떻게 정리하는지 보여드리는 예시입니다.