Architecture & Design/Software Architecture
Foundations - 02. 품질 속성
"비기능적 요구사항"이라는 이름이 아키텍처를 망치는 이유
차를 산다고 하자. 두 차 모두 "달린다, 멈춘다, 돈다"라는 같은 기능을 한다. 그런데 한 대는 리터당 15km를 가고 충돌 테스트 별 다섯 개를 받았으며 정비가 쉽고, 다른 대는 리터당 5km에 충돌 테스트 별 하나에 부품이 비싸다. 같은 기능을 해도 이 두 차는 전혀 다른 차다. 그 차이를 만드는 것 — 연비, 안전, 정비 편의성, 가속력 — 이 차의 품질 속성이다.
소프트웨어도 같다. 주문을 받고 결제하고 재고를 깎는 같은 기능을 해도, 하루 1만 건을 버티는 시스템과 1억 건을 버티는 시스템은 같은 기능의 다른 "차"다. 그런데 전통적으로 이런 차이를 "비기능적 요구사항"이라 불렀다. 이 이름이 문제다.
"비기능적"이라는 말의 함정
요구사항 문서를 펼치면 뒤쪽에 "비기능적 요구사항"이라는 절이 있다. "고부하에서 안정적", "보안 유지", "빠른 응답". 그 절엔 보통 "필수" 표시도, 측정 기준도, 책임자도 없다. 그리고 일정에 쫓기면 가장 먼저 잘려나간다 — "나중에 성능 튜닝할 때 보자."
"비기능적(non-functional)"이라는 단어는 "기능적이지 않은", 곧 "부수적인, 덜 중요한"으로 들린다. 연비·안전·정비 편의를 "비기능"이라 부르며 카탈로그 맨 뒤에 "나중에 볼 것"으로 밀어넣는 셈인데, 연비와 안전이야말로 두 차를 갈라놓는 진짜 요소다. Mark Richards와 Neal Ford는 에서 이 명명을 비판한다. 그 "비기능"이라 불린 것들 — 성능·가용성·확장성·보안·유지보수성 — 이야말로 아키텍처를 결정하는 요소(architectural drivers)라고. 이것들이 없으면 기능적 요구사항은 그저 "어떤 구조에서든 돌아가는 코드"일 뿐, 아키텍처에 대한 정보를 주지 않는다.
그래서 현대 담론은 이들을 "비기능적 요구사항"이 아니라 품질 속성, 아키텍처 특성, 아키텍처 드라이버라 부른다. 이름을 바꾸는 건 수사가 아니다. "이건 부수적이다"라는 암시를 끊어내는 실천적 조치다.
같은 기능이라도 품질 속성이 다르면 구조가 다르다
같은 기능을 해도 품질 속성이 다르면 아키텍처가 근본적으로 달라진다. 주문 처리를 예로:
| 상황 | 우선하는 품질 속성 | 자연스럽게 나오는 구조 |
|---|---|---|
| 스타트업, 하루 수백 건, 빠른 기능 추가 | 민첩성·단순성·유지보수성 | 단일 모놀리스, 공유 DB |
| 대형 쇼핑몰, 피크 트래픽, 부분 장애 불가 | 확장성·가용성·회복탄력성 | 서비스 분리, 비동기 메시징, 자동 확장 |
| 금융 결제, 규제 대상, 감사 필수 | 일관성·보안·신뢰성·추적성 | 강일관성 DB, 동기 트랜잭션, 변경 로그 |
세 시스템 모두 "주문을 결제한다"는 같은 기능을 수행한다. 우선하는 품질 속성이 달라 전혀 다른 구조를 갖는다. 기능이 아키텍처를 결정하지 않는다 — 품질 속성이 결정한다. 이것이 "비기능적"이라는 이름이 해로운 이유다. 부수적으로 취급하면 아키텍처를 이끄는 진짜 힘을 놓친다.
측정 가능하지 않으면 요구사항이 아니다
품질 속성의 다른 함정은 추상성이다. "고가용성", "확장 가능", "빠르게" — 이런 문장은 검증도, 설계도 이끌지 못한다. 누가, 어떤 상황에서, 무엇을 기준으로 "빠르다"고 판정하는가가 없으면 그건 소원이지 요구사항이 아니다.
Bass 등은 품질 속성을 시나리오로 구체화해야 비로소 다룰 수 있다고 본다. 시나리오 여섯 요소는 자극, 자극원, 환경, 산물, 응답, 응답 측정이다. "결제 시스템은 고가용성이어야 한다"는 설계를 이끌지 못하지만, "결제 노드 1대가 장애를 일으키면(자극) 정상 영업 시간에(환경) 다른 노드로 요청을 우회시키고(응답) 복구 시간 30초 이내·가동률 99.95%(측정)를 유지한다"는 즉시 헬스체크와 자동 우회, 멀티 노드 배치로 이어진다. 측정 가능한 품질 속성만이 아키텍처를 움직인다.
| 품질 속성 | 자극 | 환경 | 응답/측정 기준 |
|---|---|---|---|
| 확장성 | 주문 트래픽 피크 | 블랙프라이데이 | p99 응답 500ms 이하, 수평 확장으로 흡수 |
| 가용성 | 결제 서비스 장애 | 정상 영업 시간 | 주문은 계속 수용, 결제는 큐 적체 후 복구 |
| 일관성 | 같은 재고 동시 주문 | 동시성 | 초과 주문(오버셀) 0건, 잔여 재고 정확 |
품질 속성의 분류와 전술(tactics)
품질 속성은 무한히 많아 보이지만, 아키텍처 담론은 몇 갈래로 묶어 다룬다. Bass의 분류에 현대 보강을 합친 대략적 지도:
| 범주 | 대표 속성 | 아키텍처가 묻는 질문 |
|---|---|---|
| 가용성 | availability, reliability, resilience | 장애가 났을 때 어떻게 버티나 |
| 성능·확장성 | performance, scalability, latency | 부하가 늘어도 응답을 어떻게 유지하나 |
| 수정 용이성 | modifiability, maintainability, testability | 변경이 얼마나 국소에 머무나 |
| 운영·배포 | deployability, operability, observability | 출시·운영이 얼마나 자동화·가시화되나 |
| 보안 | security, auditability | 비인가 접근·위변조를 어떻게 막나 |
| 사용성 | usability, accessibility | 사용자가 얼마나 쉽게 쓰나 |
각 속성에는 그것을 달성하기 위한 전술(tactic)이 있다. 전술이란 그 속성을 높이기 위한 설계 수단이다. 가용성의 전술엔 장애 감지(heartbeat), 회복(페일오버, 상태 비저장 복구), 예방(서킷 브레이커)이 있다. 이 전술들은 분산 시스템의 회복탄력성, 복제·합의 같은 주제로 이어지는 재료가 된다.
하나의 전술은 거의 항상 다른 품질 속성을 희생시킨다. 강한 일관성을 위해 동기 복제를 쓰면 가용성·응답성이 떨어진다. 회복탄력성을 위해 재시도·서킷 브레이커를 두면 시스템이 복잡해지고 디버깅이 어려워진다. 품질 속성 사이에도 트레이드오프가 있다. "모든 걸 동시에 최대화하라"는 요구는 실현 불가능한 소원이다.
비즈니스 목표가 우선순위를 정한다
여러 품질 속성 사이의 트레이드오프를 정하는 건 기술이 아니라 비즈니스 목표다.
- "3개월 내 출시"가 최우선 → 민첩성·단순성이 확장성·강일관성을 이긴다. 빠르게 모놀리스로.
- "금융 규제 준수"가 최우선 → 보안·추적성·강일관성이 응답성을 이긴다. 동기 트랜잭션, 변경 로그.
- "글로벌 확장"이 최우선 → 확장성·가용성이 강일관성을 이긴다. 최종 일관성 수용, 다중 리전.
이 우선순위를 정량화하지 않으면 흔들린다. 그래서 일부 조직은 품질 속성 워크숍(QAW)이나 ATAM 같은 절차로 이해관계자를 모아 각 속성의 우선순위와 시나리오를 명시적으로 합의한다. 우선순위를 암묵적으로 두면 가장 목소리 큰 이해관계자의 관심사가 이기고, 명시적 합의가 있어야 구조가 비즈니스 목표를 따른다.
설계 사례 — 전자상거래 시스템의 품질 속성 설계
온라인 쇼핑몰의 품질 속성을 시나리오로 구체화하고, 그 시나리오가 어떤 아키텍처 결정으로 이어지는지 본다. "빠르고 안정적" 같은 추상이 아니라, 측정 가능한 시나리오가 구조를 만든다.
flowchart TD
QA1["확장성 시나리오<br/>피크 시 p99 500ms"] --> Dec1["읽기 복제 + 캐싱"]
QA2["가용성 시나리오<br/>결제 장애 시 주문 지속"] --> Dec2["비동기 이벤트<br/>+ 큐 버퍼"]
QA3["일관성 시나리오<br/>동시 주문 시 오버셀 0건"] --> Dec3["애그리거트 트랜잭션<br/>+ 분산 락"]
각 시나리오가 구체적 설계 결정으로 직결된다:
// 확장성 시나리오: "블랙프라이데이 트래픽 10배 시 p99 500ms 유지"
// → 설계 결정: 읽기 복제 + Redis 캐싱
public class ProductQueryService {
private final ProductReadStore replica; // 읽기 전용 복제본
private final Cache cache; // Redis
public Product findById(ProductId id) {
return cache.get(id)
.orElseGet(() -> replica.findById(id)); // 캐시 미스 시 복제본 조회
}
}
// 가용성 시나리오: "결제 서비스 장애 시 주문은 '결제 대기'로 수용"
// → 설계 결정: 비동기 이벤트 + 타임아웃
public class OrderService {
public void place(Order order) {
orders.save(order);
events.publish(new OrderPlaced(order.id())); // 결제는 비동기로
} // 결제 서비스가 죽어도 주문은 받힘 — 큐가 버퍼
}
// 일관성 시나리오: "동일 상품 동시 주문 시 재고 음수 방지"
// → 설계 결정: 애그리거트 내 트랜잭션 + 낙관적 잠금
public class InventoryItem {
private int quantity;
@Version private long version; // 낙관적 잠금
public void decrease(int qty) {
if (quantity < qty)
throw new OutOfStockException();
quantity -= qty;
}
}
세 시나리오가 서로 영향을 주고받는다. 가용성(비동기)을 택하면 일관성(재고 차감 시점)이 느슨해진다 — 주문은 성공하지만 재고 차감이 나중에 실패할 수 있다. 이때 보상(주문 취소 이벤트)으로 복구한다. 확장성(읽기 복제)을 택하면 일관성이 최종적이 된다 — 상품 정보가 복제 지연으로 잠깐 다를 수 있다. 품질 속성 간의 트레이드오프가 코드에 직접 드러난다.
이 설계에서 "비기능적 요구사항"이라는 절은 없다. 대신 세 시나리오가 각자 측정 기준(p99 500ms, 결제 장애 시 주문 지속, 오버셀 0건)을 갖고, 그 기준이 구체적 코드(캐싱, 비동기, 낙관적 잠금)로 이어진다. 시나리오가 없으면 이 코드는 "나중에 성능 튜닝할 때"로 미뤄졌을 것이다.
카탈로그 뒤쪽에 밀어넣지 말 것
연비·안전·정비 편의를 "비기능"이라 부르며 카탈로그 맨 뒤에 밀어넣은 차가 잘 팔리기 어려운 것처럼, 품질 속성을 "비기능적 요구사항" 절에 묻어둔 시스템은 아키텍처가 흔들리기 쉽다. 다음에 요구사항 문서를 쓸 때, "비기능적 요구사항" 절을 만들지 않는 편이 낫다. 대신 각 품질 속성을 시나리오와 측정 기준으로 적고, "이것이 왜 우선인가"를 비즈니스 목표로 연결한다. 그 문서 한 장이 "나중에 튜닝할 때 보자"로 미뤄진 한 줄보다 아키텍처에 대해 훨씬 많은 것을 결정한다. 두 차를 갈라놓는 건 "달린다"가 아니라 연비와 안전 등급이니까.
참고
- Bass, Clements, Kazman — (4th ed, Addison-Wesley, 2021), Ch.4·5·16·21 — 접근 2026-07-15
- Richards, Ford — (O'Reilly, 2020), Ch.3 ("비기능적" 명명 비판)
'Architecture & Design > Software Architecture' 카테고리의 다른 글
| Foundations - 06. Conway's Law와 팀 토폴로지 (0) | 2026.07.15 |
|---|---|
| Foundations - 05. 기술 부채와 의사결정 (0) | 2026.07.15 |
| Foundations - 04. 아키텍처 문서화 (0) | 2026.07.15 |
| Foundations - 03. 결합과 응집 (0) | 2026.07.15 |
| Foundations - 01. 아키텍처란 무엇인가 (0) | 2026.07.15 |