개발 기술 · 더 알아보기 · 직접 체험형

마지막 한 자리에 두 사람이 신청하면, 서버는 어떻게 처리할까요?

두 사람의 앱에 같은 한 자리가 보일 때, 신청 버튼을 누른 뒤 서버가 자리를 배정하고 결과를 화면에 돌려주는 과정을 살펴봅니다.

두 사람의 신청자리 차감 · 신청 저장저장 내역 다시 확인
마지막 한 자리에 두 사람이 신청하면, 서버는 어떻게 처리할까요?. 신청 대상을 POST로 전송하고, 서버가 조건을 확인해 자리 차감과 신청을 함께 저장한 뒤 GET으로 저장 결과를 조회하는 네 단계.

작동으로 살펴보는 개발 과정

신청 버튼부터 저장 결과까지, 한 흐름으로 보세요

왼쪽은 두 사람의 앱, 오른쪽은 요청을 함께 처리하는 서버입니다. ‘두 사람이 동시에 신청’을 누르면 요청이 어디로 가고, 어떤 결과가 돌아오는지 볼 수 있습니다.

앱에서 신청 → 서버에서 자리 배정 → 각 앱에 결과 전달

시연만 크게 보기 ↗

단계별 개발 판단

앱의 신청을 서버의 저장 결과까지 연결합니다

마지막 한 자리가 남은 일정에 두 사람이 신청합니다. 각자의 앱에는 모두 1자리가 보입니다. 하지만 한 자리를 두 사람에게 배정할 수는 없습니다. 그래서 신청 버튼을 누른 뒤에는 서버가 현재 남은 자리를 기준으로 신청을 처리해야 합니다.

앱은 사용자가 누르고 결과를 보는 화면입니다. 서버는 여러 사람의 요청을 받아 조건을 확인하고 저장하는 곳입니다. 데브크래프트는 두 부분 사이에 어떤 값을 보내고, 언제 저장하며, 어떤 결과를 보여줄지 연결해 기능을 구현합니다.

이번 흐름은 신청 저장이 완료되면 바로 한 자리를 확보하는 방식입니다. 두 사람의 서로 다른 요청 중 신청 1건만 저장되고, 다른 요청은 마감으로 안내되며, 잔여 수가 0이 되는 과정을 다룹니다. 결제·운영자 승인·대기자 접수는 별도의 흐름입니다.

신청 요청과 저장 조건을 그림으로 보기
A와 B의 POST 요청 본문, 서버의 대상 확인, remaining이 영보다 클 때만 자리 차감하는 데이터베이스 조건과 갱신 한 행·영 행에 따른 저장 분기.
앱에서 보낸 신청 대상을 서버가 확인하고, 남은 자리에 따라 저장 결과를 나눕니다.

개발 단계 01

앱은 서버에 남은 자리를 묻습니다

앱의 ‘남은 자리 1개’는 서버가 알려준 값입니다. 앱을 열면 일정 정보를 요청하고, 서버는 저장소에서 현재 남은 자리를 읽어 돌려줍니다. 앱은 받은 숫자를 화면에 표시합니다.

여기서 앱과 서버를 연결하는 요청 창구가 API입니다. 이 시연은 일정 조회 API로 일정 번호와 남은 자리 수를 받습니다. 같은 일정을 보던 두 사람이 1자리를 읽었더라도, 그 숫자는 조회 당시의 상태이므로 신청이 완료됐다는 뜻은 아닙니다.

앱에서 하는 일

서버가 알려준 일정과 남은 자리를 화면에 표시

서버에서 하는 일

저장된 일정과 남은 자리 수를 읽어 전달

실제 요청 값과 저장 조건 보기
조회 응답에서 화면에 반영하는 항목
GET /api/state
{ "slotId": "SESSION-1400",
  "remaining": 1, "applicationCount": 0 }
구현 판단과 예외 상황 더 보기

두 사람이 같은 시점의 정보를 읽으면 각자의 앱에 잔여 1자리가 보일 수 있습니다. 이 값은 조회 당시의 상태이며, 이후 누군가 신청하면 바뀔 수 있습니다. 따라서 조회 응답을 받았다는 사실과 내 자리를 확보했다는 사실을 구분합니다.

화면의 출발점도 서버의 값에서 만들어집니다. 이후 신청 요청에서는 예전에 읽은 숫자를 성공 근거로 재사용하지 않고, 서버가 현재 저장 상태를 기준으로 다시 판단하도록 연결합니다.

개발 단계 02

신청 버튼이 서버에 신청 내용을 보냅니다

신청 버튼을 누르면 앱은 어느 일정에 신청하는지 서버에 보냅니다. 화면에 보이는 잔여 숫자나 ‘신청 성공’이라는 결론을 보내는 것이 아닙니다. 사용자의 신청 의사를 전달하고 서버의 답을 기다리는 단계입니다.

시연에서는 신청자 A와 B가 각자 다른 요청을 보냅니다. 각 앱의 신청 버튼으로 하나씩 보낼 수도 있고, ‘두 사람이 동시에 신청’으로 두 요청을 함께 보낼 수도 있습니다. 서버는 실제 서비스에서 확인한 로그인 정보와 신청 대상을 연결해 요청자를 판단하도록 구현합니다.

앱에서 하는 일

어느 일정에 신청하는지 서버에 전달

서버에서 하는 일

보낸 값과 신청 대상·신청자를 확인

실제 요청 값과 저장 조건 보기
신청 요청의 본문 구조
POST /api/signup
X-Demo-Actor: A
{ "slotId": "SESSION-1400",
  "runId": "현재 조회에서 받은 회차 값" }
구현 판단과 예외 상황 더 보기

앱은 요청 중인 화면을 표시하고 같은 버튼의 반복 클릭을 막습니다. 하지만 다른 사람의 기기에서 오는 요청까지 앱 버튼으로 제어할 수는 없습니다. 두 요청을 실제로 받은 뒤의 판단은 서버와 저장소에서 진행해야 합니다.

요청 본문은 신청 의사를 전달하는 값입니다. 앱은 POST를 보냈다는 이유로 신청 완료를 그리지 않고, 서버가 저장을 끝낸 뒤 돌려주는 응답을 기다립니다.

개발 단계 03

서버가 누가 어떤 일정에 신청했는지 확인합니다

서버는 요청을 받았다고 바로 신청 완료로 저장하지 않습니다. 필요한 값이 있는지, 어떤 일정인지, 요청자가 누구인지부터 확인합니다. 잘못된 일정이나 이전 시연 회차의 요청은 여기서 거절합니다.

그다음에는 현재 일정과 같은 사람의 기존 신청을 저장소에서 다시 읽습니다. 앱에 처음 보였던 ‘1자리’를 그대로 믿지 않고, 지금 처리할 대상의 상태를 확인하는 것입니다. 실제 서비스에서는 접수 기간과 이용 자격, 중복 신청 기준도 함께 정합니다.

앱에서 하는 일

신청 중 표시를 유지하며 서버의 결과를 기다림

서버에서 하는 일

보낸 값 확인 → 일정 찾기 → 기존 신청 확인

실제 요청 값과 저장 조건 보기
서버에서 대조하는 기준
신청 대상: SESSION-1400
요청자 구분: A 또는 B
회차: 현재 저장된 회차와 일치
기존 신청: 같은 회차의 신청 여부
구현 판단과 예외 상황 더 보기

시연은 두 HTTP 요청마다 별도의 데이터베이스 연결을 사용합니다. SQLite에서는 쓰기 트랜잭션을 시작한 연결이 처리하는 동안 다른 쓰기 연결이 기다릴 수 있습니다. 서버의 대상 확인과 변경을 같은 쓰기 트랜잭션 안에서 이어가며, 화면에서 신청자를 임의로 성공시키지 않습니다.

화면의 서버 처리 기록에는 요청 식별값, 대상 조회, 기존 신청 확인과 트랜잭션 시작이 실제 실행된 순서로 남습니다. 설명 과정을 선택하는 행동은 화면을 바꾸고, 실제 요청을 보낸 뒤에만 이 기록이 생깁니다.

개발 단계 04

서버가 자리 차감과 신청 내역을 함께 저장합니다

자리 하나를 먼저 차감한 요청에만 신청 내역을 남겨야 합니다. 그래서 서버는 ‘남은 자리가 있을 때만 1개를 차감한다’는 조건을 저장 작업에 넣습니다. 앱 두 곳에 1자리가 보였더라도 이 조건을 만족한 요청 하나만 자리를 확보합니다.

자리를 확보한 요청은 자리 차감과 신청 내역을 한 묶음으로 확정합니다. 다음 요청이 같은 자리를 처리할 때는 남은 수가 0이므로 신청 내역을 만들지 않습니다. 어느 화면을 먼저 눌렀는지 화면에서 추측해 완료를 표시하는 방식이 아닙니다.

앱에서 하는 일

저장이 끝났다는 서버의 답을 기다림

서버에서 하는 일

자리가 있을 때만 차감 → 신청과 함께 저장

실제 요청 값과 저장 조건 보기
실제로 실행하는 차감 문장의 핵심 조건
UPDATE slots
SET remaining = remaining - 1
WHERE slot_id = ? AND remaining > 0
RETURNING remaining;

갱신 1행 → 신청 INSERT · COMMIT
갱신 0행 → 신청 INSERT 없음 · ROLLBACK
구현 판단과 예외 상황 더 보기

차감과 신청 저장을 함께 묶는 이유는 중간 실패 때문입니다. 차감 뒤 신청 INSERT가 실패하면 ROLLBACK해 자리도 원래대로 돌립니다. 단순히 트랜잭션이라는 이름을 붙이는 것만으로 충분한 것이 아니라, 차감 조건과 데이터베이스의 동시 쓰기 규칙까지 맞춰야 합니다.

아래 시연의 실제 서버 기록에서 갱신 1행과 0행, 신청 INSERT 유무, COMMIT과 ROLLBACK을 나란히 확인할 수 있습니다. 이 결과가 한 사람의 신청만 남는 이유이며, 먼저 누른 화면을 추측해 성공자를 정하는 처리가 아닙니다.

개발 단계 05

서버의 답에 따라 앱 화면이 달라집니다

저장이 끝난 서버는 해당 앱에 신청 완료와 신청 번호를 돌려줍니다. 앱은 이 답을 받은 뒤 ‘신청 완료’를 표시합니다. 버튼을 눌렀다는 이유만으로 먼저 성공 문구를 보여주지 않습니다.

자리를 확보하지 못한 요청에는 마감 결과가 돌아옵니다. 그래서 같은 시점에 신청한 두 앱의 화면이 서로 달라집니다. 통신이 끊기거나 서버에 문제가 생긴 경우는 마감과 구분해, 저장 결과를 다시 확인할 수 있도록 안내합니다.

앱에서 하는 일

받은 결과에 맞게 신청 완료 또는 마감 표시

서버에서 하는 일

저장 결과에 맞는 답을 각 앱에 전달

실제 요청 값과 저장 조건 보기
저장 결과에 따라 달라지는 응답
201 Created
{ "status": "APPLIED", "applicationId": "서버가 만든 신청 번호" }

409 Conflict
{ "error": { "code": "CAPACITY_FULL" } }
구현 판단과 예외 상황 더 보기

브라우저의 fetch는 409 같은 HTTP 응답도 정상적으로 받아올 수 있으므로, 통신 예외 처리만으로 성공과 실패를 나눌 수 없습니다. 이 시연은 실제 응답 상태와 본문을 함께 읽어 완료·마감·확인 필요를 각각 다른 화면 상태로 연결합니다.

응답을 받지 못했다면 저장이 실패했다고 단정하지 않습니다. 서버에 저장은 됐지만 응답만 도착하지 않았을 수 있으므로, 결과 확인 필요로 안내하고 내 신청 내역을 별도로 읽을 수 있게 합니다.

개발 단계 06

앱이 저장된 신청 내역을 다시 불러옵니다

결과 문구가 표시된 뒤에는 서버에서 신청 내역을 다시 불러옵니다. 이때 서버는 저장소를 새로 읽습니다. 앞에서 받은 완료 응답을 그대로 복사해 ‘저장됐다’고 보여주는 것이 아닙니다.

다시 읽은 결과에서는 한 사람의 신청만 남아 있어야 합니다. 완료된 앱의 신청 번호가 저장된 번호와 같은지, 다른 사람에게 신청이 만들어지지 않았는지, 전체 남은 자리가 0인지 함께 대조합니다.

앱에서 하는 일

다시 불러온 값으로 신청 내역과 남은 자리 표시

서버에서 하는 일

저장된 신청과 일정의 현재 값을 다시 읽음

실제 요청 값과 저장 조건 보기
POST 이후 별도로 확인하는 값
GET /api/application · A/B 각각 조회
성공자: application.id = 같은 신청 번호
마감자: application = null

GET /api/state
applicationCount = 1 · remaining = 0
구현 판단과 예외 상황 더 보기

앱은 이 GET 응답으로 저장 내역 카드와 현재 잔여 수를 다시 그립니다. 페이지를 새로고침했을 때도 앱 메모리에 남은 성공 문구가 아니라 서버의 현재 저장값을 읽습니다. 서버 실행 기록도 별도 조회로 읽어 어느 요청이 어떤 조건을 통과했는지 대조할 수 있습니다.

개발 검수에서는 두 요청의 201/409, 갱신 1행/0행, 신청 INSERT 1건과 전체 잔여 0을 연결해 봅니다. 중간 저장 실패의 롤백도 별도로 확인합니다. 이 시연은 이 처리 흐름을 보여주며, 대규모 부하·다중 서버 운영·실제 로그인 체계를 검증했다는 의미는 아닙니다.

처리 결과와 저장 내역을 그림으로 대조하기
신청자 B의 갱신 한 행·INSERT·COMMIT·201 응답과 다른 신청자의 갱신 영 행·롤백·409 응답을 나란히 보여주고 별도 GET의 신청 번호 AP-3185B4, 신청 한 건·잔여 영 자리를 대조하는 화면.
서버가 실제로 처리한 결과를 앱의 안내와 연결하고, 저장된 신청 내역을 다시 읽어 대조합니다.
처리 원리의 참고 자료

PostgreSQL · 동시 갱신과 트랜잭션 격리 · 동시 갱신 시 조건 재확인과 격리 수준에 따른 처리 원리를 참고했습니다. 이 시연은 SQLite를 사용합니다.

SQLite · 트랜잭션과 쓰기 처리 · 시연의 쓰기 트랜잭션·커밋·롤백 동작을 확인하는 근거입니다.

SQLite · UPDATE 뒤의 RETURNING 결과 · 실제로 변경한 행의 값을 받아 조건부 차감 결과를 확인하는 근거입니다.

MDN · fetch의 응답 상태와 JSON 처리 · HTTP 오류 상태와 통신 예외를 구분하고 응답 본문을 읽는 동작의 근거입니다.

여러 사람이 함께 쓰는 기능의 처리 기준을 정리해 보세요

예약·수업·재고처럼 여러 사용자가 같은 수량을 바꾸는 기능을 준비하고 있다면, 신청이 언제 완료되는지와 실패 뒤 무엇을 확인할지부터 함께 정리합니다.

30분 무료 상담 신청
앱이 만들어지는 방식으로 돌아가기