
Supabase에서 RLS(Row Level Security) 정책을 설정할 때,
auth.uid() 또는 auth.email() 같은 내장 인증 함수는 각 row마다 반복 평가되기 때문에
대량의 데이터 처리 시 심각한 성능 저하를 일으킬 수 있다.
Supabase의 Performance Advisor는 이를 다음과 같이 경고한다.
❗
auth.uid()is evaluated per-row and may cause performance issues.
Consider using(SELECT auth.uid())instead.
로그를 등록할 때 다음과 같은 이슈가 생겼다.
log (로그)
└─ place[] (장소 여러 개)
└─ place_images[] (장소별 이미지 여러 개)
로그 1개를 등록할 때 장소가 5개, 장소마다 이미지가 3개씩만 있어도 →
총 15~20개 이상의 row insert가 일어나며, 그때마다 RLS 정책이 실행된다.
place나 log_tag 테이블에 적용된 기존 RLS 정책은 다음과 같았다.
EXISTS (
SELECT 1
FROM log
WHERE log.log_id = place.log_id
AND log.user_id = auth.uid() -- 이 부분이 문제
)
위 정책은 auth.uid()를 삽입되는 row마다 평가하므로,
장소가 많거나 첫 로그 작성 시 데이터가 집중되면
Supabase가 인증을 반복 수행하게 되어 전체 insert 속도가 느려지는 병목이 발생한다.
log.user_id = auth.uid()
log.user_id = (SELECT auth.uid())
SELECT로 감싸면 auth.uid()는 쿼리 전체 실행 중 단 한 번만 평가된다.
public.place - UPDATE PolicyEXISTS (
SELECT 1
FROM log
WHERE log.log_id = place.log_id
AND log.user_id = auth.uid()
)
EXISTS (
SELECT 1
FROM log
WHERE log.log_id = place.log_id
AND log.user_id = (SELECT auth.uid())
)
public.place - DELETE Policylog.user_id = auth.uid()
log.user_id = (SELECT auth.uid())
public.log_tag - DELETE PolicyEXISTS (
SELECT 1
FROM log
WHERE log.log_id = log_tag.log_id
AND log.user_id = auth.uid()
)
EXISTS (
SELECT 1
FROM log
WHERE log.log_id = log_tag.log_id
AND log.user_id = (SELECT auth.uid())
)
public.log_tag - UPDATE PolicyEXISTS (
SELECT 1
FROM log
WHERE log.log_id = log_tag.log_id
AND log.user_id = auth.uid()
)
EXISTS (
SELECT 1
FROM log
WHERE log.log_id = log_tag.log_id
AND log.user_id = (SELECT auth.uid())
)
이번 경험을 통해 RLS 정책이 단순한 보안 도구가 아니라, 실제 퍼포먼스 병목의 원인이 될 수 있음을 체감했다.
구조가 복잡한 관계형 데이터에서 insert가 많을수록 이러한 최적화는 꼭 필요하다.