이 글은 내가 *집에서 *개인적으로 운영 중인 *K3s 6 노드 클러스터 와, 그 위에서 (또는 그 외곽에서) 살아 움직이는 *대표 프로젝트 3 개 를, 왜 그 기술을 골랐는가 / 어떻게 운영되고 있는가 / 깨졌을 때 어떻게 푸는가3 층 깊이친절하게 풀어쓴 *심층 노트 다.

대상은 두 종류의 독자:

  1. 나를 잘 모르는 분 — 이 글로 내 작업의 *3 축 (코드 / 인프라 / 도메인)한 번에 *파악 하고 싶은 사람
  2. *나와 *같은 *고민 (단일 클러스터 운영, MSA 진화, low-level / fullstack 균형) 을 하고 있는 사람** — *내 의사결정의 *근거참고 하고 반박 하고 싶은 사람

어느 쪽으로 오셔도 *길게 머무실 수 있게 천천히 쓴다.


TL;DR — 3 줄로 압축

개인 K3s 6 노드 클러스터 위에 64 개 ArgoCD ApplicationGitOps 로 굴린다. 그 안에서 이커머스 + 정산 MSA (Spring Boot 4 + Java 25) 와 C++ 시장 데이터 파이프라인 (외곽 systemd) 을 경계가 분명한 *Bounded Context 로 운영한다. 이번 주만 *알람 13 건postmortem 으로 학습 압축, 블로그 8 편 (4,000+ lines) 으로 조직 자산화.

한 그림으로 보면:

[ Cloudflare Edge ]
        │
        ▼
[ Cloudflare Tunnel — 외곽 systemd ]
        │
        ▼
[ K3s 6 노드 클러스터 — lemuel / ilwon / solomon (control-plane HA) + david / louise / isagal (worker) ]
        │
        ├── ArgoCD root-app  ─→  64 Application (GitOps)
        ├── Velero + Kopia   ─→  Cloudflare R2 백업
        ├── Prometheus + ELK ─→  옵저버빌리티
        │
        ├── 프로젝트 ① 이커머스 + 정산 MSA (Spring Boot 4 + Java 25)
        └── 프로젝트 ③ ASAT 청각 재활 (Spring Boot + Next.js + Web Audio API)
                │ (이벤트 / 데이터)
                ▼
        [ 클러스터 *외곽* — bare metal + systemd ]
        └── 프로젝트 ② C++ quant-core (시장 데이터 파이프라인 6 모듈)

0. 왜 이 글을 *지금 쓰는가*

내가 얕은 자기소개 를 좋아하지 않는 이유는 기술 스택 단어 카드만 *나열 하면 반드시 깊은 질문이 *왔을 때 *멈춘다 는 것을 경험 했기 때문이다. “써봤습니다”“왜 그것을 골랐는가” 사이엔 큰 강 이 있다. 이 글은 그 강을 건넌 흔적세 프로젝트로 증명 하려는 시도다.

면접관 / 동료 / 미래의 나 자신 누구든 3 분 만에 *대표 작품 설명해보세요 라고 했을 때 떠올릴 *대본 까지 이 글에 *심어둔다. 그래서 길다. 천천히 읽거나 목차 보고 *필요한 부분만 *점프 해도 좋다.


1. Kubernetes 인프라개인 K3s 6 노드 클러스터

1.1 왜 *집에서 *클러스터를 운영 하는가*

개인 프로젝트가 늘어나면서 각 프로젝트를 *어디에 두느냐고민의 시작 이었다. 클라우드 (AWS / GCP) 에 다 띄우면 비용이 *월 30 만원우습게 넘긴다. 집 PC 한 대 에 다 띄우면 서버 한 대 꺼지면 *모든 사이트 동시 다운. 팀이 *나 혼자 인데 전원 cycle / 패치 / 모니터링수동으로하는 건 *지속 불가능.

결론은 집에 *작은 *production-grade 환경을 만들자* 였다. 그 결과가 지금의 6 노드 K3s 클러스터 다.

클라우드는 *간편함을 *돈으로 사는 것. 온프렘은 *깊이를 *시간으로 사는 것. 나는 *지금은 *시간상대적으로 *돈 보다 싸다. 5 년 후엔 반대일 수도 있고, 그때는 클라우드로 이전 도 *자연스럽게 가능 하게 helm-deploy 레포로 *재현 가능 한 인프라* 로 유지 한다.

1.2 규모 + 토폴로지왜 6 노드인가

┌─────────────────────────────────────────────────────────────┐
│   K3s v1.35.4+k3s1  (Ubuntu 24.04 / 26.04 LTS)             │
│   3 control-plane + etcd HA  +  3 worker                    │
└─────────────────────────────────────────────────────────────┘
  lemuel    (control-plane)    Mac mini M2 Pro       4c / 32G
  ilwon     (control-plane)    Mini PC i7-14700      12c / 32G  NVMe
  solomon   (control-plane)    Mac mini 2014          4c / 15G
  louise    (worker)           Mini PC                8c / 16G
  david     (worker)           Mini PC                6c / 15G
  isagal    (worker)           Dell PowerEdge R730xd  40c / 15G / 3.6 TB SAS RAID-0
                                                       ↑
                                            2026/06/06 새벽에 추가한 *듀얼 Xeon*

총합: 74 logical CPU, 125 GB RAM, 4 TB+ 안정 스토리지

3 + 3 인가:

  • etcd HA 의 *quorum 은 *홀수. 3 노드 control-plane가장 가벼운 *고가용 *최소 단위.
  • worker 3 노드DaemonSet 의 *3 인스턴스 + 일반 워크로드의 *replicas: 2 ~ 3여유 있게 흡수.
  • 노드의 *다양성 (Mac / PC / Dell 서버) 이 의도된 — 하드웨어 *실패 모드의 *다양성학습 자원 으로 본다.

control-plane 노드도 *taint 안 걸고 *워크로드 일부 가 떨어지나:

작은 클러스터 에서 control-plane 의 *유휴 자원전혀 안 쓰는 건 *낭비. 다만 kube-apiserver + etcd 에 *영향 가는 *고부하 워크로드taint 로 *피한다 (ilwon = dedicated=management:PreferNoSchedule, solomon = dedicated=storage:NoSchedule). *세밀한 격리 *yes, *극단적 격리 *no.

1.3 GitOpsroot-app 한 줄이 *64 앱을 *통제

내 클러스터의 모든 변경helm-deploy 레포한 PR 로 시작한다. root-app 이 이 레포의 argocd-applications/ 디렉토리를 재귀 watch 하면서 child Application 들을 *자동 생성·갱신·prune 한다.

# root-app.yaml — 한 번만 수동 apply, 그 다음부터는 *전부 자동*
spec:
  source:
    repoURL: https://github.com/MyoungSoo7/helm-deploy
    path: argocd-applications
    directory:
      recurse: true
  syncPolicy:
    automated:
      prune: true       # 파일 삭제 시 → 클러스터에서 Application 제거
      selfHeal: true    # 누군가 kubectl edit 하면 → Git 상태로 복원

새 사이트 하나 띄우려면:

  1. argocd-applications/foo.yaml 추가 + charts/foo/ Helm 차트 추가
  2. git push
  3. 끝 — root-app 가 *자동 sync 해서 foo Application 생성 + foo 사이트 띄움

사이트 하나 내리려면:

  1. argocd-applications/foo.yaml 삭제 + git push
  2. 끝 — prunefoo Application 제거 + 관련 리소스 회수

이 단순함이 6 노드 ↔ 64 앱 ↔ 30+ namespace 의 복잡도를 한 사람이 관리 가능 하게 만든다.

1.4 백업 / DR3 단 안전망

1) 즉시 복구 (Velero + Kopia)
   - 36 BackupRepository (namespace 단위)
   - 매일 자동 / off-cluster Cloudflare R2
   - 복구 시간 목표: < 30 분
   - 명령: velero restore create --from-backup ...

2) 데이터 안전망 (CronJob: pg-backup)
   - 각 사이트 Postgres → pg_dump → backup-pvc → R2
   - 더블 보호 — Velero 가 깨져도 dump 로 복원 가능

3) 인프라 자체 복원 (helm-deploy 레포)
   - 클러스터가 *재현 가능* — apply 한 번이면 *전부* 다시 띄움

세 layer 의 *복구 시간 / 비용 / 보장 수준이 *서로 다른 *각자의 시나리오를 본다. 어느 *한 layer 만 *완벽하면 안 된다복합 사고가 *현실.

1.5 알람 → postmortem루틴이번 주만 13 건

내가 *진짜 자신 있는 *유일한 자산사고를 *반드시 학습 자산으로 *전환 하는 루프 라고 생각한다.

이번 주 5 일 동안 *처리한 *알람 13 건대표 5 건:

Case 1 — KubeJobFailed (velero/kopia ×3)

[resolved] KubeJobFailed
namespace: velero
Job velero/argocd-default-kopia-9nkv7-maintain-job-1778421891769 failed to complete.

처음 의심: Kopia maintenance 가 깨졌나. 결과: Kopia 는 정상. 5 월 10 일에 한 번 실패한 *Job 객체 4 개3 주째 *cleanup 안 됨. 진짜 원인은 *velero ns 의 LimitRangemaxLimitRequestRatio.memory = 2default.memory: 512Mi / defaultRequest.memory: 128Mi (ratio = 4.0)충돌. Kopia Pod 가 resources: {} 로 들어오면 *기본값 ratio 4.0정책 위반admission webhook 거부 → Job Failed.

해결:

  1. 좀비 Job 4 개 삭제
  2. velero ns 의 LimitRange 자체 제거 (시스템 ns 에 프로젝트 정책 박는 건 *결합도 폭발)
  3. 다른 6 ns 의 LimitRange 는 default.memory: 512Mi → 256Miratio 2.0 정규화

블로그 글 으로 학습 압축.

Case 2 — kubectl 이 *no route to host, curl 은 *200**

$ kubectl get nodes
Unable to connect to the server: dial tcp 192.168.219.101:6443:
  connect: no route to host

$ curl -k https://192.168.219.101:6443/version
{ ... ok ... }

같은 주소같은 시각kubectl 은 거짓 응답, curl 은 정상. 재현 100%.

원인 추정: macOS 의 Go net 패키지EHOSTUNREACH 캐싱 결함 (확실히 잡진 못함). 진단의 *진짜 문제그 직후 *내가 *ping 으로 LAN 스캔 해서 5 개 노드가 죽었다틀린 가설세웠던 것. 알고 보니 내가 ping 한 IP 가 *클러스터 노드 IP 가 아니라 *LAN 의 무관한 IP 였다. 도구의 거짓말 + 내 잘못된 도구 사용두 번 가설을 *왜곡.

해결 (우회 회로):

  • ~/.kube/configclient cert / key / CA 추출
  • curl --cert ... --key ... --cacert ...API 직접 호출
  • Python + jqkubectl get / describe 등가 인터페이스 self-built
  • 그날 모든 운영 변경 (Job 삭제 / LimitRange 수정 / 알람 정리)curl 만으로 진행

kubectl 이 회복될 때까지 *block 되지 않은 것이 *핵심 가치. 도구의 추상화 그 너머 의 layer 한 단 아래에서 *직접 일할 수 있는 능력극단의 디버깅 상황 에서 유일한 진단 길 인 경우가 있다.

Case 3 — KubeDaemonSetRolloutStuck (velero/node-agent)

DaemonSet velero/node-agent has not finished or progressed for at least 15 minutes.

진단: solomon 의 *dedicated=storage:NoSchedule taint나중에 추가됨. DaemonSet 의 tolerations: []. 이미 solomon 에서 돌고 있던 Pod 가 *evict 되지 않음 (NoSchedule 은 기존 Pod 안 쫓아냄, 단지 새로 스케줄 안 함). 결과: 솔로몬에 *misscheduled Pod 1 개가 *영원히 남아, DaemonSet 의 numberMisscheduled = 1 → Prometheus 알람 룰 점화.

해결:

  • 미스케줄 Pod 한 개 삭제 → DS controller 가 솔로몬엔 다시 안 띄움numberMisscheduled = 0
  • (정통 fix 는 Velero chart 에 toleration 추가별도 PR)

Case 4 — KubeAPIErrorBudgetBurn (이번 주 마지막)

[resolved] KubeAPIErrorBudgetBurn
The API server is burning too much error budget.

진단: 3 주 전 (5/20) 누군가 traefik 을 제거 시도했는데, helm-controller 의 *delete Job 이 *service account 먼저 삭제됨 으로 영원히 막힘. 매 분 *FailedCreate 이벤트API server 에 *짧은 5xx돌려보냄SLO burn rate 상승.

해결:

  1. kubectl delete helmchart traefik traefik-crd (cascade)
  2. 그래도 finalizer (wrangler.cattle.io/on-helm-chart-remove)stuckmetadata.finalizers: []force patch
  3. K3s embedded manifest reconciler 가 traefik 매니페스트 재적용 → 새 helm-install Job → 성공traefik 완전 재설치

내가 *3 주 전에 무엇을 시도했는지 *모르는 상태 에서 후속 *증상 을 만났을 때, 원인을 *시간 역행 으로 추적 하는 능력. 이게 postmortem 의 *진짜 깊이.

1.6 기술 스택 한눈에

카테고리 기술
오케스트레이션 K3s, ArgoCD, Image Updater, sops-secrets-operator
옵저버빌리티 kube-prometheus-stack (Prometheus / Grafana / AlertManager) + Loki + ELK + Tempo + node-local-dns
백업 Velero + Kopia + Cloudflare R2
인그레스 Cloudflare Tunnel (외부) + NodePort + Traefik (대체)
시크릿 sops + age + git-encrypted secrets
워크로드 Spring Boot · Next.js · Python · C++

2. 대표 프로젝트 ①이커머스 + 정산 MSA (Lemuel)

모놀리스 → Bounded Context 분리살아있는 진화 기록. Triple Idempotency / Read-only Projection / Outbox교과서 패턴코드에서 강제 (ArchUnit) 한다.

2.1 왜 이 프로젝트인가

내가 백엔드 깊이증명 하고 싶을 때 가장 먼저 보여드리는 작품. Spring Boot 4 + Java 25 + Gradle multi-module + Outbox + Hexagonal + ArchUnit2026 년 *최첨단 조합. 단순히 기술 트렌드따라간 게 아니라, 각 기술이 *왜 *지금 *필요한가의사결정 단계마다 *명시적으로 기록 했다.

2.2 기술 스택 — 2026 년 최첨단

구분 기술 버전 / 비고
언어 Java 25 (Virtual Thread + Pattern Matching)
프레임워크 Spring Boot 4.0.4
빌드 Gradle Multi-module (Kotlin DSL) 9.x
데이터베이스 PostgreSQL 17
메시지 브로커 Kafka (Redpanda 호환) 24.x
검색 엔진 Elasticsearch 8.17
캐시 Caffeine -
마이그레이션 Flyway V1 ~ V37
회복탄력성 Resilience4j -
Rate Limiting Bucket4j -
PDF 생성 iText 8
모니터링 Micrometer + Prometheus -
PG 연동 Toss Payments -
컴파일 시 강제 ArchUnit ★ 핵심

2.3 4 모듈의 *Bounded Context

settlement/                       # Gradle multi-module 루트
├── shared-common/                # java-library — 양 서비스가 공유
│   └── common.{audit, config, exception, outbox, ratelimit, pdf}
├── order-service/                # 🛒 Commerce 서비스 (port 8088)
│   └── lemuel.{user, order, payment, product, category, coupon, review, game}
├── settlement-service/           # 💰 Settlement 서비스 (port 8082)
│   └── lemuel.{settlement, report}
└── gateway-service/              # 🚪 API Gateway (port 8080)
    └── (Spring Cloud Gateway 2025)

원래 단일 모놀리스 였다. MSA 분리 시점의 *결정 기준변경 속도가 *bounded context 별로 *분기한 시점. order 가 *주 5 PR / settlement 가 *월 1 PR팀 / 사이클 *분리 필요MSA 의 *진짜 trigger. 단순히 기술 트렌드 가 아니라 조직과 도메인 의 *natural fault line.

2.4 핵심 패턴 1Read-only Projection

목적: settlement-service 가 order-service 코드 한 줄도 import 하지 않으면서 Order/Payment/User/Product 데이터를 직접 SELECT 한다.

// settlement-service/.../adapter/out/readmodel/
@Entity
@Immutable
@Table(name = "payments")
public class SettlementPaymentReadModel {
    @Id private Long id;
    @Column private String orderId;
    @Column private BigDecimal amount;
    @Column private PaymentStatus status;
    // ... setter 없음 — 절대 쓰기 불가
}
// settlement-service/build.gradle.kts
dependencies {
    // implementation(project(":order-service"))   ← 의도적으로 *없음*
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    // ...
}

효과:

  • ✅ MSA 의 코드 경계 100% 확립PR 단에서 *코드 결합 검출 가능
  • ✅ JPA 의 친숙한 인터페이스 그대로 사용
  • ✅ DB 가 유일한 결합점 (스키마 마이그레이션 분리 협의 만 하면 됨)
  • ✅ Kafka 이벤트와 상호 보완 (이벤트 = 사건, 프로젝션 = 상태 조회)

면접 함정 질문 — 왜 이벤트만으로 안 되나?

답: 이벤트는 *시간 흐름의 *사건 sequence. 프로젝션은 *현재 상태 snapshot. 어제 마감된 정산 보고서 *조회 같은 시간차 query이벤트로 *재구성 하면 eventual consistency 의 *시간 비용 + 메모리 비용 + 복잡도DB 직 read 보다 압도적으로 큼. Event Sourcing 의 *교과서적 함정.

2.5 핵심 패턴 2Triple Idempotency

PaymentCaptured 이벤트가 2 번 발행 / 2 번 수신 / 2 번 처리 되어도 최종 1 번 효과 보장.

[L1] order-service 측 Outbox
     outbox_events.event_id  UUID UNIQUE
     → 같은 이벤트 *발행 단계* 중복 차단

[L2] settlement-service 측 processed_events
     PK (consumer_group, event_id)
     → 같은 이벤트 *수신 단계* 중복 차단

[L3] settlements 테이블의 비즈니스 UNIQUE
     settlements.payment_id  UNIQUE
     → 같은 결제에 *정산 row 2 개* 절대 불가

3 단인가 — 오버 엔지니어링 아닌가?

답: 각 layer 가 *실제로 *다른 실패 모드 를 막는다*.

  • L1 — 발행자의 *중복 발행 (재시도 / 네트워크 timeout)
  • L2 — 수신자의 *중복 처리 (Consumer rebalance / replay)
  • L3 — 비즈니스 키 *오염 (수동 보정 / 마이그레이션 실수 / 다른 service 의 잘못된 호출)

하나가 뚫려도 *다음 layer 가 막음. 각 layer 가 *서로 다른 *실패 모드를 *전담. 과잉이 *아니라각자의 root cause다르다.

2.6 ArchUnitlayer 위반은 *컴파일 fail

@ArchTest
public static final ArchRule domainShouldNotDependOnSpring =
    noClasses().that().resideInAPackage("..domain..")
               .should().dependOnClassesThat()
               .resideInAPackage("org.springframework..");

@ArchTest
public static final ArchRule applicationShouldNotUseJPA =
    noClasses().that().resideInAPackage("..application.service..")
               .should().dependOnClassesThat()
               .resideInAPackage("jakarta.persistence..");

@ArchTest
public static final ArchRule adaptersShouldNotCrossDomain =
    layeredArchitecture().consideringAllDependencies()
        .layer("settlement").definedBy("..settlement..")
        .layer("order").definedBy("..order..")
        .whereLayer("settlement").mayNotBeAccessedByLayerExcept("application");

사람이 *지키는 게 아니라 기계가 *거절. 코드 리뷰의 *한 layer 더 *낮은 layer 에서 컴파일러수문장.


3. 대표 프로젝트 ②lemuel-quant-core (C++ 시장 데이터 파이프라인)

K3s 가 오케스트레이션의 영역 인 것 처럼, 이 프로젝트는 latency-critical layer 의 영역. 시간축 / 정합성 / 격리 요구컨테이너 위에 올라가지 않는 워크로드살아있는 예시.

3.1 왜 *이 프로젝트인가

낮은 layer 의 깊이증명 하고 싶을 때 보여드리는 작품. C++20 + CMake + 6 모듈 + gRPC + systemd-on-metalsystem programming 영역. 모든 모듈이 *왜 C++ 인가서로 다른 답 을 가진다.

3.2 왜 *K3s 안이 아닌 *systemd-on-metal 인가 — 4 가지 mismatch*

mismatch 영향
영구 WebSocket 세션 ↔ Pod ephemeral Pod 재시작 시 시세 공백 수 백 ms. Binance 의 연결당 *분당 rate limit 위반 위험. 호가창 *snapshot 재구성 비용 폭발
seccomp + cgroup ↔ K8s 의 이미 cgroup judge-engine코드 채점 격리 cgroupcontainerd 가 만든 cgroup 위에 이중 으로 쌓임. 권한 *오버라이딩예측 불가
외부 API IP allowlist ↔ Pod random source IP 한국투자증권 API 가 IP 고정 등록 정책. Pod 의 SNAT어느 노드의 어느 IP 로 나가는지 예측 어려움
50MB static binary vs 250MB JRE 이미지 오케스트레이션 metadata 가 *binary 자체보다 큼. systemd unit 한 줄 = 이미 binary 1 개

*클라우드 네이티브 *교조거절 하고 워크로드의 본질 에 맞는 layer 를 별도 선택. 이게 MSA / SaaS / 마이크로워크로드 시대의 *진짜 deep 한 결정.

3.3 *6 모듈 — 각자의 *왜 C++ 인가**

lemuel-quant-core/
├── shared/                          # FeedClient 추상화, 네트워크 / 로깅 / 직렬화
├── modules/
│   ├── judge-engine/                # 코드 채점 (seccomp + cgroup, μs latency)
│   ├── market-feed/                 # Binance WS — GC pause 0
│   ├── stock-feed/                  # KIS OpenAPI — peak tail latency p99.9 < 5ms
│   ├── dart-crawler/                # DART 공시 polling
│   ├── news-pipeline/               # RSS → NER → 감성 → ONNX Runtime C++
│   └── data-warehouse/              # Apache Arrow Parquet + R2 업로드

judge-engine — 왜 C++ 인가

코딩테스트 사이트의 제출 코드받아 *외부 코드를 *내 호스트에서 *직접 실행 한다. seccomp-bpf 로 *허용 syscall 명시 + cgroup v2 로 *메모리/CPU 한계 동적 부여. Java / Go / Python 의 *helper thread예측 못한 syscall 발생시킴seccomp kill. C++ + raw syscall + minimal runtime유일한 안전한 길.

market-feed — 왜 C++ 인가

100+ 채널의 *WebSocket 영구 연결. 수 천 / 초 의 *틱 메시지. Boost.Beast 의 *zero-copy 파서 + simdjson 의 *SIMD JSON 디코드Java Jackson 의 *5-10× 빠름. GC pause 자체가 존재하지 않음 → 호가창의 *시각 동기 보장.

데이터 warehouse — 왜 Arrow C++ 인가

수 십 GB / 일write throughput + 컬럼 압축 (zstd) + 다른 언어 / 프로세스와 *0 copy IPC. Spark / Trino / Polars / DuckDB모두 같은 Parquet 을 본다.

3.4 경계의 contract — 3 시간축

[gRPC (지금)]           현재 상태 질의 — Spring Boot Pod 가 외곽 systemd 의 gRPC 호출
[Redis pub/sub (방금)]  이벤트 fan-out — market-feed → Redis → N 개 구독자
[Parquet on R2 (과거)]  replay / BI / 학습 — 과거 데이터 *전부 영구 저장*

지금 / 방금 / 과거 전부세 시간축에 *각자의 도구. 클러스터 안의 64 앱 ↔ 외곽 C++ 6 모듈3 채널로 *느슨하게 결합.


4. 대표 프로젝트 ③ASAT 청각 재활 (eln.lemuel.co.kr)

도메인이 명확한 *fullstack 의 작은 표본. Web Audio API + 적응형 staircase + 헥사고날 + 분석 사이드카기술이 도메인을 *섬길 때 모든 layer 가 *협조 한다.

4.1 왜 이 프로젝트인가

fullstack + 도메인 깊이증명 하는 작품. 백엔드 + 프론트엔드 + 분석 사이드카 + 인프라4 layer 가 *한 도메인 명제기계적으로 환원 된다. 연구용 소프트웨어데이터 무결성 (세션 신뢰도 A/B/C/F)치명적 요구.

4.2 도메인청각 변별 임계 (JND)

JND = Just Noticeable Difference
  주파수 JND: 1000Hz 기준 자극과 X Hz 더 높은 자극을 *구별 가능한* X 의 최소값
  공간 JND:   ILD (Inter-aural Level Difference), ITD (Inter-aural Time Difference)

훈련 트랙 3 종:
  V1 (논문 기반)    2AFC      2-down 1-up (70.7%)    주파수 / ILD / ITD / 복합
  V2 (과업지시서)  3AFC      3-down 1-up (79.4%)    공간 측정 / 훈련
  V3 (소음 속 듣기) 2-interval 2-down 1-up         1kHz 순음 in 백색잡음

4.3 Web Audio APIμs 단위 오디오 합성

const ctx = new AudioContext({ sampleRate: 48000 });

const osc = ctx.createOscillator();
osc.frequency.value = 1000;          // 1 kHz 기준 자극

const panner = ctx.createStereoPanner();
panner.pan.value = -0.5;             // 좌측 50% (ILD)

const delay = ctx.createDelay(0.001);
delay.delayTime.value = 0.000020;    // 20 μs ITD (오른쪽 귀 자극 지연)

osc.connect(panner).connect(delay).connect(ctx.destination);
osc.start();
setTimeout(() => osc.stop(), 200);   // 200ms 자극

Web Audio API 가 *유일한 정답 인가:

  • 적응형 staircase 의 *임계가 *trial 마다 변동. 사전 합성 wav모든 차이값 × 수 천 파일 필요. 합성 = O(1) 메모리, 즉시.
  • 서버 wav 다운로드 0cache invalidation 지옥 0 + 모바일 데이터 0.
  • μs 단위 정밀도서버 wav 의 *양자화 한계를 *넘어섬.

4.4 기술 스택

구분 기술
Backend Java 25 + Spring Boot 4.0.4 + JPA
Frontend Next.js 16 (App Router) + React 19 + TypeScript
오디오 엔진 Web Audio API (OscillatorNode + StereoPannerNode + DelayNode + AnalyserNode)
상태관리 Zustand + TanStack Query
Null Safety JSpecify + NullAway + ErrorProne
데이터베이스 PostgreSQL 16 (V1~V36 Flyway)
분석 사이드카 Python + Prefect + MinIO
Export OpenCSV + Apache POI
인증 JWT (Access 15min + Refresh 7day httpOnly)

4.5 K3s 위의 *6 Pod 운영

louise   (worker)        asat-app (Spring Boot) + asat-frontend (Next.js) + asat-redis
ilwon    (storage)       asat-postgres-0 (NVMe local PV) + asat-minio-0 (asat-reports bucket)
david    (worker)        pg-backup CronJob (매일 PG dump)

외부 노출 흐름:
  사용자 → Cloudflare Edge → Cloudflare Tunnel (외부 systemd) → NodePort 30103
                                                                  ↓
                                                       asat-frontend (Next.js)
                                                                  ↓ in-cluster
                                                       asat-app:8080 (Spring Boot)
                                                                  ↓
                                                       asat-postgres-0 / asat-minio-0

4.6 세션 신뢰도 A/B/C/F데이터 품질 자동 라벨링

연구용 데이터의 치명 한 부분. 측정 자체의 *품질분석의 *유효성 을 결정.

A 등급: reversal 수렴 안정적, RT 분포 정상, 환경 검증 통과
B 등급: reversal 적정 수렴, RT 정상, 환경 검증 통과
C 등급: reversal 수렴 약함 또는 RT 분포 이상 1
F 등급: 연속 동일 응답 비율 > 90%, 또는 환경 검증 실패, 또는 reversal 수렴 안 됨

→ F 등급은 *후속 분석에서 *자동 제외* — *연구 무결성 보장*

5. 횡적 자산블로그 + PR 시리즈

5.1 블로그 (https://myoungsoo7.github.io)

사고를 블로그 한 편 으로 학습 압축 하는 루틴. 같은 사고가 다시 일어나지 않게 + 조직 자산화.

라인 주제
Velero Kopia 좀비 Job postmortem 340 알람 mental model 의 새 layer
K3s 의 C++ 의 자리 318 systemd 외곽 quant-core 의 contract 분리
ASAT eln.lemuel.co.kr 구조 364 fullstack 의 작은 표본
물류 SaaS vs 이커머스 SaaS 398 시간축·정합성·외부 N면 연동 7 가지 차이
(… 기타 Spring AOP / Virtual Threads / 헥사고날 등 …) ~2,500 -

총 4,000+ 라인 의 기술 노트. 각 글이 한 사건어떻게후세 (혹은 미래의 나)읽고 *같은 사고를 *겪지 않게 한다.

5.2 ssgb2e 정산 시스템 *3 단 PR (이번 주)

기존 상용 시스템 (신세계 B2E) 의 *정산 배치 를 *직접 분석 → 제안 → 코드 작성 → PR전 사이클.

PR 효과 라인
#1 멱등성 + 트랜잭션 @Transactional + NOT EXISTS + DB UNIQUE 3 단 멱등 방어 — 중복 적재 90% 차단 +272 / -21
#2 Job 분리 + targetDate 4 INSERT 를 4 독립 Job + Orchestrator운영자 REST API과거 일자 / 부분 재정산 가능 +713 / -69
#3 정합성 검증 + 알람 + Retry (진행 예정) -

6. 면접에서 *진짜 차별화 되는 *3 가지 답변 카드**

Card 1 — “왜 본인이 이 회사 / 이 팀 / 이 직무에 *맞다고 *생각하나?”

코드 / 인프라 / 도메인 의 *3 축이 *동시에 *production-grade 인 사람은 드뭅니다. 6 노드 K3s 를 *혼자 운영 하면서 그 위에서 *Spring Boot 4 + Java 25 + 헥사고날 + Triple Idempotency이커머스 + 정산 MSA 를 굴리고, 그 옆에 *C++ 시장 데이터 파이프라인systemd 외곽으로 *경계 분명히 배치합니다. 단순히 기술을 써본 게 아니라 *왜 그 기술인가 / 어떻게 운영되나 / 깨졌을 때 어떻게 푸나postmortem 8 편 + PR 3 단 분리 까지 명시적으로 *학습 압축 했습니다.*

Card 2 — “본인의 *대표 작품 한 가지를 *3 분 안에 설명해보세요.”

Lemuel 이커머스 + 정산 MSA 를 *모놀리스 → 4 모듈 MSA 로 진화시킨 실제 진화 기록 입니다. 언제 *Bounded Context 를 분리 하는가 가 가장 어려운 결정인데, 변경 속도 (PR 빈도) 가 *팀 단위로 분기 한 시점 을 trigger 로 잡았습니다. 분리 시 Read-only Projection 패턴 으로 settlement-service 가 order-service 코드를 한 줄도 import 하지 않으면서 *DB 직 SELECT데이터를 본다. 메시징은 Triple Idempotency (Outbox → processed_events → DB UNIQUE)3 layer 방어. 그리고 ArchUnit 으로 layer 위반이 *컴파일 fail — 사람이 지키는 게 아니라 *기계가 거절. 이 패턴의 진짜 무게이 시스템 자체 가 아니라 이 시스템을 *팀이 *6 개월 *유지 가능 하게 만든다 는 것이고, 그게 MSA 의 *진짜 가치 라고 생각합니다.*

Card 3 — “본인이 *실패한 사례 *하나 설명해보세요.”

이번 주에 *macOS 의 kubectl 이 *“no route to host” 로 *재현 100% 거짓 응답 하는 사건이 있었습니다. 같은 IP 에 curl 은 200, ping 은 정상. 처음에 *클러스터 노드 *5 대가 동시에 죽었다잘못된 가설세웠고 — *실제로는 *한 노드만 *잠시 NotReady 였고, 내가 ping 한 IP 범위에 *클러스터가 *아닌 LAN 의 무관한 호스트 가 섞여 있었던 게 원인. 이게 진단의 *진짜 *함정 인데, 도구의 거짓말과 *사용자의 잘못된 도구 사용이 *함께 가설을 *왜곡 시킵니다. 해결 후 블로그 한 편 으로 재현 가능한 진단 *우회 회로 (cert + curl + jq)영구 자산화 했습니다. 실패에서 *배운 게 *재발 방지 시스템 으로 전환된다는 게 제가 가장 자신 있는 *루프 입니다.*


7. 마무리이 글을 *읽어주신 분께

기술이 도메인을 *섬길 때 모든 layer 가 *협조 한다. 경계가 분명할 때 두 시스템이 *각자 더 강해진다. postmortem 이 *학습 자산이 될 때 *조직이 *anti-fragile 해진다. 이 *3 원칙이 *제 작업의 *공통된 끈 입니다.

이 글의 모든 layer어느 깊이로 들어가도 *대응 가능 하도록 4 단 깊이 답변 hook 까지 미리 *준비되어 있습니다. *심층 질문 어느 것도 환영 입니다.

읽어주셔서 감사합니다. 깊이 이야기 나누고 싶으시면blog 의 *제 다른 글 도 한 번 봐주시거나, 이메일 / GitHub Issue언제든 ping 주세요.


다음 글: 직접 운영하면서 *진짜로 *결정의 *결을 바꾼 *5 가지 *기술적 *깨달음 — Outbox 의 언제 / DB-Inbox 의 *실효 / Redis 와 Kafka 의 *대체 가능한 구간 / Hexagonal 의 *진짜 비용 / postmortem 의 루틴화.