STORAGE & POLICY · 2026.10.12

저장한 다음도
생각해야 합니다

파일 저장과 관리: 위치·권한·용량·비용.
첨부부터 삭제까지, Cursor에서 정책을 확인합니다.

파일 첨부 버튼을 만드는 것으로 끝나지 않습니다. 어디에 저장하고, 누가 볼 수 있고, 얼마나 보관하고, 언제 지울지 정해야 합니다. 저장량이 늘면 비용도 늘어납니다. 이번에는 파일과 메타데이터를 나누고 용량 정책이 실제로 작동하는 작은 웹앱을 만듭니다.

01 · 지난주에서 이어가기

구분 기존 학습 이번 연결
복습 · W19 CSV Worker와 파일 저장 프로그램을 종료해도 파일은 남음
확장 · W20 인증과 인가 파일을 찾는 것과 볼 권한을 확인하는 것은 별개
확장 · W21 TinyDB의 JSON 저장 DB에는 파일 자체 대신 파일을 설명하는 정보 기록
NEW 스토리지·객체·버킷·키 파일 보관 위치와 이름을 관리
NEW 용량·보관·삭제·비용 정책 계속 쌓이는 데이터를 운영하는 규칙

지난주 완료 상태는 TinyDB 터미널 프로그램입니다. 이번 실습은 새 폴더에서 실행하는 독립적인 Flask·TinyDB 웹앱입니다. 이전 파일을 옮기거나 로그인 앱을 합칠 필요가 없습니다. 가상 작업 3개를 함께 제공합니다.

02 · 파일 본문과 메타데이터

메타데이터는 데이터를 설명하는 정보입니다. 설문 PDF의 실제 내용과 “어떤 작업에 첨부했는지, 원본 이름과 크기가 무엇인지”를 나누어 생각합니다.

브라우저에서 파일 선택
  → 서버가 파일 종류·크기·전체 용량 검사
  → storage/에 파일 본문 저장
  → TinyDB에 작업 ID·파일명·저장 키·크기·등록 시각 기록
  → 목록에서 파일 ID로 다운로드 요청
  → 서버가 저장 키로 파일을 찾아 전달
위치 저장하는 내용 예시
storage/ 파일 본문 임의의저장키.pdf
data/db.json 메타데이터 원본 이름, 저장 키, 작업 ID, 크기, 시각
다운로드 URL 서버에 파일을 요청하는 주소 /api/files/파일ID/download

저장 키·로컬 경로·다운로드 URL은 서로 다릅니다. 원본 이름이 같아도 다른 저장 키를 부여하면 덮어쓰지 않습니다. 파일을 DB에 넣는 방식도 가능하지만, 이번에는 파일 보관과 검색용 메타데이터 관리를 분리합니다. “이미지는 절대 DB에 넣으면 안 된다”는 절대 규칙은 아닙니다.

로컬 저장도 유효한 선택입니다. 서버 프로그램을 끈다고 파일이 사라지지는 않습니다. 다만 여러 서버가 같은 파일을 보거나, 임시 디스크를 사용하는 배포 환경에 올리거나, 백업·복구를 운영할 때는 추가 설계가 필요합니다.

03 · S3와 객체 스토리지

스토리지(storage)는 데이터를 보관하는 저장소입니다. Amazon S3는 AWS가 제공하는 대표적인 객체 스토리지 서비스입니다. 이번에는 개념만 배우고 계정이나 클라우드 자원을 만들지 않습니다.

용어 뜻 비유
버킷(bucket) 객체를 담는 논리적 공간 창고
객체(object) 데이터와 메타데이터를 가진 저장 단위 물건 상자
키(key) 버킷 안에서 객체를 구별하는 이름 상자의 이름표

S3의 일반적인 버킷에서 jobs/2026/report.pdf는 객체 키입니다. 슬래시와 접두사로 폴더처럼 표시하지만 로컬 디렉터리와 같은 구조로 이해하지 않습니다. 서비스·버킷 종류에 따른 차이가 있으므로 모든 스토리지가 똑같다고 일반화하지 않습니다. S3 객체 키 설명

이번 로컬 storage/는 실제 폴더입니다. S3를 흉내 낸 클라우드 서비스가 아닙니다. 본문은 저장소에, 위치와 설명은 DB에 두는 역할 분리를 체험하는 것입니다.

04 · 저장에도 정책이 필요하다

“업로드할 수 있다”와 “계속 보관해도 된다”는 다른 결정입니다. 용량을 늘릴 수 있어도 비용·보관 책임·복구 부담이 함께 늘어납니다. 개발 전에 업무 담당자와 다음 규칙을 정합니다.

정책 정할 질문 예시와 주의점
업로드 제한 어떤 형식, 파일 하나 최대 얼마인가? PDF·이미지 허용, 불필요한 큰 영상은 제한
할당량 사용자·프로젝트·전체에 얼마를 허용하는가? 한도 초과 시 거부·승인 요청 등 행동까지 결정
보관 기간 임시 자료·완료 작업·장기 보관 자료를 언제 정리하는가? 업무 목적에 따라 다르게 정하고 일괄 삭제하지 않기
삭제 누가 지울 수 있고, 언제 실제 파일을 지우는가? 확인 절차, DB 기록과 본문 정리, 보류 대상 정의
복구 휴지통·백업·이전 버전을 얼마나 유지하는가? 복구 가능 기간 동안 추가 저장 비용 발생
비용 감시 누가 사용량을 확인하고 초과 시 대응하는가? 알림 수신자와 대응 담당자를 함께 지정

사용량을 먼저 예상하기

매달 1,000개 파일을 받고 평균 크기가 2MB라면, 새로 쌓이는 양은 대략 월 2GB입니다. 처음 비어 있고 삭제 없이 12개월을 보관하면 약 24GB가 됩니다. 3개월 지난 파일을 정리하는 정책이라면 안정화 이후 약 6GB 규모를 예상할 수 있습니다. 유입량이 일정하다는 단순 예시이며 백업·이전 버전은 별도로 더해야 합니다.

같은 2MB 파일을 월 10,000회 다운로드하면 전송량은 약 20GB입니다. 저장된 양이 작아도 다운로드가 많으면 전송 비용이 커질 수 있습니다. 여기서는 MB·GB를 십진 단위로 계산하며 실제 요금표의 단위를 확인합니다.

돈은 어디에서 나갈까?

제공사·리전·저장 등급·무료 제공 조건마다 다릅니다. “1GB는 무조건 얼마”라는 고정 가격으로 외우지 않습니다. S3 공식 요금 항목

예산 알림은 자동 과금 차단과 다릅니다. 알림만 설정해 두면 업로드가 계속될 수 있고 비용 정보 반영에도 지연이 있습니다. 앱의 용량 제한, 사용량 점검, 알림 후 대응 절차를 함께 정합니다. AWS Budgets 안내

보관 기간이 지나면 어떻게 할까?

수명 주기(lifecycle) 정책으로 대상 파일을 저렴한 저장 등급으로 옮기거나 만료 후 삭제할 수 있습니다. 조회 빈도·복구 시간·최소 보관 기간도 고려합니다. 버전 관리가 켜진 경우 화면에서 삭제해도 이전 버전이 남아 비용이 계속 발생할 수 있으므로 함께 설계합니다. 정책을 설명하는 것과 자동 정리를 실제 설정하는 것은 다릅니다. S3 수명 주기

05 · 전달과 접근 권한

CDN은 사용자 가까운 서버에서 콘텐츠를 전달하도록 돕는 네트워크입니다. 캐시는 받은 데이터를 재사용하기 위해 보관한 사본입니다. 저장소의 보관 기능과 CDN의 전달 기능은 다릅니다. 캐시는 삭제·갱신·비공개 데이터 처리도 함께 고려해야 합니다.

공개 파일은 URL로 접근할 수 있지만, 비공개 파일은 접근 권한 확인이 필요합니다. 서명 URL(presigned URL)은 특정 객체와 작업에 대해 제한된 시간 동안 사용할 수 있는 주소입니다. 권한을 확인한 서버가 발급하며, 주소를 가진 사람도 사용할 수 있으므로 공유 범위에 주의합니다. 만료되었다고 원본 파일이 삭제되는 것은 아닙니다.

DB에는 버킷·저장 키 같은 안정적인 위치를 기록하고 필요할 때 서명 URL을 발급하는 구성을 사용할 수 있습니다. 만료되는 URL을 영구 저장 위치로 취급하지 않습니다. S3 서명 URL

이번 앱에는 로그인·사용자별 권한·CDN·서명 URL이 없습니다. localhost에서 본인이 테스트하는 앱이며 다운로드는 Flask 서버가 직접 제공합니다. W20의 소유자 검사는 실제 서비스로 확장할 때 별도로 연결해야 합니다.

06 · Cursor에서 첨부파일 보관소 만들기

  1. 새 storage-lab 폴더를 만들고 Cursor의 File → Open Folder로 엽니다.
  2. Terminal → New Terminal에서 py --version으로 Python 3.10 이상인지 확인합니다. py가 없다면 python으로 확인하고 첫 명령을 바꿉니다.
  3. Agent에 아래 프롬프트를 붙여 넣습니다. 이전 주차 앱은 필요하지 않습니다.

Python 3.10+, Flask==3.1.3, tinydb==4.9.0으로 로컬 작업 첨부파일 보관소를 만들어줘.
파일: app.py, templates/index.html, sample-jobs.json, make_samples.py,
requirements.txt, README.md, .gitignore.
작업 3개: job-001 설문 CSV 검토, job-002 누락값 확인, job-003 월간 리포트 작성.
최초 DB 생성에만 작업을 넣어. 기존 파일이 손상되면 자동 초기화하지 마.

화면: 사용/전체/남은 용량, 적용 정책, 작업 선택, 파일 선택, 첨부 버튼,
작업별 첨부 목록, 원본 이름·작업·크기·등록 시각, 다운로드와 삭제 버튼.
화면에 이름을 넣을 때 textContent를 사용해. 빈 목록·서버 오류도 안내해.

본문은 app.py 기준 storage/, 메타데이터는 data/db.json에 저장해.
TinyDB에 jobs와 files 테이블을 사용해. 파일 기록은 file_id, job_id,
original_name, storage_key, size, created_at으로 구성해.
저장 키는 UUID와 확장자로 만들고 이름이 같은 파일도 덮어쓰지 마.
원본 이름은 별도 보관해 한글·공백도 다운로드 시 유지해.

정책 상수: 개별 5MiB, 전체 10MiB, pdf/png/jpg/txt/csv 허용.
MiB는 1024*1024바이트. 전체 사용량은 storage/ 실제 파일 합계로 계산해.
전체 한도는 DB·샘플·가상환경 용량과 별개인 교육용 첨부파일 한도야.
업로드는 한 파일씩, 빈 파일·없는 작업·잘못된 형식은 400,
개별 크기 초과는 413, 총량 초과는 409와 한국어 이유를 반환해.
초과하면 저장 전에 거부하고 기존 파일을 자동 삭제하거나 한도를 늘리지 마.

GET /api/jobs: 작업 목록
GET /api/storage: 적용 정책과 used_bytes/remaining_bytes/total_limit_bytes/file_limit_bytes
POST /api/files: multipart file과 job_id로 첨부
GET /api/files: 목록, 선택적 job_id 필터
GET /api/files/파일ID/download: 파일 다운로드
DELETE /api/files/파일ID: 파일과 메타데이터 삭제

삭제 전 파일명을 보여주고 되돌릴 수 없음을 확인받아.
실제 파일 삭제 후 메타데이터를 지워. 파일이 이미 없으면 기록만 정리해.
파일 삭제 실패 시 기록 유지, DB 정리 실패 시 재시도 가능하게 해.
저장 오류를 성공으로 표시하지 마. 실패한 새 파일은 가능한 범위에서 정리해.
JSON 쓰기는 임시 파일 작성 후 교체해. 파일+DB가 하나의 트랜잭션이라고 설명하지 마.
저장 키 검증과 경로 검사로 storage 밖 접근·심볼릭 링크 접근을 막아.
파일 미리보기는 하지 말고 attachment로 내려줘. 확장자 검사는 내용 검사가 아니야.

127.0.0.1:5000, debug=False, threaded=False, 단일 프로세스로 실행해.
Host는 127.0.0.1:5000, 변경 요청 Origin은 http://127.0.0.1:5000만 허용해.
로그인·S3·서명 URL·자동 삭제·백업은 구현하지 말고 실습 범위를 표시해.
make_samples.py는 quota-samples/에 4MiB txt 3개를 만들고 기존 파일은 보존해.
.gitignore에 .venv/, __pycache__/, storage/, data/, quota-samples/를 넣어.
README에 설치·실행·용량 초과·삭제 후 재첨부·재시작 검증 순서를 써줘.

설치와 실행

py -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe app.py

한 줄씩 실행하고 서버 터미널을 켜 둔 상태에서 http://127.0.0.1:5000을 엽니다. 가상환경 활성화는 필요하지 않습니다. 종료는 Ctrl+C, 재실행은 마지막 명령입니다. 처음 패키지를 설치할 때 인터넷이 필요합니다. 서버는 하나만 실행하세요.

막히면 완성 예제 ZIP을 풀고 app.py가 있는 폴더를 열어 같은 명령을 실행하세요. 실행 안내 · 서버 코드 · 용량 샘플 생성 코드

07 · 용량 초과와 삭제를 직접 확인하기

먼저 작은 가상 파일로 첨부·다운로드를 확인합니다. storage/에는 파일 본문, data/db.json에는 이름·키·크기만 있는지 보세요. 파일명은 같아도 다른 키로 저장됩니다. 재시작해도 목록과 파일은 남습니다.

용량 실습은 기존 첨부파일을 화면에서 정리한 뒤 진행합니다. 서버를 Ctrl+C로 종료하고 샘플을 생성한 다음 다시 실행합니다.

.\.venv\Scripts\python.exe make_samples.py
.\.venv\Scripts\python.exe app.py
단계 실행 확인
1 quota-samples/sample-1.txt 첨부 사용 4MiB / 남은 6MiB
2 sample-2.txt 첨부 사용 8MiB / 남은 2MiB
3 sample-3.txt 첨부 총량 초과 거부, 사용 8MiB 유지
4 첫 파일 삭제 질문 취소 파일·용량 그대로
5 첫 파일 삭제 확인 사용 4MiB / 남은 6MiB
6 세 번째 파일 다시 첨부 성공, 사용 8MiB
7 서버 재시작 후 조회·다운로드 기록·실제 파일·용량 유지

개별 제한과 전체 제한은 다릅니다. 4MiB 파일은 개별 제한을 통과해도 남은 공간이 2MiB면 거부됩니다. 5MiB·10MiB는 교육용 상한으로, 디스크 전체 크기나 클라우드 무료 제공량이 아닙니다. 정책 상수는 app.py에서 확인합니다.

실습과 실제 운영의 차이

이번 삭제는 되돌릴 수 없고 자동 삭제·백업이 없습니다. 실제 서비스는 보관 목적과 복구 요구를 먼저 정해야 합니다. 파일 본문과 DB 수정은 하나의 트랜잭션이 아니므로 비정상 종료 후 남은 파일과 기록을 점검하는 운영도 필요합니다. 프로그램 실행 중 파일이나 DB를 직접 수정하지 마세요.

막혔을 때

증상 확인
파일·모듈을 못 찾음 app.py 폴더와 .venv의 Python으로 설치·실행했는지 확인
접속 안 됨 서버 터미널이 실행 중인지, 정확한 127.0.0.1 주소인지 확인
포트 사용 중 지난 실습 서버를 해당 터미널에서 Ctrl+C로 종료
개별 파일은 작은데 첨부 거부 전체 사용량과 남은 용량 확인
다운로드 404 실제 파일 존재 여부 확인, 남은 기록은 삭제 가능
삭제·저장 오류 성공으로 간주하지 않고 권한·DB 상태 확인 후 재시도
DB 손상 서버 종료 후 백업·확인. 자동 초기화하지 않음

08 · 다음 질문: 다른 컴퓨터에서도 쓰려면?

이번 완료 상태는 로컬 Flask·TinyDB 첨부파일 보관소입니다. 클라우드 DB·로그인·객체 스토리지를 모두 완성한 것이 아닙니다. 다음 주는 배포 환경에서 파일이 어디에 남고, 다른 사용자와 어떻게 공유하며, 비용과 권한 정책을 어떻게 유지할지 살펴봅니다. 정적 교육 사이트에 Python 파일을 올리는 것만으로 서버가 실행되지는 않습니다.

← 21주차 DB와 TinyDB · 전체 학습 노트