Supabase cron Edge Function: AI 영상 예약 실행

pg_cron과 pg_net으로 Supabase Edge Function을 예약 실행하고, 영상 API 키는 함수 시크릿에 두고, 날짜로 만든 키로 하루에 실행 하나를 시작하세요.

읽는 시간 6분Sume
전체 글

Supabase Edge Function을 cron 스케줄로 실행하려면 Supabase Cron(pg_cron 확장)과 pg_net을 활성화한 뒤, 함수 URL로 net.http_post를 호출하는 SQL을 cron 작업으로 예약하세요. 이때 URL과 키는 Supabase Vault에서 읽습니다. AI 영상이라면 영상 API 키는 SQL이 아니라 함수가 보관합니다. 함수는 날짜로 만든 Idempotency-Key와 웹훅 URL로 실행을 시작하고 바로 반환합니다.

Supabase 관련 내용은 Supabase의 Edge Functions 스케줄링, Cron, pg_net 페이지와 출처에 나열한 Edge Functions 페이지에서, Sume 관련 내용은 Format 호출하기 (영문), 실행과 결과 (영문), 인증에서 가져왔습니다. 모두 2026-09-27에 확인했습니다. Sume에는 Supabase 전용 커넥터가 없으며, 함수가 HTTPS로 직접 호출합니다. 받는 쪽은 Sume용 Supabase Edge Function 웹훅에서 다룹니다.

SQL 쿼리로 Supabase에서 cron을 어떻게 활성화하나요?

Supabase Cron은 pg_cron 확장 위에서 동작하며, pg_net을 쓰면 cron 작업이 HTTP 요청을 보낼 수 있습니다. 아래 SQL로 둘 다 활성화하거나, Dashboard의 Integrations 아래에 있는 Cron 모듈에서 pg_cron을 활성화하세요. Supabase는 두 가지를 주의하라고 합니다. pg_net API는 베타라서 함수 시그니처가 바뀔 수 있고, pg_cron을 비활성화하면 모든 cron 작업이 영구 삭제됩니다.

create extension pg_cron with schema pg_catalog;
grant usage on schema cron to postgres;
grant all privileges on all tables in schema cron to postgres;
create extension pg_net with schema "extensions";

pg_cron으로 Edge Function을 어떻게 예약하나요?

Supabase의 레시피는 프로젝트 URL과 키를 Vault에 저장한 다음, 함수를 대상으로 net.http_post를 예약합니다. 아래 버전은 여기에 cron_secret을 더합니다. Supabase는 publishable 키를 브라우저에서 써도 안전한 키라고 설명하므로, 이 키를 가졌다고 해서 호출자가 누구인지 증명되지는 않습니다. 작업 이름은 대소문자를 구분하며, 같은 이름으로 두 번째 작업을 만들면 첫 번째 작업을 덮어씁니다. Supabase 예제는 스케줄에 GMT 기준으로 주석을 달므로 0 6 * * *는 06:00 GMT입니다. net.http_post는 기본적으로 2000 ms를 기다리며, Supabase가 직접 제시한 분 단위보다 짧은 간격의 예제는 timeout_milliseconds를 5000으로 설정합니다.

select vault.create_secret('https://project-ref.supabase.co', 'project_url');
select vault.create_secret('YOUR_SUPABASE_PUBLISHABLE_KEY', 'publishable_key');
select vault.create_secret('a-long-random-string', 'cron_secret');

select cron.schedule(
  'daily-recap-video',
  '0 6 * * *', -- 06:00 GMT every day
  $$
  select net.http_post(
    url:= (select decrypted_secret from vault.decrypted_secrets where name = 'project_url')
      || '/functions/v1/start-daily-video',
    headers:= jsonb_build_object(
      'Content-Type', 'application/json',
      'apikey', (select decrypted_secret from vault.decrypted_secrets where name = 'publishable_key'),
      'x-cron-secret', (select decrypted_secret from vault.decrypted_secrets where name = 'cron_secret')
    ),
    timeout_milliseconds:= 5000
  ) as request_id;
  $$
);

Edge Function은 영상 API에 무엇을 보내야 하나요?

SUME_API_KEY와 CRON_SECRET은 supabase secrets set으로 설정하세요. 시크릿은 다시 배포하지 않아도 바로 쓸 수 있으며, 이름은 SUPABASE_로 시작할 수 없습니다. 함수는 스타터 템플릿의 키 검사를 그대로 두고 자체 시크릿 검사를 더한 뒤, 저장된 Sume 레시피인 Format의 실행 하나를 시작합니다. 키와 본문은 UTC 날짜만으로 만들므로, 그날 다시 호출되어도 똑같은 요청을 보냅니다. data.id는 날짜별로 저장하세요. 202는 새 실행이고, 200은 그날 실행의 재전송입니다.

import { withSupabase } from "npm:@supabase/server@^1";

export default {
  fetch: withSupabase({ auth: ["publishable", "secret"] }, async (req) => {
    const cronSecret = Deno.env.get("CRON_SECRET");
    if (!cronSecret || req.headers.get("x-cron-secret") !== cronSecret) {
      return new Response("forbidden", { status: 403 });
    }
    const day = new Date().toISOString().slice(0, 10); // UTC date
    const res = await fetch("https://api.sume.com/v1/formats/acme/daily-recap/runs", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${Deno.env.get("SUME_API_KEY")}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `daily-recap-${day}`,
      },
      body: JSON.stringify({
        input: { day },
        communication: { webhook_url: "https://project-ref.supabase.co/functions/v1/sume-webhook" },
      }),
    });
    const { data, error } = await res.json();
    if (!res.ok) return Response.json(error, { status: res.status });
    return Response.json({ run_id: data.id, replay: data.idempotency_hit });
  }),
};

SQL에서 영상 API를 바로 호출하면 왜 안 되나요?

pg_net도 JSON을 POST할 수 있지만 이 용도에는 잘 맞지 않습니다. 모든 cron 작업은 cron.job 테이블에 저장되므로 명령에 적은 키가 테이블에 남는데, Sume 문서는 API 키를 신뢰할 수 있는 서버에 두라고 안내합니다. 또 net.http_post는 비동기라서 요청 ID만 반환하고, 응답은 net._http_response에 들어가 기본적으로 6시간 동안 보관됩니다. 저장해야 할 실행 ID를 SQL은 끝내 보지 못하지만, 함수는 봅니다.

cron 작업이 두 번 실행되거나 호출이 타임아웃되면 어떻게 되나요?

날짜 키 덕분에 그날 일어나는 반복은 모두 안전합니다. 여기서 중요한 Sume의 멱등성 규칙은 두 가지이며, 나머지는 AI 영상 API 멱등성 키에서 다룹니다.

  • 같은 키, 같은 본문: 원래 영수증과 idempotency_hit: true가 담긴 200이 돌아옵니다. 두 번째 실행도, 두 번째 청구도 없습니다.
  • 같은 키, 다른 본문: 409 idempotency_conflict가 돌아오고 아무것도 실행되지 않습니다. webhook_url은 본문의 일부이므로 고정해 두세요.
  • 무슨 일이 있었는지 보려면 cron 작업의 모든 실행과 그 상태를 기록하는 cron.job_run_details, 그리고 함수의 status_code와 timed_out 플래그가 담긴 net._http_response를 읽으세요.
Supabase의 Cron, pg_net, Edge Function 한도 페이지와 Sume의 실행과 결과 (영문) 기준, 2026-09-27 확인.
구성 요소한도 또는 기본값
Supabase Cron동시에 실행되는 작업은 8개 이하, 작업마다 10분 이하로 둘 것을 Supabase가 권장
net.http_post기본 타임아웃 2000 ms. 응답은 6시간 보관
Edge Function wall clockFree 플랜 150 s, 유료 플랜 400 s
Sume 롱폼 영상15분에서 30분 걸리는 작업
Sume 실행 마감created_at으로부터 최대 90분. 그 뒤 failed로 강제 종료

함수가 영상을 기다려야 하나요?

아닙니다. 롱폼 영상은 위 표에 나온 Edge Function wall clock 한도 어느 쪽보다도 오래 걸리며, 함수가 지켜보기를 멈춰도 실행과 그 지출은 멈추지 않습니다. 실행이 완료되거나 실패하면 communication.webhook_url로 서명된 POST가 한 번 전송되므로, 두 번째 Edge Function이 결과를 받습니다. Sume용 Supabase Edge Function 웹훅에서 verify_jwt = false와 HMAC 검사를 쓰는 그 함수를 보여 줍니다. Cloudflare에서 쓰는 같은 트리거 패턴은 Cloudflare Workers cron job에서 다룹니다.

출처

관련 글

연동 카테고리의 다른 글

연동 글 전체 보기

작성자 Sume