Skip to content
dlsnfl0615 edited this page Jul 29, 2026 · 1 revision

📄 원본(노션): https://app.notion.com/p/3ab19935b8ae80c0bad2d39f2437c646 — 최종 동기화 2026-07-29

07-29 (2주차 수)

  • 회의 날짜: 2026-07-29
  • 점심: 또또벅스🐷

아침 스크럼

  • 석희
    • PR 올린거 통합하고, 부르미 도메인 시작, workflow만들어서 ec2 연동하기
  • 현성
    • 매칭 마무리, api 만들기, 배송 상태관리 설계
  • 동혁
    • 매칭 시스템 mvp 완성, 히어로 이미지 수정, 아키텍쳐 이미지 업로드
  • 현서
    • 회의록 연동 고치지, s3 확정 후 만들기

안건

  1. 로컬 H2 DB용 application.properties 양식 통합 각자 개발 환경 .env 공유

  2. 에이전트 md

    석희님이 만드심.

  3. 이미지 업로드 - presignedURL vs 서버로 이미지를 보내고 서버에서 S3로 석희

    • presigned
      • 서버 부하가 줄어든다.
      • → 2개를 순차적으로 업로드 + API 생각하기가 어려움. 어차피 사진 업로드가 핵심은 아니니까…
    • server
      • webflux
        • 이걸 위해서 새로 도입?
      • 블로킹 I/O

    업데이트 (07-29): upload 로직 자체는 presignedURL로 구현. 단 클라이언트에서 호출할 때 upload, 실제 api를 두 개로 나눠서 보내도록 구현.

  • ✅ aws mysql이 안되는 이슈가 있음.

    그냥 인터넷이 느린거였음.

  • ✅ Action on saves properties 비활성화
  • ✅ 프로젝트 멘토링 질문 정리 프로젝트 관련 멘토링 질문 — 프로젝트 관련 멘토에게 하고 싶은 질문을 남겨주세요. 여러개인 경우 불릿으로 나누어서 질문해주세요. (프로젝트와 관계없는 질문은 하지 마세요)
    • 저녁회고 할 때 정리 할 예정

(Github Discussion 기준)

오늘 스케줄

10:30 ~ 12:30 직무 교육


현성 동혁 mysql 실습
-- 1. 사용자 테이블 (users)
CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(100) NOT NULL UNIQUE,
    nickname VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    -- 닉네임 검색 최적화를 위한 인덱스 (이메일은 UNIQUE 제약조건으로 자동 인덱싱됨)
    INDEX idx_nickname (nickname)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 2. 게시글 테이블 (posts)
CREATE TABLE posts (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    -- 외래키 제약조건
    CONSTRAINT fk_posts_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,

    -- 목록 페이지네이션 최적화 (최신순 정렬 조회용 복합 인덱스)
    INDEX idx_created_at_id (created_at DESC, id DESC),

    -- 특정 사용자가 작성한 글 조회용 인덱스
    INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 3. 댓글 테이블 (comments)
CREATE TABLE comments (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    post_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    -- 외래키 제약조건
    CONSTRAINT fk_comments_post FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE,
    CONSTRAINT fk_comments_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,

    -- 특정 게시글의 댓글 목록 조회 최적화 (게시글 ID + 생성일 순 정렬)
    INDEX idx_post_created (post_id, created_at ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
파이썬 스크립트 v1
import time
import random
import pymysql

# --- DB 연결 설정 (본인 환경에 맞게 수정) ---
DB_CONFIG = {
    'host': '127.0.0.1',
    'port': 3306,
    'user': 'user',
    'password': 'your_password',
    'db': 'prac',
    'charset': 'utf8mb4',
    'autocommit': False
}

# --- 생성할 데이터 수량 ---
USER_COUNT = 1_000_000
POST_COUNT = 2_000_000
COMMENT_COUNT = 3_000_000

BATCH_SIZE = 10_000  # 1회 커밋당 처리할 행 수

# 샘플 텍스트 데이터 (데이터 생성 속도 최적화를 위해 단순 조합 활용)
LOREM_TITLES = [
    "안녕하세요 첫 글입니다.", "MySQL 대용량 데이터 테스트", "Spring Boot 개발 질문",
    "오늘의 뉴스 공유합니다.", "데이터베이스 인덱스 최적화 팁", "성능 테스트 진행 중",
    "이 에러 어떻게 해결하나요?", "자유게시판 질문입니다.", "추천 알고리즘 구현 방법"
]

LOREM_CONTENTS = [
    "게시글 본문 내용입니다. 테스트를 위해 대용량 데이터를 생성하고 있습니다.",
    "인덱스 설계와 페이지네이션 성능을 측정하기 위한 가상 데이터입니다.",
    "Database Query Optimization with MySQL 8.0.",
    "자세한 설명은 댓글로 남겨주시면 감사하겠습니다."
]

LOREM_COMMENTS = [
    "좋은 글 감사합니다!", "동의합니다.", "잘 읽었습니다.",
    "이 부분은 조금 더 확인이 필요해 보이네요.", "도움이 많이 되었습니다."
]


def bulk_insert_users(cursor):
    print(f"1/3 사용자 {USER_COUNT:,}명 데이터 생성 및 삽입 시작...")
    start_time = time.time()

    sql = "INSERT INTO users (email, nickname) VALUES (%s, %s)"
    data_buffer = []

    for i in range(1, USER_COUNT + 1):
        email = f"user_{i}@example.com"
        nickname = f"user_nick_{i}"
        data_buffer.append((email, nickname))

        if len(data_buffer) == BATCH_SIZE:
            cursor.executemany(sql, data_buffer)
            data_buffer.clear()
            print(f"  Users: {i:,} / {USER_COUNT:,} 완료 ({time.time() - start_time:.1f}초)")

    if data_buffer:
        cursor.executemany(sql, data_buffer)

    print(f"사용자 삽입 완료! 소요시간: {time.time() - start_time:.2f}\n")


def bulk_insert_posts(cursor):
    print(f"2/3 게시글 {POST_COUNT:,}개 데이터 생성 및 삽입 시작...")
    start_time = time.time()

    sql = "INSERT INTO posts (user_id, title, content) VALUES (%s, %s, %s)"
    data_buffer = []

    for i in range(1, POST_COUNT + 1):
        user_id = random.randint(1, USER_COUNT)
        title = f"{random.choice(LOREM_TITLES)} #{i}"
        content = random.choice(LOREM_CONTENTS)
        data_buffer.append((user_id, title, content))

        if len(data_buffer) == BATCH_SIZE:
            cursor.executemany(sql, data_buffer)
            data_buffer.clear()
            print(f"  Posts: {i:,} / {POST_COUNT:,} 완료 ({time.time() - start_time:.1f}초)")

    if data_buffer:
        cursor.executemany(sql, data_buffer)

    print(f"게시글 삽입 완료! 소요시간: {time.time() - start_time:.2f}\n")


def bulk_insert_comments(cursor):
    print(f"3/3 댓글 {COMMENT_COUNT:,}개 데이터 생성 및 삽입 시작...")
    start_time = time.time()

    sql = "INSERT INTO comments (post_id, user_id, content) VALUES (%s, %s, %s)"
    data_buffer = []

    for i in range(1, COMMENT_COUNT + 1):
        post_id = random.randint(1, POST_COUNT)
        user_id = random.randint(1, USER_COUNT)
        content = random.choice(LOREM_COMMENTS)
        data_buffer.append((post_id, user_id, content))

        if len(data_buffer) == BATCH_SIZE:
            cursor.executemany(sql, data_buffer)
            data_buffer.clear()
            print(f"  Comments: {i:,} / {COMMENT_COUNT:,} 완료 ({time.time() - start_time:.1f}초)")

    if data_buffer:
        cursor.executemany(sql, data_buffer)

    print(f"댓글 삽입 완료! 소요시간: {time.time() - start_time:.2f}\n")


def main():
    connection = pymysql.connect(**DB_CONFIG)
    cursor = connection.cursor()

    try:
        total_start = time.time()

        # 대량 데이터 삽입 속도 극대화를 위한 속성 해제
        print("속도 향상을 위한 제약조건 일시 해제 중...")
        cursor.execute("SET FOREIGN_KEY_CHECKS = 0;")
        cursor.execute("SET UNIQUE_CHECKS = 0;")
        cursor.execute("SET AUTOCOMMIT = 0;")

        # 데이터 삽입 수행
        bulk_insert_users(cursor)
        bulk_insert_posts(cursor)
        bulk_insert_comments(cursor)

        # 트랜잭션 커밋 및 제약조건 원복
        print("트랜잭션 커밋 및 제약조건 재설정 중...")
        connection.commit()
        cursor.execute("SET FOREIGN_KEY_CHECKS = 1;")
        cursor.execute("SET UNIQUE_CHECKS = 1;")

        print(f" 모든 작업이 성공적으로 완료되었습니다! 총 소요시간: {time.time() - total_start:.2f}초")

    except Exception as e:
        print(f"오류 발생: {e}")
        connection.rollback()
    finally:
        cursor.close()
        connection.close()


if __name__ == "__main__":
    main()
파이썬 스크립트 v2
import time
import random

# --- 설정 ---
OUTPUT_FILE = "dummy_data.sql"

USER_COUNT = 1_000_000
POST_COUNT = 2_000_000
COMMENT_COUNT = 3_000_000

# 1개의 INSERT 문당 묶을 행 수 (MySQL max_allowed_packet 초과 방지 및 메모리 최적화)
BATCH_SIZE = 5_000

LOREM_TITLES = [
    "안녕하세요 첫 글입니다.", "MySQL 대용량 데이터 테스트", "Spring Boot 개발 질문",
    "오늘의 뉴스 공유합니다.", "데이터베이스 인덱스 최적화 팁", "성능 테스트 진행 중",
    "이 에러 어떻게 해결하나요?", "자유게시판 질문입니다.", "추천 알고리즘 구현 방법"
]

LOREM_CONTENTS = [
    "게시글 본문 내용입니다. 테스트를 위해 대용량 데이터를 생성하고 있습니다.",
    "인덱스 설계와 페이지네이션 성능을 측정하기 위한 가상 데이터입니다.",
    "Database Query Optimization with MySQL 8.0.",
    "자세한 설명은 댓글로 남겨주시면 감사하겠습니다."
]

LOREM_COMMENTS = [
    "좋은 글 감사합니다!", "동의합니다.", "잘 읽었습니다.",
    "이 부분은 조금 더 확인이 필요해 보이네요.", "도움이 많이 되었습니다."
]


def generate_sql():
    start_time = time.time()
    print(f"'{OUTPUT_FILE}' 파일 생성 시작...")

    with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
        # 1. 헤더: 성능 최적화를 위한 설정 해제
        f.write("-- 대용량 데이터 삽입 최적화 설정\n")
        f.write("SET FOREIGN_KEY_CHECKS = 0;\n")
        f.write("SET UNIQUE_CHECKS = 0;\n")
        f.write("SET AUTOCOMMIT = 0;\n\n")

        # 2. 사용자 데이터 (users) 생성
        print(f"1/3 사용자 SQL 작성 중 ({USER_COUNT:,}건)...")
        for i in range(1, USER_COUNT + 1, BATCH_SIZE):
            rows = []
            end_idx = min(i + BATCH_SIZE, USER_COUNT + 1)
            for j in range(i, end_idx):
                email = f"user_{j}@example.com"
                nickname = f"user_nick_{j}"
                rows.append(f"('{email}', '{nickname}')")

            f.write("INSERT INTO users (email, nickname) VALUES\n")
            f.write(",\n".join(rows))
            f.write(";\n\n")

        # 3. 게시글 데이터 (posts) 생성
        print(f"2/3 게시글 SQL 작성 중 ({POST_COUNT:,}건)...")
        for i in range(1, POST_COUNT + 1, BATCH_SIZE):
            rows = []
            end_idx = min(i + BATCH_SIZE, POST_COUNT + 1)
            for j in range(i, end_idx):
                user_id = random.randint(1, USER_COUNT)
                title = f"{random.choice(LOREM_TITLES)} #{j}"
                content = random.choice(LOREM_CONTENTS)
                rows.append(f"({user_id}, '{title}', '{content}')")

            f.write("INSERT INTO posts (user_id, title, content) VALUES\n")
            f.write(",\n".join(rows))
            f.write(";\n\n")

        # 4. 댓글 데이터 (comments) 생성
        print(f"3/3 댓글 SQL 작성 중 ({COMMENT_COUNT:,}건)...")
        for i in range(1, COMMENT_COUNT + 1, BATCH_SIZE):
            rows = []
            end_idx = min(i + BATCH_SIZE, COMMENT_COUNT + 1)
            for j in range(i, end_idx):
                post_id = random.randint(1, POST_COUNT)
                user_id = random.randint(1, USER_COUNT)
                content = random.choice(LOREM_COMMENTS)
                rows.append(f"({post_id}, {user_id}, '{content}')")

            f.write("INSERT INTO comments (post_id, user_id, content) VALUES\n")
            f.write(",\n".join(rows))
            f.write(";\n\n")

        # 5. 푸터: 트랜잭션 커밋 및 제약조건 원복
        f.write("COMMIT;\n")
        f.write("SET FOREIGN_KEY_CHECKS = 1;\n")
        f.write("SET UNIQUE_CHECKS = 1;\n")
        f.write("SET AUTOCOMMIT = 1;\n")

    print(f"\n파일 생성 완료! 소요 시간: {time.time() - start_time:.2f}초")


if __name__ == "__main__":
    generate_sql()
파이썬 스크립트 v3
import time
import random
import pymysql

# --- DB 연결 설정 ---
DB_CONFIG = {
    'host': '127.0.0.1',
    'port': 3306,
    'user': 'user',
    'password': 'your_password',
    'db': 'prac',
    'charset': 'utf8mb4',
    'autocommit': False
}

# --- 생성할 데이터 수량 ---
USER_COUNT = 1_000_000
POST_COUNT = 2_000_000
COMMENT_COUNT = 3_000_000

# 1회 커밋당 처리할 행 수 (50,000건 단위 커밋 권장)
BATCH_SIZE = 50_000

LOREM_TITLES = [
    "안녕하세요 첫 글입니다.", "MySQL 대용량 데이터 테스트", "Spring Boot 개발 질문",
    "오늘의 뉴스 공유합니다.", "데이터베이스 인덱스 최적화 팁", "성능 테스트 진행 중",
    "이 에러 어떻게 해결하나요?", "자유게시판 질문입니다.", "추천 알고리즘 구현 방법"
]

LOREM_CONTENTS = [
    "게시글 본문 내용입니다. 테스트를 위해 대용량 데이터를 생성하고 있습니다.",
    "인덱스 설계와 페이지네이션 성능을 측정하기 위한 가상 데이터입니다.",
    "Database Query Optimization with MySQL 8.0.",
    "자세한 설명은 댓글로 남겨주시면 감사하겠습니다."
]

LOREM_COMMENTS = [
    "좋은 글 감사합니다!", "동의합니다.", "잘 읽었습니다.",
    "이 부분은 조금 더 확인이 필요해 보이네요.", "도움이 많이 되었습니다."
]


def bulk_insert_users(connection, cursor):
    print(f"1/3 사용자 {USER_COUNT:,}명 데이터 생성 및 삽입 시작...")
    start_time = time.time()

    sql = "INSERT INTO users (email, nickname) VALUES (%s, %s)"
    data_buffer = []

    for i in range(1, USER_COUNT + 1):
        email = f"user_{i}@example.com"
        nickname = f"user_nick_{i}"
        data_buffer.append((email, nickname))

        if len(data_buffer) == BATCH_SIZE:
            cursor.executemany(sql, data_buffer)
            connection.commit()  # 배치 단위 주기적 커밋!
            data_buffer.clear()
            print(f"  Users: {i:,} / {USER_COUNT:,} 삽입 및 커밋 완료 ({time.time() - start_time:.1f}초)")

    if data_buffer:
        cursor.executemany(sql, data_buffer)
        connection.commit()  # 잔여 데이터 커밋

    print(f"사용자 삽입 완료! 소요시간: {time.time() - start_time:.2f}\n")


def bulk_insert_posts(connection, cursor):
    print(f"2/3 게시글 {POST_COUNT:,}개 데이터 생성 및 삽입 시작...")
    start_time = time.time()

    sql = "INSERT INTO posts (user_id, title, content) VALUES (%s, %s, %s)"
    data_buffer = []

    for i in range(1, POST_COUNT + 1):
        user_id = random.randint(1, USER_COUNT)
        title = f"{random.choice(LOREM_TITLES)} #{i}"
        content = random.choice(LOREM_CONTENTS)
        data_buffer.append((user_id, title, content))

        if len(data_buffer) == BATCH_SIZE:
            cursor.executemany(sql, data_buffer)
            connection.commit()  # 배치 단위 주기적 커밋!
            data_buffer.clear()
            print(f"  Posts: {i:,} / {POST_COUNT:,} 삽입 및 커밋 완료 ({time.time() - start_time:.1f}초)")

    if data_buffer:
        cursor.executemany(sql, data_buffer)
        connection.commit()  # 잔여 데이터 커밋

    print(f"게시글 삽입 완료! 소요시간: {time.time() - start_time:.2f}\n")


def bulk_insert_comments(connection, cursor):
    print(f"3/3 댓글 {COMMENT_COUNT:,}개 데이터 생성 및 삽입 시작...")
    start_time = time.time()

    sql = "INSERT INTO comments (post_id, user_id, content) VALUES (%s, %s, %s)"
    data_buffer = []

    for i in range(1, COMMENT_COUNT + 1):
        post_id = random.randint(1, POST_COUNT)
        user_id = random.randint(1, USER_COUNT)
        content = random.choice(LOREM_COMMENTS)
        data_buffer.append((post_id, user_id, content))

        if len(data_buffer) == BATCH_SIZE:
            cursor.executemany(sql, data_buffer)
            connection.commit()  # 배치 단위 주기적 커밋!
            data_buffer.clear()
            print(f"  Comments: {i:,} / {COMMENT_COUNT:,} 삽입 및 커밋 완료 ({time.time() - start_time:.1f}초)")

    if data_buffer:
        cursor.executemany(sql, data_buffer)
        connection.commit()  # 잔여 데이터 커밋

    print(f"댓글 삽입 완료! 소요시간: {time.time() - start_time:.2f}\n")


def main():
    connection = pymysql.connect(**DB_CONFIG)
    cursor = connection.cursor()

    try:
        total_start = time.time()

        print("속도 향상을 위한 세션 제약조건 일시 해제 중...")
        cursor.execute("SET FOREIGN_KEY_CHECKS = 0;")
        cursor.execute("SET UNIQUE_CHECKS = 0;")

        # 각 함수 내부에서 connection.commit()을 분할 실행
        bulk_insert_users(connection, cursor)
        bulk_insert_posts(connection, cursor)
        bulk_insert_comments(connection, cursor)

        print("세션 제약조건 재설정 중...")
        cursor.execute("SET FOREIGN_KEY_CHECKS = 1;")
        cursor.execute("SET UNIQUE_CHECKS = 1;")

        print(f"모든 작업이 성공적으로 완료되었습니다! 총 소요시간: {time.time() - total_start:.2f}초")

    except Exception as e:
        print(f"오류 발생: {e}")
        # 오류가 나더라도 이미 커밋된 이전 배치들은 DB에 안전하게 보존됨
        connection.rollback()
    finally:
        cursor.close()
        connection.close()


if __name__ == "__main__":
    main()

실제 시간 측정 - mysql

SELECT * FROM users WHERE nickname = 'user_nick_500000';
-- 출력: 1 row in set (0.00 sec) 또는 (0.12 sec)

EXPLAIN ANALYZE

EXPLAIN ANALYZE
SELECT * FROM posts
ORDER BY created_at DESC, id DESC
LIMIT 30 OFFSET 1500;

PYTHON

import time
import pymysql

conn = pymysql.connect(
    host='127.0.0.1', user='user', password='your_password', db='prac'
)
cursor = conn.cursor()

def measure_query(query, params=None):
    start = time.perf_counter()  # 정밀 타이머 시작
    cursor.execute(query, params or ())
    result = cursor.fetchall()
    end = time.perf_counter()    # 타이머 종료

    execution_time_ms = (end - start) * 1000
    print(f"쿼리 소요 시간: {execution_time_ms:.3f} ms | 조회 결과: {len(result)}건")
    return result

# 테스트 실행
measure_query("SELECT * FROM users WHERE email = %s", ('user_500000@example.com',))
ALTER TABLE users DROP INDEX idx_nickname;
CREATE INDEX idx_nickname ON users (nickname);
mysql> explain analyze select nickname from users where nickname like "%123%"
    -> ;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                          |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: (users.nickname like '%123%')  (cost=100746 rows=110531) (actual time=0.517..429 rows=3999 loops=1)
    -> Table scan on users  (cost=100746 rows=994882) (actual time=0.463..215 rows=1e+6 loops=1)
|
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

LIKE '%keyword%' 검색의 경우 B-Tree 인덱스를 생성해도 활용하기 어렵다는 것을 확인했습니다.

실행 계획에서 Table scan on users가 발생했고, 100만 개의 row를 모두 읽은 뒤 nickname LIKE '%123%' 조건을 필터링했습니다.

이는 B-Tree 인덱스가 정렬된 prefix 기반 탐색에는 적합하지만, 앞부분이 와일드카드인 contains 검색에서는 시작 위치를 찾을 수 없기 때문입니다.

대량 데이터에서 부분 검색이 필요하다면 Full Text Index, Elasticsearch 등의 별도 검색 엔진을 고려해야 합니다.

배송상태?

배송상태

댕글님 피드백

  • cloudFront 쓸 때 pay as you go로 설정하는거 잊지말기
  • (기타 ReadMe에 대한 것)

현대 재직자 멘토링 질문 불릿 리스트

초안
  • SSE vs Websocket 현실적인 방안을 선택할지, 기술적 도전에 중점을 둔 방안을 선택할지 예를 들어 웹소켓 방식이 실시간성이 더 좋고, 룸별로 상태를 관리할 수 있기 때문에 편리하지만 연결이 끊어졌을 때 복구하는 로직이 복잡. 또한 SSE를 사용해서 5~10초 단위로 상태를 업데이트해줘도 배달 서비스에서는 충분한 실시간성임. 이런 경우에 현실적인 SSE를 선택할지 아니면 웹소켓을 선택해서 온갖 단점들을 모두 해결해보면서 기술적 성장을 목표로 하는 것이 좋을지.
    • 지금까지 프로젝트에서 기술이나 방향 관련해서 결정하는데 많은 논의와 갈등이 있었다. 실제 현업에서는 어느정도로 기술과 현실을 타협에서 결정을 하는지 궁금함
    • Docs 작성/협업:코드 작성에 쏟는 시간의 비율이 어느정도 되는가?
  • 프로젝트 요구사항에 "실시간"이라는 키워드가 공통된다. 이 실시간이라는건 어디까지가 실시간일까
  • 클라이언트를 어느정도 불신해야하는가?
  • 추가적으로 MVP를 만들면서 팀 내에서 보안 관련 문제가 계속해서 나오고 있는데
    • MVP에서 보안은 나중에 챙기는 것이 더 좋은 것인지
    • 아니면 처음부터 챙기면서 천천히 진행하는 것인지가 궁금함.
  • JDK 버전을 유지하거나 변경할 때 어떻게 결정하는지
  • @Valid, 자바 컨벤션 등의 코드내에서의 팀간의 규약을 어떻게 검증하는지 (지금이야 에이전트 지침에 넣어서 검증하지만, 현업에서는 이런 규약을 어떻게 검증하는지)
  • 현업에서는 많은 양의 mock 데이터를 넣어서 테스트할 때 어떤 과정으로 테스트 하는지
  • 외래키 제약을 걸어놓으면 성능이 안좋아져서, 외래키 관계여도 실제로는 외래키 제약을 걸지는 않는 경우가 있다고 하는데, 그러면 개발 단계에서부터 외래키 제약 조건을 빼놓고 설계하는지 아니면 개발 단계에서는 외래키 제약을 걸어두고 개발하다가 실제 서비스 단계에서만 제거하는지

  • 기술 선택 기준: 현실적 최적해 vs 기술적 도전

    저희는 배달 추적 기능에서 SSE와 WebSocket을 두고 오래 논의했습니다. WebSocket은 실시간성과 룸 단위 상태 관리에 유리하지만 재연결·상태 복구 로직의 복잡도가 크고, SSE로 5~10초 주기 업데이트만 해도 배달 도메인 요구사항은 충족된다고 판단했습니다. 현업이라면 어느 쪽을 택하실까요? 그리고 "요구사항을 만족하는 가장 단순한 선택"과 "팀의 기술 역량 성장" 사이에서 실제로 어떻게 균형을 잡으시나요?

  • "실시간"의 정의

    요구사항에 "실시간"이라는 단어가 여러 군데 등장하는데, 팀원마다 떠올리는 기준이 달랐습니다. 현업에서는 이런 모호한 비기능 요구사항을 어떤 식으로 수치화하고 합의하나요?

  • 대량 mock 데이터 테스트

    대량의 테스트 데이터가 필요할 때 실제 절차가 궁금합니다. 데이터를 어떻게 생성하는지(스크립트, 픽스처, 운영 데이터 마스킹 등), 어느 환경에서 돌리는지, 성능 테스트는 개발 사이클의 어느 시점에 들어가는지요.

  • 클라이언트를 어디까지 불신할 것인가

    모든 값을 서버에서 재검증하면 안전하지만 개발 비용과 부하가 늘어납니다. "이건 클라이언트를 믿어도 된다"와 "반드시 서버가 판단한다"를 가르는 실무적 기준이 무엇인가요?

  • MVP 단계의 보안 우선순위

    MVP를 만들면서 보안 이슈가 계속 나오는데 일정과 충돌합니다. 처음부터 챙기며 천천히 가는 쪽과, 일단 동작시키고 나중에 보강하는 쪽 중 현실적으로 어느 쪽인가요? 그리고 "MVP라도 저희 프로젝트에서 이것만은 절대 미루면 안 된다"는 최소 항목이 있다면 무엇인가요?

  • 팀 규약의 검증 방법

    @Valid 사용 여부나 자바 코드 컨벤션 같은 팀 규약을 현업에서는 어떻게 강제하나요? 저희는 지금 AI 에이전트 지침에 넣어 검증하는데, 실무에서는 정적 분석 도구나 CI 게이트로 어디까지 자동화하고, 어디부터는 사람이 리뷰로 잡나요?

  • 문서·협업과 코드 작성의 시간 비율

    실제로 코드 작성과 문서·회의·리뷰에 쓰는 시간 비율이 대략 어느 정도인가요?

  • 외래키 제약

    성능 때문에 논리적 관계는 있어도 물리적 FK 제약은 걸지 않는 경우가 있다고 들었습니다. 설계 단계부터 빼는지, 개발 중에는 걸어두고 운영 배포 시 제거하는지 궁금합니다.

  • 라이브러리·프레임워크 선택 기준

    새 라이브러리나 프레임워크를 도입할 때, 혹은 기존 버전을 유지할지 올릴지 결정할 때 어떤 기준으로 판단하시나요? "이건 직접 구현하지 말고 검증된 라이브러리를 쓰자" 또는 "이건 의존성 늘리지 말고 직접 만들자"를 가르는 선은 어디쯤인가요?

저녁 회고

  • 석희 : 오후에 인프라 설계해서 docker 빌드 자동으로 붙이기
    • MySQL driver 추가했음.
    • 이슈 66번 처리
    • 내일
      • 프론트 까지 띄워서 s3 업로드 및 클라우드프론트 붙이기
      • 부르미 도메인 시작
  • 현성
    • 오늘
      • PR리뷰
      • 배송 로직 및 상태, 모든 분기 고려해서 수도코드 작성
    • 내일
      1. 동혁님 매칭 자바 코드 ⇒ PR
      2. 동혁님 매칭엔진 참고해서 배달도 그렇게 만들고 ⇒ PR
      3. DB 관련 연결 (매칭과 배달이랑 관련있는 DB 실제)
      4. 매칭에 웹소켓 얹기
      5. 배달에 SSE 얹기
  • 동혁 :
    • 오늘
      • 매칭 엔진 코드를 부분적으로 구현완료
    • 내일
      • 엔진 코드 PR 잘라서 올리고 새로운 issue만들어서 브랜치 새로 파기.
      • 석희님과 웹소켓 관련.
  • 현서 :
    • 클로드 PR 리뷰 기준으로 코드 수정
    • 업로드 도메인 만들고 API 명세서 추가. 명세서로 넣을 것은 따로 없어서 다운로드 url만 남기고 업로드 url만 없애기
    • 비대면 배달 완료 인증 사진 올리고 나중에 다른 사진으로 바꾼다면 분란
    • 내일
      • api 명세서 수정
      • s3 업로드 구현
  • 오늘의 교훈
    • PR을 최대한 작게 가져가자 → 리뷰하기 너무 어려움

시나리오

  1. presigned url을 클라이언트가 몰래 저장
  2. 정상적으로 업로드해서 제출완료 후 관리자가 인증 끝냄
  3. 나중에 공격자가 해당 presigned url을 이용해서 엉뚱한 사진으로 교체 해버림
참고
1. 임시 key 발급
   temp/{uploadId}

2. Presigned URL로 임시 객체 업로드

3. 서버가 임시 객체 검사
   - 파일 형식
   - 크기
   - 이미지 디코딩
   - 체크섬
   - 악성 파일 검사

4. 검증 성공 시 서버 권한으로 최종 key에 복사
   final/{imageId}

5. 임시 객체 삭제
6. DB에는 final key만 저장

🛵 쉼, 부름

Softeer Bootcamp 8th · WEB Team 3


🏠 시작하기

🤝 협업

📋 기획안

🧠 기술 결정 기록 (ADR)

🕸️매칭 시스템

🧪 부하 테스트

🔧 트러블슈팅

🗓️ 데일리 스크럼


Repo

Clone this wiki locally