RDB는 테이블 기반의 정형화된 구조를 가진 관계형 데이터베이스 입니다. 고정된 스키마와 SQL 기반 질의를 사용하며, 데이터 간의 정합성과 트랜잭션 안정성이 중요한 경우에 적합합니다.
반면 NoSQL은 문서, 키-값, 그래프 등 유연한 스키마를 기반으로 다양한 형태의 데이터를 저장할 수 있으며, 수평 확장성(Scale-out)에 강점이 있습니다. 대용량 데이터 처리, 빠른 읽기/쓰기, 스키마가 자주 바뀌는 환경에 적합합니다.실제로 저는 JWT 기반 인증 시스템을 구축할 때 두 가지를 병행해 사용했습니다. 회원 정보와 권한 관리 등 정형 데이터는 RDB에 저장하고, Access/Refresh 토큰과 임시 인증정보는 Redis와 같은 NoSQL 기반의 키-값 저장소에 관리함으로써, 빠른 조회와 유효성 검증, 그리고 자동 만료 처리를 효율적으로 구현했습니다.
이처럼 시스템의 요구사항에 따라 RDB와 NoSQL을 적절히 분리해 사용하는 것이 실무에서 중요한 설계 전략이라고 생각합니다.
RDB
| DBMS 이름 | 특징 |
|---|---|
| MySQL | 가장 많이 쓰이는 오픈소스 RDB |
| PostgreSQL | 기능 강력한 오픈소스, JSON도 지원 |
| Oracle | 기업용 상용 DB, 높은 안정성과 보안 |
| SQLite | 모바일, 임베디드용 경량 DB |
NoSQL
| DB 종류 | 주요 예시 | 설명 |
|---|---|---|
| 문서형 | MongoDB | JSON 문서 저장, 유연한 스키마 구조 |
| 키-값형 | Redis | 인메모리 기반, 빠른 캐시·세션 저장 |
| 컬럼형 | Cassandra | 열 단위 저장, 대규모 데이터 처리(로그 분석 등)에 쓰임 |
| 그래프형 | Neo4j | 노드-간 관계를 그래프로 표현, 추천 시스템·SNS(친구 관계)에 유리 |
Redis는 인메모리 기반의 키-값 저장소로, 매우 빠른 읽기/쓰기 성능을 제공하는 NoSQL 데이터베이스입니다. 일반적으로 캐시, 세션 관리, 실시간 데이터 처리에 많이 활용되며, 데이터가 메모리에 저장되기 때문에 지연 시간이 극히 짧습니다.
Redis는 모든 데이터를 메모리(RAM)에 저장하고 처리하기 때문에 디스크보다 훨씬 빠릅니다.
그리고 싱글 스레드로 동작하지만, 요청을 한 번에 빠르게 처리하고 기다리지 않는 구조(이벤트 루프)라서, 많은 요청을 순차적으로 빠르게 처리할 수 있습니다. 게다가 여러 스레드에서 동시에 데이터를 접근할 일이 없기 때문에, 락이 필요 없어 성능 손실이 없습니다.이런 구조 덕분에 복잡한 멀티스레딩보다 오히려 더 단순하고 빠르게 작동할 수 있고, 실제로도 수십만 건의 요청을 안정적으로 처리할 수 있습니다.
Redis는 빠르지만 캐시는 결국 임시 저장소라서, 캐시 미스·데이터 불일치·Redis 장애 같은 문제가 생길 수 있습니다. 그래서 DB와 캐시의 역할을 분리하고, 장애 발생 시에도 DB로 fallback할 수 있는 구조를 설계하는 게 중요하다고 생각합니다.
JOIN은 SQL에서 두 개 이상의 테이블을 연결해서 하나의 결과로 보여주는 기능입니다. INNER JOIN은 양쪽 테이블에 모두 존재하는 데이터만 결과에 포함되고, OUTER JOIN은 한쪽 테이블을 기준으로 데이터를 모두 포함하는 방식입니다. LEFT JOIN은 왼쪽 테이블을 기준으로 오른쪽에 값이 없으면 NULL로 채워서 포함하고, RIGHT JOIN은 반대로 오른쪽 테이블을 기준으로 합니다. FULL OUTER JOIN은 양쪽 중 하나라도 값이 있으면 모두 포함해주고, CROSS JOIN은 조건 없이 가능한 모든 조합을 생성하는 카테시안 곱 방식입니다. 마지막으로 SELF JOIN은 자기 자신과 조인할 때 사용되며, 보통 같은 테이블 안에서 상하 관계처럼 연결된 데이터를 조회할 때 활용됩니다.
OUTER JOIN을 사용하면 NULL이 포함될 수 있는데, 이때 COUNT 함수의 동작 방식에 따라 결과가 달라질 수 있습니다. 예를 들어 COUNT(컬럼명)는 NULL을 제외하고 세기 때문에 일부 행이 누락될 수 있지만, COUNT(*)는 NULL 여부와 관계없이 모든 행을 집계합니다. 따라서 OUTER JOIN과 집계를 함께 사용할 때는 어떤 기준으로 셀 것인지 명확히 설정해야 합니다.
완전 함수적 종속은 어떤 속성 집합 X가 Y를 결정할 때, X의 어떤 속성이라도 제거하면 더 이상 Y를 결정하지 못하는 경우를 말합니다. 즉, Y가 X 전체에 완전히 의존하고 있다는 뜻입니다. 함수적 종속이란 어떤 속성 A의 값을 알면 다른 속성 B의 값이 유일하게 정해지는 관계를 의미하는데, 완전 함수적 종속은 그 중에서도 X 전체가 꼭 있어야 Y가 결정되는 경우입니다. 예를 들어 X가 학번과 과목코드이고 Y가 성적일 때, 성적은 학번만으로도, 과목코드만으로도 정해지지 않고 두 개 모두 필요하다면, 성적은 학번과 과목코드에 완전 함수적으로 종속된다고 말할 수 있습니다.
충돌률이 높을 땐 비관적 락이 안정성을 주지만, 성능 저하가 크기 때문에 상황에 따라 락 범위를 최소화하거나 낙관적 락을 활용해 충돌 시만 처리하는 방식이 더 효율적일 수 있습니다.
낙관적 락
충돌 가능성이 있어도 락을 걸지 않고 먼저 작업을 시도하고, 최종 저장 시 충돌 여부를 검사합니다.
→ 주로 읽기 많고 쓰기 적은 경우, 혹은 충돌은 드물지만 발생 시만 처리해도 될 때 적합합니다.
→ JPA에서는 @Version 어노테이션으로 구현 가능
비관적 락
데이터에 접근할 때 즉시 락을 걸어서 다른 트랜잭션의 접근을 막는 방식입니다.
→ 충돌이 자주 발생하거나, 데이터 정합성이 매우 중요한 경우에 사용합니다.
→ SQL에서는 SELECT ... FOR UPDATE로 구현
분산 락 (Redis RedLock, ZooKeeper 등)
서버 간에 분산 환경이라면, 멀티 인스턴스 간 락 동기화가 필요합니다.
→ Redis 기반 락은 속도가 빠르고 경량화되어 있어 사용 빈도가 높습니다.
이행적 함수 종속은 어떤 속성 A가 B를 결정하고, B가 C를 결정할 때 A가 C를 간접적으로 결정하게 되는 종속 관계를 말합니다. 이는 정규화 과정 중 제3정규형에서 중요하게 다뤄지는데, 제3정규형은 기본키가 아닌 속성이 다른 기본키가 아닌 속성에 이행적으로 종속되는 것을 제거하는 것을 목표로 합니다.
제1정규형(1NF)은 테이블의 모든 컬럼이 더 이상 나눌 수 없는 원자값만 가지도록 하는 것으로, 반복 속성이나 다중 값 속성을 제거하는 것이 목적입니다. 제2정규형(2NF)은 1NF를 만족하면서, 기본키가 복합키인 경우 부분 함수 종속을 제거해 기본키 전체에만 종속되도록 하는 것을 목표로 합니다. 제3정규형(3NF)은 2NF를 만족하면서, 이행적 함수 종속을 제거해 기본키가 아닌 속성이 또 다른 기본키가 아닌 속성에 의존하지 않도록 테이블을 분리합니다.
데이터의 정확성, 일관성, 유효성을 유지하는 것을 의미합니다. 무결성이 유지될 때 DB에 저장된 데이터 값과 거기에 해당하는 현실 세계의 실제 값이 일치하는지 신뢰할 수 있습니다. 즉, 잘못된 값이 저장되거나 논리적으로 모순되는 데이터가 생기지 않도록 보장하는 것입니다.
→ 개체 무결성(기본 키로 선택된 필드는 빈 값 허용X)
→ 참조 무결성(서로 참조 관계에 있는 두 테이블의 데이터는 항상 일관된 값을 유지)
→ 도메인 무결성(테이블에 존재하는 필드의 무결성을 보장하기 위한 것으로 올바른 데이터가 입력되었는지 체크)
→ 고유 무결성(특정 속성에 대해 고유한 값을 가지도록 조건이 주어진 경우, 그 속성값은 모두 고유한 값을 가짐)
→ NULL 무결성(특정 속성값에 NULL이 올 수 없다는 조건이 주어진 경우, 그 속성값은 NULL이 될 수 없다는 제약 조건)
→ 키 무결성(한 릴레이션에는 최소한 하나의 키가 존재해야 한다는 제약 조건)
클러스터형 인덱스는 인덱스 자체가 실제 데이터를 담고 있어 인덱스와 데이터가 같은 구조로 저장되며, 기본키 기준으로 정렬되어 있는 것이 특징입니다. 따라서 클러스터형 인덱스를 통해 데이터를 검색하면 곧바로 실제 데이터에 접근할 수 있어 효율적입니다. 반면 보조 인덱스는 실제 데이터와 분리된 인덱스로, 인덱스를 통해 먼저 기본키 값을 찾고 그 값을 이용해 다시 테이블에서 데이터를 조회해야 하므로 한 번의 추가 접근이 필요합니다. MySQL의 InnoDB 엔진에서는 기본키가 클러스터형 인덱스로 작동하고, 나머지 인덱스는 모두 보조 인덱스로 동작합니다.