PostgreSQL에서 배열은 테이블의 한 칸에 여러 값을 저장할 수 있게 해줘. 예를 들어, 게시글의 태그 리스트나 상품의 카테고리 목록처럼 관련된 데이터를 묶어서 저장할 때 엄청 편하지. 근데 배열을 검색하거나 필터링하거나 교집합을 찾으려고 하면, 속도가 확 느려질 수 있어. 그래서 배열 인덱싱이 진짜 중요해지는 거야. 인덱스를 쓰면 이런 작업들이 훨씬 빨라져. 예를 들면:
- 배열에 특정 값이 있는지 확인하기,
- 특정 요소를 포함하는 배열 찾기,
- 배열끼리 교집합이 있는지 확인하기.
배열 다루는 연산자들
인덱스 만드는 얘기 들어가기 전에, 배열 다룰 때 자주 쓰는 연산자부터 정리해보자:
@> (contains) — 배열이 다른 배열의 모든 요소를 포함하는지 확인해.
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
여기서는 "SQL" 태그가 들어있는 강좌를 찾는 거야.
<@ (is contained by) — 한 배열이 다른 배열에 포함되어 있는지 확인해.
SELECT *
FROM courses
WHERE ARRAY['PostgreSQL', 'SQL'] <@ tags;
여기서는 ARRAY['PostgreSQL', 'SQL']의 모든 요소가 tags에 들어있는 강좌를 찾는 거야.
&& (overlap) — 두 배열에 겹치는 값이 있는지 확인해.
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
이 쿼리는 "NoSQL"이나 "Big Data" 태그가 하나라도 들어있는 강좌를 찾아줘.
인덱싱이 어떻게 도움이 될까?
예를 들어 courses 테이블에 수백만 개의 데이터가 있다고 해보자. 위 연산자 중 하나로 쿼리를 날리면, 인덱스가 없으면 PostgreSQL이 모든 행을 하나씩 다 뒤져야 해 — 이건 진짜 오래 걸려 (특히 컴파일 기다리는 개발자처럼 인내심이 부족하다면 더더욱).
인덱스를 쓰면 이런 고생을 안 해도 돼. PostgreSQL은 배열에 딱 맞는 인덱스 두 가지를 제공해:
GIN(Generalized Inverted Index) — 배열엔 이게 최고야.BTREE— 배열 전체를 비교할 때 써.
예제: 배열 인덱스 만들어보기
실습을 위해 배열이 들어간 테이블을 하나 만들어보자.
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
tags TEXT[] NOT NULL
);
몇 개 데이터도 넣어보자:
INSERT INTO courses (name, tags)
VALUES
('SQL 기초', ARRAY['SQL', 'PostgreSQL', '데이터베이스']),
('Big Data 다루기', ARRAY['Hadoop', 'Big Data', 'NoSQL']),
('Python 개발', ARRAY['Python', 'Web', '데이터']),
('PostgreSQL 강좌', ARRAY['PostgreSQL', 'Advanced', 'SQL']);
이렇게 테이블이 생겼을 거야:
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL, PostgreSQL, 데이터베이스} |
| 2 | Big Data 다루기 | {Hadoop, Big Data, NoSQL} |
| 3 | Python 개발 | {Python, Web, 데이터} |
| 4 | PostgreSQL 강좌 | {PostgreSQL, Advanced, SQL} |
인덱스 없이: 느린 검색
이제 SQL 태그가 들어있는 모든 강좌를 찾고 싶다고 해보자.
EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
이 쿼리는 돌아가긴 하는데, 데이터가 많으면 진짜 느려져. PostgreSQL이 일명 순차 검색(Sequential Scan)으로 모든 행을 다 뒤져야 하거든.
예상 결과:
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL, PostgreSQL, 데이터베이스} |
| 4 | PostgreSQL 강좌 | {PostgreSQL, Advanced, SQL} |
GIN 인덱스 만들기
검색을 빠르게 하려면 GIN 인덱스를 만들어보자:
CREATE INDEX idx_courses_tags
ON courses USING GIN (tags);
같은 쿼리를 다시 실행해보면:
EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
이제 PostgreSQL이 만든 GIN 인덱스를 써서 쿼리 속도가 확 빨라질 거야.
예전엔 순차 검색(Seq Scan)이었는데, 이제 실행 계획에서 Bitmap Index Scan이 보일 거야:
| Step | Rows | Cost | Info |
|---|---|---|---|
| Bitmap Index Scan | N | 낮음 | idx_courses_tags 인덱스 사용 |
| Bitmap Heap Scan | N | 낮음 | 테이블에서 행 선택 |
Rows랑 Cost 값은 데이터 양에 따라 다르지만, 중요한 건 이제 실행 계획에 인덱스가 들어간다는 거야.
연산자랑 인덱스가 어떻게 같이 동작할까?
예제 1: @> 연산자
쿼리:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
GIN 인덱스는 이 연산자에 딱 맞아. Postgres가 해당 요소가 들어있는 행을 빠르게 찾아서 결과를 내줘.
쿼리 결과:
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL, PostgreSQL, 데이터베이스} |
| 4 | PostgreSQL 강좌 | {PostgreSQL, Advanced, SQL} |
@>는 "포함한다"는 뜻이야 — 이 쿼리는 tags 배열에 SQL 값이 있는 모든 강좌를 돌려줘.
예제 2: && 연산자
쿼리:
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
이 연산자는 배열끼리 겹치는 값이 있는지 확인해: tags 배열이 전달된 배열의 요소 중 하나라도 겹치면 해당 행을 돌려줘.
GIN 인덱스가 또 한 번 활약 — 데이터가 많아도 검색이 금방 끝나.
쿼리 결과:
| id | name | tags |
|---|---|---|
| 2 | Big Data 다루기 | {Hadoop, Big Data, NoSQL} |
이건 "교집합이 있다"는 뜻 — 하나라도 태그가 겹치면 조건이 만족돼.
인덱싱 & 최적화 팁
배열 쓸 때는 아래 팁을 참고해봐:
GIN인덱스를 배열 검색에 써라. 순차 검색보다 훨씬 빠르다.- 진짜 자주 쓰는 컬럼에만 인덱스를 추가해라. 인덱스는 공간도 차지하고, 데이터 삽입도 느려지니까 꼭 필요한 곳에만!
EXPLAIN이나EXPLAIN ANALYZE로 쿼리 프로파일링 해봐. 인덱스가 진짜 쓰이고 있는지 확인할 수 있어.
예제: 배열 인덱스 만들기
배열 연산자별로 인덱스를 어떻게 만들고, 왜 필요한지 실전에서 확인해보자.
@> 연산자용 인덱스
이미 courses 테이블이 있다고 치자:
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL, PostgreSQL, 데이터베이스} |
| 2 | Big Data 다루기 | {Hadoop, Big Data, NoSQL} |
| 3 | Python 개발 | {Python, Web, 데이터} |
| 4 | PostgreSQL 강좌 | {PostgreSQL, Advanced, SQL} |
@> 연산자(배열이 요소를 포함하는지 확인) 쿼리를 빠르게 하려면 GIN 인덱스를 만들어:
CREATE INDEX idx_courses_tags_gin
ON courses USING GIN (tags);
이제 쿼리 실행:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
결과:
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL, PostgreSQL, 데이터베이스} |
| 4 | PostgreSQL 강좌 | {PostgreSQL, Advanced, SQL} |
@>, <@, && 연산자용 인덱스
테이블은 위와 똑같아.
@>, <@, && 연산자는 전부 GIN 인덱스랑 잘 맞으니까, 하나만 만들어도 세 연산자 모두 빠르게 쓸 수 있어:
CREATE INDEX idx_tags
ON courses USING GIN (tags);
쿼리 예시랑 결과:
@>— 배열이 지정한 요소를 포함하는지 확인:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL, PostgreSQL, 데이터베이스} |
| 4 | PostgreSQL 강좌 | {PostgreSQL, Advanced, SQL} |
<@— 배열이 다른 배열에 포함되는지 확인:
SELECT *
FROM courses
WHERE tags <@ ARRAY['SQL', 'PostgreSQL', 'Advanced', 'Big Data', 'NoSQL', 'Python'];
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL, PostgreSQL, 데이터베이스} |
| 2 | Big Data 다루기 | {Hadoop, Big Data, NoSQL} |
| 3 | Python 개발 | {Python, Web, 데이터} |
| 4 | PostgreSQL 강좌 | {PostgreSQL, Advanced, SQL} |
&&— 배열끼리 교집합이 있는지 확인:
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
| id | name | tags |
|---|---|---|
| 2 | Big Data 다루기 | {Hadoop, Big Data, NoSQL} |
좀 더 복잡한 쿼리 해보자
태그 중에 ['Python', 'SQL', 'NoSQL'] 중 하나라도 들어있는 강좌를 찾는 쿼리:
SELECT *
FROM courses
WHERE tags && ARRAY['Python', 'SQL', 'NoSQL'];
결과:
| id | name | tags |
|---|---|---|
| 1 | SQL 기초 | {SQL,PostgreSQL,데이터베이스} |
| 2 | Big Data 다루기 | {Hadoop,Big Data,NoSQL} |
| 3 | Python 개발 | {Python,Web,데이터} |
GIN 인덱스 덕분에 이런 쿼리도 데이터가 수백만 개여도 바로 결과가 나와.
배열 쓸 때 흔히 하는 실수
인덱스가 안 쓰임: EXPLAIN 결과에 Seq Scan이 보이면, 인덱스가 제대로 만들어졌는지, 그리고 네가 쓰는 연산자가 인덱스를 지원하는지 꼭 확인해봐.
배열 컬럼을 거의 안 씀: 배열 컬럼이 쿼리에서 거의 안 쓰이거나 자주 업데이트된다면, 인덱스가 공간만 차지하고 별로 도움 안 될 수도 있어.
인덱스 남발: 인덱스는 디스크 공간도 먹고, 쓰기 작업도 느려지니까, 진짜 필요한 것만 만들어.
이제 PostgreSQL에서 배열을 진짜 빠르게 다루는 방법 다 배웠어 — @>, <@, && 연산자랑 GIN 인덱스 조합으로 쿼리 속도 올려보자. 직접 네 데이터로 실험해보는 것도 잊지 말고!
GO TO FULL VERSION