프론트엔드

BO에서 글을 수정해도 Web이 안 바뀌는 이유: 모노레포 환경 캐시 전략

BO에서 글 수정 시 Web에 미반영 문제 관리자 페이지(BO)에서 게시글을 수정하면 DB 및 BO에는 즉시 반영된다. 문제는 수정 직후 Web에서 해당 글을 확인하면, 내용이 이전 버전으로 계속 노출되었다. 관리자에서 수정이 완료되었는데 Web에는 반영되지 않는 상황이기 때문에, 운영 흐름상 수정 직후 화면에 변경 사항이 보이지 않는 것은 문제가 될 수밖에 없다. Web은 Next.js App Router + fetch 기반 SSR 구조로 구성되어 있었고, App Router의 fetch는 서버 레벨 캐싱을 기본으로 동작한다. 이는 일반적인 클라이언트 캐싱 라이브러리(예: React Query)와는 동작 방식이 다르다. App Router 환경에서 fetch는 기본적으로 force-cache로 동작한다. 단순 새로고침은 서버 재요청을 의미하지 않는다. 데이터는 빌드 시점 혹은 ISR 주기에 따라 캐시된다. 명시적으로 무효화하지 않는 한 캐시는 유지된다. 따라서 DB가 갱신되더라도, Web 서버의 캐시가 무효화되지 않으면 화면에는 반영되지 않는다. Web과 BO가 분리된 모노레포 구조 프로젝트는 pnpm 기반 모노레포로 구성되어 있다. 구조적으로 다음과 같은 특성을 가진다. web: SSR 기반 콘텐츠 렌더링 bo: CRUD 중심 관리자 기능 api: 데이터 처리 백엔드 이 세 애플리케이션은 같은 레포에 존재하지만, 런타임은 완전히 독립적으로 동작한다. 즉, 모노레포는 코드 레벨의 공유를 의미할 뿐, 런타임 공유를 의미하지 않는다. BO에서 게시글을 수정하면 DB는 갱신 되지만, Web 서버 내부 캐시는 별도의 프로세스로 유지된다. 따라서 BO에서 직접 revalidate를 호출하더라도 Web 서버의 캐시는 무효화되지 않는다. 설계한 캐시 무효화 전략 ① Web에 invalidation 전용 API route 생성 Web 서버 내부에서만 실행되는 캐시 무효화 엔드포인트를 별도로 구성하였다. 캐시 무효화는 반드시 Web 서버 내부에서 실행되도록 설계하였다. ② BO → API → Web Invalidation API 호출 게시글 생성/수정/삭제 성공 이후 BO가 호출한 백엔드 api는 Web의 invalidation API를 호출한다. 이를 통해 캐시 무효화 요청이 Web 서버로 전달된다. ③ Web 서버 내부에서 revalidateTag 실행 Web 서버 내부에서 revalidateTag를 호출하여 도메인 단위 캐시를 선택적응로 무효화한다. 게시글 상세는 도메인 단위 태그 + ID 단위 태그를 함께 사용했다. 전체 흐름은 다음과 같다. contracts 패키지에 revalidate 전략 포함하기 이 과정에서 contracts 패키지를 단순 타입 공유 계층으로 사용하지 않았다. 캐시 무효화 전략을 API 계약의 일부로 정의하였다. API contract는 다음과 같은 요소를 포함하였고, 캐시 전략을 위해서 여기에 revalidate 전략을 포함시켰다. HTTP Method Path Request Params / Body Response 타입 revalidate 즉, 특정 API가 호출되었을 때 어떤 캐시 태그가 무효화되어야 하는지까지 명시했다. 예시: UpdatePost Contract 설계 의도 이를 통해 다음과 같은 효과를 얻었다. BO와 Web이 동일한 무효화 규칙을 공유 도메인 단위 무효화 정책의 재사용 가능 캐시 전략을 명시적 설계 요소로 승격 결과적으로 contracts는 단순한 타입 패키지가 아니라 애플리케이션 간 계약을 정의하는 계층으로 확장되었다. 회고 이전에도 모노레포 구조를 구성해본 적은 있었지만, 두 개 이상의 애플리케이션을 동시에 개발하고 실제 데이터 흐름과 런타임 경계를 고려하며 설계한 것은 처음이었다. 특히 web, bo, api 세 애플리케이션과 contracts, ui 패키지가 유기적으로 연결된 구조에서 타입 공유만으로는 해결되지 않는 문제들이 드러났다. 같은 레포에 있다는 사실이 곧 실행 환경까지 공유한다는 의미는 아니었다. 런타임은 명확히 분리되어 있었고, 이번 캐시 이슈는 그 차이를 분명하게 보여준 사례였다. 또한 콘텐츠 운영 환경에서는 단순히 CRUD를 구현하는 것만으로는 충분하지 않다는 점을 알게 되었다. 데이터를 저장하는 것과 사용자 화면에 반영되는 것 사이에는 여러 계층이 존재한다. 이번 경험은 기능 구현을 넘어서 운영을 고려한 설계에 대해 고민하게 된 계기였다.

2026년 02월 19일 16:20

프론트엔드

URL을 상태로 관리한 방법: TanStack Router

인턴 기간 동안 백오피스 프로젝트에서 React Router 기반 레거시 코드를 마이그레이션 하는 작업을 맡았다. 단순히 마이그레이션 하기 보다는, 기존 JavaScript의 상태를 TypeScript로 마이그레이션 하면서 어떻게 런타임과 컴파일 타임 둘다 안정성을 잡을 수 있을지에 대한 고민이 많았다. 레거시 React Router 구조 기존 구조에서는 많은 필터 값을 url에 올리지 않고, 내부 상태로만 관리하고 있었다. 여기에서의 문제점은 사용자가 새로고침 시, 설정해놓은 필터 값들이 사라진다는 것이다. 따라서 UX를 향상하기 위해 사용자가 필터 값들을 새로고침 시에도 상태가 변경되지 않게 URL에 올리기로 결정하였다. URL로 상태를 옮기며 마주한 문제 ① 타입 안정성 저하 예를 들어 이런 URL이 들어올 수 있다 page, size는 number 타입이어야 하지만 실제로 사용자가 임의로 주소창에 입력한 값은 string이다. 사용자가 직접 값을 주입하였을 때, 서버로 값이 들어갈 수 있다는 점에서 라우터 단의 타입 관리가 필요해보였다. ② 상태의 기준점 모호 URL에 필터값을 올리는 방식을 채택하였을 때 필터 상태가 다음과 같이 여러 군데에 흩어져 있는 문제가 있다. useState로 관리되는 내부 상태 서버 요청 시 사용하는 query 객체 URL search 파라미터 따라서 이 기준점을 어디에 잡아야할지도 논의 및 합의가 필요한 부분이었다. 라우터 단에서 상태를 정규화하기 TanStack Router는 React Router와 달리 라우터 단에서 validateSearch를 제공한다. search 파라미터를 읽는 것과 동시에, 컴파일 및 런타임에서 zod로 검증 및 정규화를 수행할 수 있다. 라우터 단에서 다음의 기능을 수행하였다. 허용된 값만 통과 타입 자동 추론 실패 시 기본값 정규화 잘못된 쿼리는 서버에 전달되지 않음 상태 업데이트 규칙을 통일하기 URL 쿼리 값 정규화 문제는 해결하였지만, 가장 큰 문제는 필터가 있는 화면에서 코드가 계속 반복된다는 점이었다. 부분 업데이트 로직 기본값 처리 URL과 useState, URL과 서버 상태의 동기화 로직 이런 로직들이 페이지마다 반복되고 있었으며, 개발자마다 다른 로직을 사용하여 코드를 알아보기에도 쉽지 않았다. 이 문제는 useSearchParams 커스텀 훅을 만들어 해결하였다. useSearchParams Custom Hook 이 훅의 목적은 단순하다. search는 항상 타입 안전해야 하고 업데이트 방식은 팀 내에서 동일해야 한다. ① URL에 들어갈 수 있는 타입 제한 Date, 객체, Map 같은 값은 애초에 타입 단계에서 차단했다. search는 URL 직렬화 가능한 원시값만 가진다는 규칙을 강제했다. ② 업데이트 방식을 통일 이 구조로 인해서 페이지에서 search를 다루는 방식이 완전 동일해졌다. 부분 업데이트는 applySearch 전체 리셋은 resetSearch undefined는 무시 defaultSearch는 항상 기준점 적용 이후의 변화 이 커스텀 훅을 사용함으로써 다음의 변화가 생겼다. 프론트엔드 개발자 3명이 동일한 navigation 규칙 사용 필터 로직 중복 코드 약 40% 감소 공유, 새로고침, 뒤로가기 상황에서 상태 유지 신규 페이지 추가 시, navigation 로직 추가 간편화 회고 URL을 상태로 쓰는 건 생각보다 까다롭다. 페이지 내부 상태로만 관리할 때는 "내가 넣은 값만 존재한다"는 가정이 가능하지만, URL로 올리는 순간부터는 “누군가 아무 값이나 넣을 수 있다” 는 전제가 시작된다. 그래서 오히려 상태가 더 불안정해질 수도 있다. 재미있게도, 이런 종류의 문제는 결국 라이브러리 선택보다 팀 규칙의 문제다. 정규화 지점이 어디인지, 기본값이 무엇인지, 업데이트가 어떤 규칙을 따르는지. 이걸 문서가 아니라 코드로 강제했을 때 팀 생산성이 가장 크게 달라졌다. 전체 useSearchParams 코드 ts const { search, applySearch, resetSearch } = useSearchParams({ defaultSearch: { page: 1, keyword: '' }, Route, }) applySearch({ keyword: 'abc', page: 1 }) applySearch({ keyword: '' }) // keyword 제거 의도 applySearch({ tags: ['a', '', 'b'] }) // tags -> ['a','b'] applySearch({ tags: [] }) // tags 제거 의도 resetSearch() *

2026년 02월 12일 03:56

프론트엔드

토큰 만료(401)와 인터셉터 재설계 기록

백오피스를 마이그레이션하면서 가장 먼저 마주한 문제는 401이었다. 단순한 인증 에러처럼 보였지만, 실제로는 인증 흐름 전반을 다시 설계해야 하는 문제였다. 이번 글은 기존 코드를 그대로 사용하는 대신, Axios 인터셉터와 토큰 갱신 전략을 처음부터 다시 설계한 과정에 대한 기록이다. 기존 코드 사용으로 시작한 인터셉터 초기에는 기존 프로젝트의 Axios 인터셉터를 그대로 가져와 사용했다. 기능 자체는 동작했지만, 내부 동작 원리를 충분히 이해한 상태는 아니었다. 코드 리뷰에서 “구조를 이해하고 다시 설계해보자”는 피드백을 받았고, 그 시점에서 인터셉터의 전체 인증 흐름을 다시 정리하기 시작했다. 마침 면접 당시 받았던 질문이 떠올랐다. 사실 이번 인턴 면접에서 토큰 기반 인증에 관한 질문이 있었고, 한 페이지에서 여러 요청이 동시에 401이 발생하면 어떻게 처리할 것인가? 그때는 명확히 답하지 못했지만, 이번에 실제로 같은 상황을 마주하게 되었다. 첫 문제: 401 이후 403 예상 시나리오는 다음과 같았다. 오래된 at로 api 호출 서버가 401 던짐 인터셉터가 refresh 호출 새 토큰으로 요청 재시도 … 되어야 하는데 accessToken 만료 → 401 자동 refresh 호출 → 403 원인을 확인해보니 accessToken과 refreshToken의 만료 시간이 동일하게 설정되어 있었다. accessToken이 만료되는 시점에는 이미 refreshToken도 만료된 상태였다. at가 만료되었을 때 rt도 만료가되어 refresh를 할 수 있는 시간이 없었던 것이다. accessToken 만료 → refresh 요청 시도 근데 refreshToken도 이미 만료 → 403 그래서 당연히 403이 나는 구조였다. 근데 백엔드를 바꿔달라고 할 수가 없는 상황 이걸 계기로 오랜만에 조금 머리를 굴려보았다. 자동 재발급 전략 재설계 처음에는 이런 식의 자동 로직을 생각했다. accessToken 만료 5분 전에 자동으로 refresh 수행 하지만 백오피스 특성상, 사용자가 아무 행동을 하지 않아도 토큰을 갱신하는 방식은 적절하지 않았다. 그래서 방향을 바꿨다. 사용자가 실제 api 요청 시 만료 시간이 5분 이하라면 그때 refresh 하자 즉 활성 사용자만 재발급 → 비활성 사용자 세션은 종료 동시 요청 문제 → Pending Queue 도입 다음 문제는 이거였다. 이거는 내가 면접 때 질문을 받고 대답을 못했던 그 문제이기도 한데 A API 요청 시 → 토큰 만료 → refresh 시작 그 사이에 B API 요청 → 또 토큰 만료 → refresh 또 호출 이러면 refresh api가 동시에 여러번 호출되며 경합이 발생한다. 그래서 필수적으로 refresh 중엔 다른 요청을 멈춰서 줄세우는 로직이 필요하였다. 그것이 바로 pending queue ~~내가 자료구조 수업에서 듣고 코테 풀때만 쓰던 큐를 진짜 써보는 날이 오다니~~ 요약하자면 refresh 중이면 → 요청을 queue 에 쌓아둠 refresh 끝나면 → queue에 있던 요청들을 새 토큰으로 재요청 이렇게 하면 동시 재발급 요청 문제는 해결된다. 로그아웃 중복 실행 문제 refresh 실패 시 로그아웃을 처리하도록 구성했지만, 동시에 여러 API가 실패할 경우 alert가 여러 번 표시되는 문제가 발생했다. 이를 방지하기 위해 isLoggingOut 상태를 추가하여 로그아웃이 한 번만 실행되도록 제어했다. 또한 이 상태값이 persist에 저장되지 않도록 설정하여 재로그인 이후 정상 동작하도록 수정했다. 최종 Axios 인터셉터 구조 정리 최종적으로 인터셉터 구조는 다음과 같이 정리되었다. 요청 시 Authorization 자동 주입 만료 5분 이하 → refresh 시도 refresh 중에는 pending queue 대기 refresh 실패 → 단일 로그아웃 처리 상태 관리는 zustand와 연계 정리하자면.. 이번에 인터셉터를 처음부터 설계하면서 느낀 점: 토큰 구조를 이해해야 한다 세션 만료 UX까지 고려해야 한다 동시 요청 문제까지 잡아야 한다 상태관리까지 연계해야 한다 인터셉터는 그냥 훅 두 개가 아니라 프로젝트의 전체 인증 흐름을 담당하는 중요한 부분임을 깨달았다. 그리고 무엇보다 절대… 생각 안하고 코드 복붙부터 시작하지 말자.

2025년 11월 20일 00:33

프론트엔드

BO에서 글을 수정해도 Web이 안 바뀌는 이유: 모노레포 환경 캐시 전략

BO에서 글 수정 시 Web에 미반영 문제 관리자 페이지(BO)에서 게시글을 수정하면 DB 및 BO에는 즉시 반영된다. 문제는 수정 직후 Web에서 해당 글을 확인하면, 내용이 이전 버전으로 계속 노출되었다. 관리자에서 수정이 완료되었는데 Web에는 반영되지 않는 상황이기 때문에, 운영 흐름상 수정 직후 화면에 변경 사항이 보이지 않는 것은 문제가 될 수밖에 없다. Web은 Next.js App Router + fetch 기반 SSR 구조로 구성되어 있었고, App Router의 fetch는 서버 레벨 캐싱을 기본으로 동작한다. 이는 일반적인 클라이언트 캐싱 라이브러리(예: React Query)와는 동작 방식이 다르다. App Router 환경에서 fetch는 기본적으로 force-cache로 동작한다. 단순 새로고침은 서버 재요청을 의미하지 않는다. 데이터는 빌드 시점 혹은 ISR 주기에 따라 캐시된다. 명시적으로 무효화하지 않는 한 캐시는 유지된다. 따라서 DB가 갱신되더라도, Web 서버의 캐시가 무효화되지 않으면 화면에는 반영되지 않는다. Web과 BO가 분리된 모노레포 구조 프로젝트는 pnpm 기반 모노레포로 구성되어 있다. 구조적으로 다음과 같은 특성을 가진다. web: SSR 기반 콘텐츠 렌더링 bo: CRUD 중심 관리자 기능 api: 데이터 처리 백엔드 이 세 애플리케이션은 같은 레포에 존재하지만, 런타임은 완전히 독립적으로 동작한다. 즉, 모노레포는 코드 레벨의 공유를 의미할 뿐, 런타임 공유를 의미하지 않는다. BO에서 게시글을 수정하면 DB는 갱신 되지만, Web 서버 내부 캐시는 별도의 프로세스로 유지된다. 따라서 BO에서 직접 revalidate를 호출하더라도 Web 서버의 캐시는 무효화되지 않는다. 설계한 캐시 무효화 전략 ① Web에 invalidation 전용 API route 생성 Web 서버 내부에서만 실행되는 캐시 무효화 엔드포인트를 별도로 구성하였다. 캐시 무효화는 반드시 Web 서버 내부에서 실행되도록 설계하였다. ② BO → API → Web Invalidation API 호출 게시글 생성/수정/삭제 성공 이후 BO가 호출한 백엔드 api는 Web의 invalidation API를 호출한다. 이를 통해 캐시 무효화 요청이 Web 서버로 전달된다. ③ Web 서버 내부에서 revalidateTag 실행 Web 서버 내부에서 revalidateTag를 호출하여 도메인 단위 캐시를 선택적응로 무효화한다. 게시글 상세는 도메인 단위 태그 + ID 단위 태그를 함께 사용했다. 전체 흐름은 다음과 같다. contracts 패키지에 revalidate 전략 포함하기 이 과정에서 contracts 패키지를 단순 타입 공유 계층으로 사용하지 않았다. 캐시 무효화 전략을 API 계약의 일부로 정의하였다. API contract는 다음과 같은 요소를 포함하였고, 캐시 전략을 위해서 여기에 revalidate 전략을 포함시켰다. HTTP Method Path Request Params / Body Response 타입 revalidate 즉, 특정 API가 호출되었을 때 어떤 캐시 태그가 무효화되어야 하는지까지 명시했다. 예시: UpdatePost Contract 설계 의도 이를 통해 다음과 같은 효과를 얻었다. BO와 Web이 동일한 무효화 규칙을 공유 도메인 단위 무효화 정책의 재사용 가능 캐시 전략을 명시적 설계 요소로 승격 결과적으로 contracts는 단순한 타입 패키지가 아니라 애플리케이션 간 계약을 정의하는 계층으로 확장되었다. 회고 이전에도 모노레포 구조를 구성해본 적은 있었지만, 두 개 이상의 애플리케이션을 동시에 개발하고 실제 데이터 흐름과 런타임 경계를 고려하며 설계한 것은 처음이었다. 특히 web, bo, api 세 애플리케이션과 contracts, ui 패키지가 유기적으로 연결된 구조에서 타입 공유만으로는 해결되지 않는 문제들이 드러났다. 같은 레포에 있다는 사실이 곧 실행 환경까지 공유한다는 의미는 아니었다. 런타임은 명확히 분리되어 있었고, 이번 캐시 이슈는 그 차이를 분명하게 보여준 사례였다. 또한 콘텐츠 운영 환경에서는 단순히 CRUD를 구현하는 것만으로는 충분하지 않다는 점을 알게 되었다. 데이터를 저장하는 것과 사용자 화면에 반영되는 것 사이에는 여러 계층이 존재한다. 이번 경험은 기능 구현을 넘어서 운영을 고려한 설계에 대해 고민하게 된 계기였다.

2026년 02월 19일 16:20

프론트엔드

URL을 상태로 관리한 방법: TanStack Router

인턴 기간 동안 백오피스 프로젝트에서 React Router 기반 레거시 코드를 마이그레이션 하는 작업을 맡았다. 단순히 마이그레이션 하기 보다는, 기존 JavaScript의 상태를 TypeScript로 마이그레이션 하면서 어떻게 런타임과 컴파일 타임 둘다 안정성을 잡을 수 있을지에 대한 고민이 많았다. 레거시 React Router 구조 기존 구조에서는 많은 필터 값을 url에 올리지 않고, 내부 상태로만 관리하고 있었다. 여기에서의 문제점은 사용자가 새로고침 시, 설정해놓은 필터 값들이 사라진다는 것이다. 따라서 UX를 향상하기 위해 사용자가 필터 값들을 새로고침 시에도 상태가 변경되지 않게 URL에 올리기로 결정하였다. URL로 상태를 옮기며 마주한 문제 ① 타입 안정성 저하 예를 들어 이런 URL이 들어올 수 있다 page, size는 number 타입이어야 하지만 실제로 사용자가 임의로 주소창에 입력한 값은 string이다. 사용자가 직접 값을 주입하였을 때, 서버로 값이 들어갈 수 있다는 점에서 라우터 단의 타입 관리가 필요해보였다. ② 상태의 기준점 모호 URL에 필터값을 올리는 방식을 채택하였을 때 필터 상태가 다음과 같이 여러 군데에 흩어져 있는 문제가 있다. useState로 관리되는 내부 상태 서버 요청 시 사용하는 query 객체 URL search 파라미터 따라서 이 기준점을 어디에 잡아야할지도 논의 및 합의가 필요한 부분이었다. 라우터 단에서 상태를 정규화하기 TanStack Router는 React Router와 달리 라우터 단에서 validateSearch를 제공한다. search 파라미터를 읽는 것과 동시에, 컴파일 및 런타임에서 zod로 검증 및 정규화를 수행할 수 있다. 라우터 단에서 다음의 기능을 수행하였다. 허용된 값만 통과 타입 자동 추론 실패 시 기본값 정규화 잘못된 쿼리는 서버에 전달되지 않음 상태 업데이트 규칙을 통일하기 URL 쿼리 값 정규화 문제는 해결하였지만, 가장 큰 문제는 필터가 있는 화면에서 코드가 계속 반복된다는 점이었다. 부분 업데이트 로직 기본값 처리 URL과 useState, URL과 서버 상태의 동기화 로직 이런 로직들이 페이지마다 반복되고 있었으며, 개발자마다 다른 로직을 사용하여 코드를 알아보기에도 쉽지 않았다. 이 문제는 useSearchParams 커스텀 훅을 만들어 해결하였다. useSearchParams Custom Hook 이 훅의 목적은 단순하다. search는 항상 타입 안전해야 하고 업데이트 방식은 팀 내에서 동일해야 한다. ① URL에 들어갈 수 있는 타입 제한 Date, 객체, Map 같은 값은 애초에 타입 단계에서 차단했다. search는 URL 직렬화 가능한 원시값만 가진다는 규칙을 강제했다. ② 업데이트 방식을 통일 이 구조로 인해서 페이지에서 search를 다루는 방식이 완전 동일해졌다. 부분 업데이트는 applySearch 전체 리셋은 resetSearch undefined는 무시 defaultSearch는 항상 기준점 적용 이후의 변화 이 커스텀 훅을 사용함으로써 다음의 변화가 생겼다. 프론트엔드 개발자 3명이 동일한 navigation 규칙 사용 필터 로직 중복 코드 약 40% 감소 공유, 새로고침, 뒤로가기 상황에서 상태 유지 신규 페이지 추가 시, navigation 로직 추가 간편화 회고 URL을 상태로 쓰는 건 생각보다 까다롭다. 페이지 내부 상태로만 관리할 때는 "내가 넣은 값만 존재한다"는 가정이 가능하지만, URL로 올리는 순간부터는 “누군가 아무 값이나 넣을 수 있다” 는 전제가 시작된다. 그래서 오히려 상태가 더 불안정해질 수도 있다. 재미있게도, 이런 종류의 문제는 결국 라이브러리 선택보다 팀 규칙의 문제다. 정규화 지점이 어디인지, 기본값이 무엇인지, 업데이트가 어떤 규칙을 따르는지. 이걸 문서가 아니라 코드로 강제했을 때 팀 생산성이 가장 크게 달라졌다. 전체 useSearchParams 코드 ts const { search, applySearch, resetSearch } = useSearchParams({ defaultSearch: { page: 1, keyword: '' }, Route, }) applySearch({ keyword: 'abc', page: 1 }) applySearch({ keyword: '' }) // keyword 제거 의도 applySearch({ tags: ['a', '', 'b'] }) // tags -> ['a','b'] applySearch({ tags: [] }) // tags 제거 의도 resetSearch() *

2026년 02월 12일 03:56

CS

[정보처리기사] 정보시스템 구축관리

정보시스템 구축관리 소프트웨어 생명주기 / 개발 모델 소프트웨어 생명주기 모델 비용 산정 / 일정 관리 비용 산정 모델 COCOMO 유형 표준 / 품질 모델 ISO / 국제 표준 품질 특성 (ISO 9126) 프로세스 성숙도 / 평가 모델 SPICE CMMI 테스트 / 품질 보증 테스트 종류 알파 / 베타 테스트 보안 / 접근 제어 접근 제어 모델 네트워크 / 보안 시스템 IPSec 네트워크에서의 안전한 연결을 설정하기 위한 통신 규칙 또는 프로토콜 세트 보안 장비 클라우드 / 신기술 악성코드 / 보안 위협 (Malware) 네트워크 공격 / 서비스 거부 공격 (DoS) 서비스 거부 공격(DoS / DDoS) DoS (Denial of Service) 하나의 공격자가 시스템 자원을 고갈시켜 정상 서비스 불가 상태로 만듦 DDoS (Distributed DoS) 여러 대의 좀비 PC가 동시에 공격 분산 공격, 탐지 및 차단 어려움

2026년 02월 04일 16:26

CS

[정보처리기사] 비트 연산자, 연산자 우선순위

비트 연산자 & 연산자 우선순위 비트 연산자란? 비트 연산자는 정수 값을 2진수(비트) 단위로 변환한 뒤 연산을 수행하는 연산자이다. 즉, 숫자를 그대로 계산하는 게 아니라 0과 1로 바꿔서 한 칸씩 비교한다. 비트 연산자 종류와 의미 2.1 AND 연산자 & 두 비트가 모두 1일 때만 1 그 외는 모두 0 예제 👉 결과: 4 2.2 OR 연산자 | 둘 중 하나라도 1이면 1 예제 👉 결과: 7 2.3 XOR 연산자 ^ 서로 다를 때만 1 같으면 0 예제 👉 결과: 3 2.4 NOT 연산자 ~ 모든 비트를 반전 0 → 1, 1 → 0 예제 4 → 00000100 ~4 → 11111011 👉 결과: -5 (2의 보수 체계) 2.5 시프트 연산자 > 왼쪽 시프트 > 비트를 오른쪽으로 이동 2로 나누는 효과 예제 👉 결과: 2 연산자 우선순위란? 하나의 식에 여러 연산자가 있을 때 어떤 연산을 먼저 계산할지 정해 놓은 규칙이다. 📌 핵심 우선순위가 높을수록 먼저 계산된다. 괄 → 단 → 산 → 시 → 관 → 비 → 논 → 대 ( ) 단항 ++ -- ! ~ 산술 \* / % + - 시프트 > 관계 >= 비트 & ^ | 논리 && 8. 대입 =

2026년 02월 04일 15:27

CS

[정보처리기사] Database

데이터베이스(Database) 데이터베이스란 무엇인가? 데이터베이스(Database)는 여러 사용자가 공동으로 사용하는 데이터의 집합이다. 단순히 데이터를 저장하는 것이 목적이 아니라, 데이터의 중복을 줄이고 데이터 간 일관성을 유지하며 여러 사용자가 동시에 안전하게 접근할 수 있도록 관리하는 것이 핵심이다. 이러한 관리를 담당하는 소프트웨어를 DBMS(Database Management System) 라고 한다. 데이터베이스 설계가 필요한 이유 현실 세계의 데이터를 그대로 저장하면 다음과 같은 문제가 발생한다. 동일한 데이터가 여러 곳에 저장됨 → 중복 발생 한 곳만 수정되어 데이터 불일치 발생 일부 데이터를 삭제했는데, 다른 데이터까지 사라짐 이러한 문제를 방지하기 위해 체계적인 설계 과정을 통해 데이터 구조를 정의해야 한다. 데이터베이스 설계 단계 데이터베이스 설계는 다음과 같은 순서로 진행된다. 요구사항 분석 → 개념 설계 → 논리 설계 → 물리 설계 3.1 요구사항 분석 사용자가 어떤 데이터를 필요로 하고, 어떤 처리가 이루어져야 하는지를 분석하는 단계이다. 어떤 정보가 필요한가? 데이터 간 관계는 무엇인가? 3.2 개념 설계 (Conceptual Design) 현실 세계를 개념적으로 모델링하는 단계이다. 엔터티(Entity) 속성(Attribute) 관계(Relationship) 를 정의하며, 주로 ER 다이어그램(ERD) 을 사용한다. 이 단계에서는 DBMS 종류나 구현 방식은 고려하지 않는다. 3.3 논리 설계 (Logical Design) 개념 설계를 바탕으로 DBMS에서 사용할 논리적 구조를 설계하는 단계이다. 테이블 정의 속성 및 키 정의 정규화 수행 즉, 정규화는 논리 설계 단계에서 수행된다. 3.4 물리 설계 (Physical Design) 논리 설계 결과를 실제 저장 장치에 맞게 구현하는 단계이다. 저장 구조 인덱스 접근 경로 성능(처리량, 응답 시간) 을 고려한다. 👉 물리 설계의 핵심은 성능 최적화이다. 키(Key)의 개념 데이터베이스에서 키는 튜플을 유일하게 식별하기 위한 속성 또는 속성의 집합이다. 주요 키 종류 슈퍼키(Super Key): 유일성은 만족하지만 최소성은 만족하지 않음 후보키(Candidate Key): 유일성과 최소성을 모두 만족 기본키(Primary Key): 후보키 중 선택된 하나 대체키(Alternate Key): 기본키로 선택되지 않은 후보키 외래키(Foreign Key): 다른 테이블의 기본키를 참조 정규화(Normalization) 정규화는 데이터의 중복을 제거하고 이상 현상을 방지하기 위한 과정이다. 정규화를 통해 다음과 같은 이상 현상을 방지할 수 있다. 삽입 이상 삭제 이상 갱신 이상 5.1 제1정규형 (1NF) 모든 속성 값은 원자값(Atomic Value) 을 가져야 한다. 즉, 하나의 속성에 여러 값이 들어가면 안 된다. 5.2 제2정규형 (2NF) 제1정규형을 만족하면서 부분 함수 종속을 제거한 상태이다. 복합 기본키의 일부에만 의존하는 속성이 존재하면 이를 분리해야 한다. 5.3 제3정규형 (3NF) 제2정규형을 만족하면서 이행 함수 종속을 제거한 상태이다. 기본키가 아닌 속성이 다른 속성을 결정하면 안 된다. 5.4 BCNF 제3정규형보다 더 엄격한 정규형으로, 모든 결정자가 후보키여야 한다. 함수 종속(Function Dependency) 속성 X의 값이 결정되면 속성 Y의 값이 항상 하나로 결정될 때, 이를 Y는 X에 함수적으로 종속된다고 하며 다음과 같이 표기한다. X → Y 관계대수와 관계해석 관계대수(Relational Algebra) 절차적 질의 언어 연산의 순서를 명시 관계 연산의 집합 관계해석(Relational Calculus) 비절차적 질의 언어 원하는 결과만 기술

2026년 02월 02일 15:31