본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 04. 29. 09:53

Supabase PostgreSQL 함수 — RPC 를 통해 복잡한 로직을 서버 측으로 이동하기

요약

PostgreSQL 함수와 RPC(Remote Procedure Call)를 활용하여 복잡한 데이터 로직을 클라이언트(Flutter 등)에서 서버 측 PostgreSQL 레이어로 옮기는 방법을 설명합니다. 이를 통해 여러 테이블에 걸친 집계, 전체 텍스트 검색, 권한 검사 등의 복잡한 작업을 단일 호출로 처리할 수 있어 네트워크 라운드트립을 줄이고 애플리케이션의 효율성을 높입니다. 특히 `SECURITY DEFINER`를 사용하면 RLS(Row Level Security)와 분리된 관리자 전용 로직 구현이 가능하며, Flutter 클라이언트에서는 `supabase.rpc()` 메서드를 통해 쉽게 호출할 수 있습니다.

핵심 포인트

  • 복잡한 비즈니스 로직을 서버 측 PostgreSQL 함수로 이동시켜 네트워크 효율성을 극대화합니다.
  • RPC를 사용하면 여러 테이블의 데이터를 단일 트랜잭션으로 집계하거나 검색하는 것이 가능합니다.
  • `SECURITY DEFINER`는 RLS와 독립적인 관리자 전용 기능을 구현할 때 유용하며, 호출 권한 관리가 필수적입니다.
  • 전체 텍스트 검색(Full-Text Search)과 같은 복잡한 쿼리도 함수 내에서 처리하여 클라이언트의 부담을 줄일 수 있습니다.
  • Flutter 등 클라이언트는 `supabase.rpc('func_name', params: {...})` 구문을 사용하여 서버 함수를 호출합니다.

Flutter 클라이언트에서 복잡한 쿼리를 작성하면 네트워크 라운드트립이 증가합니다. PostgreSQL 함수 + RPC 가 이 문제를 해결합니다.

기본 사항: 커스텀 RPC 함수 생성
-- supabase/migrations/20280812000000_create_get_user_stats.sql
CREATE OR REPLACE FUNCTION get_user_stats ( p_user_id UUID ) RETURNS JSON AS $$
DECLARE result JSON ;
BEGIN
SELECT json_build_object (
'post_count' , ( SELECT COUNT ( * ) FROM posts WHERE user_id = p_user_id ),
'comment_count' , ( SELECT COUNT ( * ) FROM comments WHERE user_id = p_user_id ),
'follower_count' , ( SELECT COUNT ( * ) FROM follows WHERE followee_id = p_user_id ),
'joined_at' , ( SELECT created_at FROM profiles WHERE id = p_user_id )
) INTO result ;
RETURN result ;
END ;
$$ LANGUAGE plpgsql SECURITY DEFINER ;

Flutter 에서 호출하는 방법
final stats = await supabase . rpc ( 'get_user_stats' , params: { 'p_user_id' : supabase . auth . currentUser !. id , });
print ( stats [ 'post_count' ]); // 42
print ( stats [ 'follower_count' ]); // 128

SETS 를 반환하는 함수
CREATE OR REPLACE FUNCTION search_posts ( p_query TEXT , p_limit INT DEFAULT 10 ) RETURNS SETOF posts AS $$
BEGIN
RETURN QUERY SELECT * FROM posts WHERE to_tsvector ( 'english' , title || ' ' || content ) @@ plainto_tsquery ( 'english' , p_query ) ORDER BY ts_rank ( to_tsvector ( 'english' , title || ' ' || content ), plainto_tsquery ( 'english' , p_query )) DESC LIMIT p_limit ;
END ;
$$ LANGUAGE plpgsql STABLE ;

final List results = await supabase . rpc ( 'search_posts' , params: { 'p_query' : 'Flutter Riverpod' , 'p_limit' : 20 , });

RLS 와 결합하기
-- SECURITY DEFINER 는 RLS 를 우회하여 관리자 통계 조회
CREATE OR REPLACE FUNCTION admin_get_daily_stats ( p_date DATE ) RETURNS JSON AS $$
BEGIN
-- 이 함수는 service_role 만 호출할 수 있음
IF auth . role () != 'service_role' THEN RAISE EXCEPTION 'permission denied' ; END IF ;
RETURN json_build_object (
'new_users' , ( SELECT COUNT ( * ) FROM profiles WHERE created_at :: date = p_date ),
'new_posts' , ( SELECT COUNT ( * ) FROM posts WHERE created_at :: date = p_date ),
'active_users' , ( SELECT COUNT ( DISTINCT user_id ) FROM posts WHERE created_at :: date = p_date )
);
END ;
$$ LANGUAGE plpgsql SECURITY DEFINER ;

요약
RPC 를 사용하는 경우 → 여러 테이블 집계 / 전체 텍스트 검색 / 권한 분기 처리 시
SECURITY DEFINER → 관리자 전용 (항상 호출자의 역할을 확인해야 함)
STABLE → 부작용이 없는 함수 (쿼리 플래너가 최적화 가능)
Flutter 호출 → supabase.rpc('func_name', params: {...})
로직을 서버 측으로 이동하면 클라이언트가 단순해지고 네트워크 라운드트립이 감소합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
4

댓글

0