시퀀스
고친 사람 github-actions[bot]
시퀀스는 여럿을 한 줄로 세워 누가 앞이고 누가 뒤인지를 정해 줍니다. 그런데 무엇을 세우느냐에 따라 다른 물건을 가리킵니다. 프로그래밍에서는 번호로 하나씩 꺼낼 수 있는 값의 모음입니다. 데이터베이스에서는 겹치지 않는 번호를 차례로 내주는 장치입니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 순서를 뜻으로 삼는 이름입니다. 사과 · 포도 · 귤을 이 차례로 담아 둔 모음도 시퀀스입니다. 주문 번호를 1 · 2 · 3 으로 내주는 데이터베이스 장치도 시퀀스입니다. 두 뜻은 앞뒤가 정해져 있다는 점만 같습니다. 나머지는 남입니다.
왜 이렇게 하나 — 값의 시퀀스는 몇 번째인지로 값을 찾을 수 있어야 해서입니다. 번호를 내주는 시퀀스는 여러 곳에서 동시에 데이터를 넣어도 번호가 겹치지 않아야 해서입니다.
어떻게 도나
- 값의 모음은 맨 앞부터 번호를 붙여 둡니다. 번호를 대면 그 값을 건넵니다
- 번호를 내주는 장치는 다음 수를 한 군데에 적어 둡니다. 달라고 할 때마다 하나 올려 건넵니다
- 순서를 표시하는 번호도 같은 방식으로 하나씩 올립니다. 받는 쪽은 번호를 보고 빠진 것과 뒤바뀐 것을 압니다
대가 — 번호를 내주는 장치는 모두가 한 군데에 물어보게 되어 그곳이 병목이 됩니다. 받아 간 번호를 쓰지 않고 버려도 되돌려주지 않아 중간에 빈 번호가 생깁니다. 차례 자체가 정보일 때 씁니다. 번호가 끊기면 안 되는 곳에는 쓰지 않습니다.
상세
「시퀀스」라고 부르는 것들을 나란히 놓고 봅니다.
시퀀스가 가리키는 것들
네 맥락에서 시퀀스가 무엇을 가리키는지를 나란히 놓습니다. 같은 칸은 순서이고, 갈리는 칸은 늘어선 것입니다.
| 맥락 | 시퀀스가 가리키는 것 | 예 |
|---|---|---|
| 프로그래밍 언어 · 자료구조 | 번호를 매겨 하나씩 꺼내는 값의 모음 | 배열 · 연결 리스트 · 문자열 |
| 관계형 데이터베이스 | 겹치지 않는 정수를 차례로 내주는 장치 | 주문 번호를 매기는 시퀀스 |
| 네트워크 · 동시성 | 순서를 표시하려고 하나씩 올리는 번호 | 세그먼트마다 붙는 시퀀스 번호 |
| 수학 | 순서가 정해진 수의 나열 | 1 · 2 · 4 · 8 로 이어지는 수열 |
네 뜻이 함께 가진 것은 앞뒤가 정해져 있고 그 순서 자체가 뜻을 진다는 점입니다. 갈리는 것은 늘어선 것이 값이냐 번호냐입니다.
백엔드 개발자가 자주 만나는 것은 앞의 셋입니다. 아래에서 하나씩 봅니다.
값이 늘어선 시퀀스
값이 늘어선 시퀀스는 원소를 정해진 순서로 담아 두고 몇 번째인지로 꺼내게 해 주는 모음입니다. 사과 · 포도 · 귤을 이 차례로 담은 모음이 그렇습니다. 맨 앞이 사과, 그다음이 포도, 마지막이 귤이고 이 순서는 손대기 전까지 바뀌지 않습니다.
몇 번째인지를 가리키는 번호가 인덱스입니다. 대개 맨 앞을 0번으로 셉니다. 1번을 달라고 하면 포도가 나옵니다.
순서가 뜻을 진다는 점이 집합과 갈리는 대목입니다. 집합은 무엇이 들어 있는지만 따지므로 사과와 포도를 넣은 차례가 달라도 같은 집합입니다. 시퀀스는 차례가 다르면 다른 시퀀스입니다. 같은 값을 여러 번 담아도 각각 제 번호를 갖습니다.
그래서 차례 자체가 정보일 때 시퀀스를 씁니다. 장바구니에 담은 차례, 로그가 쌓인 차례가 그렇습니다.
시퀀스는 담는 방법까지 정하지는 않습니다. 무엇을 할 수 있는지만 정해 두고, 어떻게 담을지는 쓰는 쪽이 고릅니다. 이런 약속을 추상 자료형이라고 합니다. 배열과 연결 리스트, 글자를 늘어놓은 문자열이 모두 시퀀스입니다.
담는 방법이 갈리면 값을 꺼내는 품도 갈립니다. 아래는 같은 세 값을 두 방법으로 담은 모습입니다.
flowchart TD
subgraph A["배열로 담았다 · 값들이 붙어 있다"]
A0["0번 사과"] --- A1["1번 포도"] --- A2["2번 귤"]
end
subgraph B["연결 리스트로 담았다 · 각자 다음 것의 주소를 든다"]
B0["사과"] -->|주소| B1["포도"]
B1 -->|주소| B2["귤"]
end
배열은 값들을 같은 크기로 붙여 놓습니다. 그래서 몇 번째인지만 알면 그 값이 어디에 있는지가 곧바로 셈해집니다. 2번을 달라고 하면 사과와 포도를 건너뛰고 귤로 갑니다.
연결 리스트는 값마다 다음 값의 주소만 들고 있습니다. 어디에 있는지를 셈할 수 없으니 2번을 찾으려면 사과에서 포도를 거쳐 귤까지 하나씩 따라가야 합니다.
이 때문에 「시퀀스의 조회 비용」이라는 말은 성립하지 않습니다. 비용은 시퀀스가 아니라 담는 방법이 정합니다. 시퀀스를 받아 몇 번째 값을 되풀이해 꺼내는 함수를 떠올려 보면 됩니다. 받은 것이 배열이면 꺼낼 때마다 한 번에 끝납니다. 연결 리스트면 꺼낼 때마다 맨 앞에서 다시 따라갑니다.
번호를 내주는 시퀀스
관계형 데이터베이스에서 시퀀스는 값의 모음이 아닙니다. 부를 때마다 다음 정수를 하나 건네주는 장치입니다. 한 번 건넨 수는 다시 건네지 않습니다.
새 행을 넣을 때마다 그 행을 가리킬 식별자가 하나씩 필요합니다. 직접 「지금 가장 큰 번호에 하나를 더한다」로 만들면, 두 요청이 같은 순간에 가장 큰 번호를 읽어 같은 값을 쓰게 됩니다. 이렇게 동시에 일어난 순서에 따라 결과가 달라지는 것이 경쟁 상태입니다.
sequenceDiagram
participant A as 요청 A
participant B as 요청 B
participant T as 주문 테이블
A->>T: 가장 큰 번호를 읽는다
B->>T: 가장 큰 번호를 읽는다
Note over A,B: 둘 다 3 을 받았다
A->>T: 4 를 넣는다
B->>T: 4 를 넣는다
Note over A,B: 같은 번호가 두 번 쓰였다 · 시퀀스에 물으면 이 겹침이 안 난다
시퀀스는 다음 수를 적은 장부를 한 군데만 둡니다. 그 장부는 한 번에 한 요청씩만 수를 올립니다. 그래서 두 요청이 같은 수를 받지 못합니다. 만들어 두고 부르면 이렇게 돕니다.
CREATE SEQUENCE order_seq;
SELECT nextval('order_seq'); -- 1
SELECT nextval('order_seq'); -- 2
SELECT nextval('order_seq'); -- 3
부를 때마다 값이 하나씩 올라갑니다. 누가 부르든 같은 수를 두 번 받지 않습니다.
시퀀스가 하는 약속은 「겹치지 않는다」입니다. 「빈틈없이 이어진다」는 약속은 하지 않습니다. 번호를 받아 간 작업 묶음(트랜잭션)이 도중에 취소돼 통째로 없던 일이 되어도 그 번호는 돌아오지 않기 때문입니다. 주문 번호가 1 · 2 · 4 로 남는 일이 흔합니다.
sequenceDiagram
participant R as 요청
participant S as 시퀀스
participant T as 주문 테이블
R->>S: 번호를 달라고 한다
S-->>R: 1 · 2 · 3 을 차례로 건넨다
R->>T: 1 번 행과 3 번 행을 넣는다
Note over R,T: 2 번을 받은 요청은 도중에 취소됐다
R->>S: 다음 번호를 달라고 한다
S-->>R: 4
Note over S: 건넨 수는 되돌려받지 않는다
이 성질을 모르고 「번호가 비었으니 데이터가 지워졌다」고 읽으면 엉뚱한 곳을 뒤지게 됩니다. 몇 건이 들어왔는지는 마지막 번호로 짐작하지 말고 행을 세어야 합니다.
장부가 한 군데라 번호를 받으려면 모두 그곳에 물어봐야 합니다. 요청이 몰리면 이곳이 병목이 됩니다. 번호를 한 번에 여러 개 받아 두고 나눠 쓰는 방법이 있습니다. 각자 따로 만들어도 겹치지 않는 UUID(Universally Unique Identifier, 범용 고유 식별자) 같은 식별자를 쓰기도 합니다.
이름은 데이터베이스마다 갈립니다. 오라클과 PostgreSQL 은 시퀀스라는 장치를 테이블과 별개로 세웁니다. MySQL 은 열에 붙이는 자동 증가 속성으로 같은 일을 합니다. 하는 일은 같습니다. 앞의 것은 여러 테이블이 나눠 쓸 수 있습니다. 뒤의 것은 그 열에 매여 있습니다.
순서를 표시하는 시퀀스 번호
셋째 뜻은 번호 하나를 가리킵니다. 데이터를 조각내어 보낼 때는 조각마다 번호를 붙입니다. 한 값을 여러 번 고칠 때는 고칠 때마다 번호를 하나 올립니다. 이 번호가 시퀀스 번호입니다. 줄여서 그냥 시퀀스라고 부르기도 합니다.
TCP(Transmission Control Protocol, 전송 제어 프로토콜)가 이 방식을 씁니다. 보내는 쪽은 데이터를 세그먼트로 쪼개고 조각마다 번호를 붙입니다. 받는 쪽은 번호를 보고 뒤바뀌어 온 것을 제 순서에 놓습니다. 빠진 번호가 있으면 다시 보내 달라고 합니다.
sequenceDiagram
participant S as 보내는 쪽
participant R as 받는 쪽
S->>R: 세그먼트 1
S-xR: 세그먼트 2 · 도중에 잃었다
S->>R: 세그먼트 3
R->>S: 2 번을 다시 보내 달라
S->>R: 세그먼트 2
Note over R: 1 · 2 · 3 차례로 다시 놓는다
번호는 이미 본 것을 가려내는 데도 씁니다. 지나간 번호가 또 오면 받는 쪽은 그것을 버립니다. 남의 메시지를 가로채 그대로 다시 보내는 재전송 공격을 막는 방법 하나가 이것입니다.
동시성에서도 같은 번호를 씁니다. 값을 고칠 때마다 번호를 하나 올려 두면, 읽는 쪽은 읽기 전후로 번호를 보고 읽는 동안 값이 바뀌었는지 알 수 있습니다.
flowchart TD
A["읽기 전에 번호를 본다"] --> B["값을 읽는다"]
B --> C["읽은 뒤 번호를 다시 본다"]
C --> D{"두 번호가 같나"}
D -->|같다| E["읽은 값을 믿고 쓴다"]
D -->|다르다| A
번호가 달라졌다는 것은 읽는 동안 누군가 값을 고쳤다는 뜻입니다. 그대로 다시 읽으면 고쳐진 값에서 새로 시작하므로 몇 번을 되돌아도 틀린 값을 붙잡지 않습니다.
이 방식이 주는 것은 읽는 쪽이 아무것도 잠그지 않아도 된다는 점입니다. 대가는 고침이 잦으면 읽기가 계속 다시 돈다는 것입니다. 그래서 읽기가 대부분이고 고침이 드문 데에 씁니다.
낙관적 잠금도 같은 생각을 씁니다. 값을 고치기 전에 번호가 그대로인지 확인합니다. 달라졌으면 고치기를 포기합니다.
어느 뜻인지 가르는 단서
글에서 「시퀀스」를 만나면 곁에 놓인 말을 봅니다. 대개 그것만으로 어느 뜻인지 갈립니다.
| 곁에 놓인 말 | 뜻 |
|---|---|
| 몇 번째 · 원소 · 반복문 · 슬라이싱 | 값이 늘어선 모음 |
CREATE SEQUENCE · nextval · 기본 키 · 자동 증가 |
번호를 내주는 장치 |
| 세그먼트 · 확인 응답 · 재전송 · 번호가 밀렸다 | 순서를 표시하는 번호 |
| 항 · 일반항 · 수렴 | 수학의 수열 |
「시퀀스 다이어그램」의 시퀀스는 위의 어느 뜻도 아닙니다. 주고받는 일이 일어나는 차례를 그린 그림을 가리킵니다. 데이터베이스가 테이블을 앞에서부터 끝까지 훑는 일을 부르는 「시퀀셜 스캔」도 마찬가지입니다. 여기서의 시퀀셜은 값의 모음이 아니라 「앞에서부터 차례로」라는 뜻입니다.
관련 항목
시퀀스의 하위 종류로 드는 자료구조
배열 · 연결 리스트 · 문자열 · 리스트 · 튜플 · 덱 · 동적 배열
시퀀스와 맞세워지는 다른 모음 종류
시퀀스가 속하는 상위 분류
시퀀스에서 값을 하나씩 꺼내는 수단
인덱스 · 이터레이터 · 슬라이싱 · 반복문 · 제너레이터 · 스트림
데이터베이스에서 겹치지 않는 번호를 만드는 방법
자동 증가 · 식별자 · 기본 키 · 대리 키 · UUID · 스노우플레이크 · 관계형 데이터베이스
순서를 표시하는 번호와 시계
시퀀스 번호 · 타임스탬프 · 논리 시계 · 벡터 시계 · 단조 시계 · 에포크 · 리비전
시퀀스 번호로 순서와 겹침을 가리는 프로토콜
TCP · 세그먼트 · 확인 응답 · 재전송 · 재전송 공격 · 흐름 제어 · 논스
번호를 견주어 다시 읽게 하는 동시성 수단
다른 이름: sequence