Supabase๋ก ์ค์ผ์ค๋ง์ ์์ฑํ ์ ์๋ค๊ณ ํด์ ์๋น์ค๋ฅผ ์ด์ฉํด๋ณด๊ธฐ๋ก ํ๋ค.
๋์ !!
1. Database > Extensions ํด๋ฆญ

๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์๋๊ณ ํ์ฅํ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์
pg_cron์ ํ์ฑํํด์ผ ํ๋ค.
์ง๊ธ์ ํ์ฑํํ๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์์ ์ฌ๋ผ์์ ๋ณด์ด์ง๋ง.. ์๋ณด์ธ๋ค๋ฉด ๊ฒ์์ฐฝ์ pg_cron ๊ฒ์ ํ ํ์ฑํํ ๊ฒ
2. ์ค์ผ์ค๋ง ์คํํ ํจ์ ๋ง๋ค๊ธฐ
๋ด ๋ก์ง์๋ ๊ธฐ์กด์ ํ ์ด๋ธ ๋ฐ์ดํฐ ์ ๋ถ ์ญ์ → ๋๋ค ์ฃผ์ ํ ์ด๋ธ์์ ์๋ก์ด row์ ํ → ์๋ก์ด ์ฃผ์ insert์๊ธฐ ๋๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
1. ๋๋ค ์ฃผ์ INSERT
CREATE OR REPLACE FUNCTION dalmuri.get_random_topic_fix()
RETURNS VOID
LANGUAGE plpgsql
SET search_path = dalmuri, public
AS $$
DECLARE
selected_topic_content text;
BEGIN
-- 1. ์กฐํ ๋ก์ง ...
SELECT
"RANTO_CONTENT" INTO selected_topic_content
FROM
"RANDOM_TOPIC_VIEW"
ORDER BY
RANDOM()
LIMIT 1;
-- 2. ์ฝ์
๋ก์ง ...
IF selected_topic_content IS NOT NULL THEN
INSERT INTO
dalmuri."TODAY_WORD_TOPIC" ("TOPIC_ID", "TODAY_TOPIC", "INSERT_DATETIME")
VALUES (
gen_random_uuid(),
selected_topic_content,
NOW()
);
ELSE
RAISE NOTICE '[WARN]: RANDOM_TOPIC_VIEW์์ ํ ํฝ์ ์ฐพ์ ์ ์์ต๋๋ค.';
END IF;
RETURN;
END;
$$;
ํจ์ ์ ์ ๋ฐ ์ค์
- CREATE OR REPLACE FUNCTION : ํจ์๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ฉด ๊ต์ฒดํ๊ณ , ์์ผ๋ฉด ์๋ก ์์ฑ. ํจ์ ์ด๋ฆ์ get_random_topic_fix์ด๋ฉฐ ์ธ์(ํ๋ผ๋ฏธํฐ)๋ ์์
- RETURNS VOID : ํน์ ๋ฐ์ดํฐ๋ฅผ ํธ์ถ์์๊ฒ ๋ฐํํ์ง ์๊ณ ๋ด๋ถ ์์ ์ ์ํํ๋๋ฐ ์ฌ์ฉ๋จ
- LANGUAGE plpgsql : ํจ์ ๋ณธ๋ฌธ์ด PostgreSQL๋ก ์์ฑ๋ฌ์์ ๋ช ์
- SET search_path : ํจ์ ์คํ ์ ๊ฒ์ ๊ฒฝ๋ก(search_path)๋ฅผ ์ค์ . ์ด ํจ์ ๋ด๋ถ์์ ํ ์ด๋ธ ํน์ ๋ทฐ๋ฅผ ์ฐธ์กฐํ ๋ ์คํค๋ง ์ด๋ฆ์ ๋ช ์ํ์ง ์์ผ๋ฉด dalmuri ์คํค๋ง๋ฅผ ๋จผ์ ์ฐพ๊ณ , ์์ผ๋ฉด public ์คํค๋ง๋ฅผ ์ฐพ์
- DECLARE : ํจ ์ ๋ด์์ ์ฌ์ฉํ ๋ณ์๋ฅผ ์ ์ธ. ์ฌ๊ธฐ์ selected_topic_content
ํจ์ ๋ณธ๋ฌธ
- BEGIN ~ END ์์ ์์ฑ
1๏ธโฃ ์กฐํ ๋ก์ง (๋ฌด์์ ํ ํฝ ์ ํ)
SELECT
"RANTO_CONTENT" INTO selected_topic_content
FROM
"RANDOM_TOPIC_VIEW"
ORDER BY
RANDOM()
LIMIT 1;
- "RANDOM_TOPIC_VIEW"๋ทฐ์์ "RANTO_CONTENT ์ปฌ๋ผ์ ๊ฐ์ ์กฐํ
- ORDER BY RANDOM() : ์กฐํ๋ ๊ฒฐ๊ณผ ์งํฉ์ ๋ฌด์์ ์์๋ก ์ ๋ ฌ
- LIMIT 1: ๋ฌด์์๋ก ์ ๋ ฌ๋ ๊ฒฐ๊ณผ ์ค ์ฒซ๋ฒ์งธ ํญ๋ชฉ ๋จ ํ๋๋ง ์ ํ
2๏ธโฃ ์ฝ์ ๋ก์ง (์ ํ๋ ํ ํฝ ์ ์ฅ)
IF selected_topic_content IS NOT NULL THEN
INSERT INTO
dalmuri."TODAY_WORD_TOPIC" ("TOPIC_ID", "TODAY_TOPIC", "INSERT_DATETIME")
VALUES (
gen_random_uuid(),
selected_topic_content,
NOW()
);
ELSE
RAISE NOTICE '[WARN]: RANDOM_TOPIC_VIEW์์ ํ ํฝ์ ์ฐพ์ ์ ์์ต๋๋ค.';
END IF;
- IF selected_topic_content INS NOT NULL THEN : 1๋จ๊ณ ์กฐํ ๋ก์ง์์ selected_topic_content๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๊ฐ์ ธ์๋์ง ํ์ธ
- NULL์ด ์๋ ๊ฒฝ์ฐ = ํ ํฝ์ ์ฐพ์์ ๊ฒฝ์ฐ
- dalmuri."TODAY_WORD_TOPIC" ํ ์ด๋ธ์ ์๋ก์ด ํ์ ์ฝ์
- "TOPIC_ID" ์ปฌ๋ผ : gen_random_uuid() ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ณ ์ ํ ๋ฒ์ฉ ๊ณ ์ ์๋ณ์(UUID)๋ฅผ ์์ฑํ์ฌ ์ ์ฅ
- "TODAY_TOPIC" ์ปฌ๋ผ : ์กฐํํ ๋ฌด์์ ํ ํฝ ๋ด์ฉ(selected_topic_content)์ ์ ์ฅ
- "INSERT_DATETIME" : NOW()ํจ์(ํ์ฌ ์๊ฐ)๋ฅผ ์ ์ฅ
- NULL์ผ ๊ฒฝ์ฐ = ํ ํฝ์ ์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ
- RAISE NOTICE : ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฉ์์ง ์ฐฝ์ ๊ฒฝ๊ณ ๋ฉ์์ง ์ถ๋ ฅ
- RETURN : ํจ์ ์ข ๋ฃ
2. ์ค๋์ ๋๋ค ์ฃผ์ ๋ฐ ๋๊ธ DELETE
CREATE OR REPLACE FUNCTION dalmuri.reset_today_topic()
RETURNS void
LANGUAGE plpgsql
SET search_path = dalmuri, public
AS $$
BEGIN
-- TODAY_WORD_CMNT ๊ด๋ จ ๋ชจ๋ ํ ์ญ์
DELETE FROM dalmuri."TODAY_WORD_CMNT";
-- TODAY_WORD_TOPIC ๊ด๋ จ ๋ชจ๋ ํ ์ญ์
DELETE FROM dalmuri."TODAY_WORD_TOPIC";
-- get_random_topic_fix ์คํ์ผ๋ก ๋๋คํ ํ ์ถ๊ฐ
PERFORM dalmuri.get_random_topic_fix();
RETURN;
END;
$$;
ํจ์ ์ ์ ๋ฐ ์ค์
- CREATE OR REPLACE FUNCTION : ํจ์๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ค๋ฉด ๊ต์ฑ, ์์ผ๋ฉด ์๋ก ์์ฑ, ์ธ์๋ ์์
- RETURNS VOID : ํน์ ๊ฐ์ ๋ฐํํ์ง ์๋ ํจ์. ๋ด๋ถ ์์ ์ ์ํ
- LANGUAGE plpgsql : ํจ์ ๋ณธ๋ฌธ์ด PostgreSQL๋ก ์์ฑ๋จ
- SET search path = dalmuri, public : ํจ์ ์คํ ์ ๊ฒ์ ๊ฒฝ๋ก๋ฅผ dalmuri์ public์ผ๋ก ์ค์
ํจ์ ๋ณธ๋ฌธ
1๏ธโฃ TODAY_WORD_CMNT ํ ์ด๋ธ ์ด๊ธฐํ (๋๊ธ ์ญ์ )
DELETE FROME dalmuri."TODAY_WORD_CMNT";
- dalmuri ์คํค๋ง์ "TODAY_WORD_CMNT" ํ ์ด๋ธ์ ์๋ ๋ชจ๋ ํ์ ์ญ์
2๏ธโฃ TODAY_WORD_TOPIC ํ ์ด๋ธ ์ด๊ธฐํ (ํ ํฝ ์ญ์ )
DELETE FROM dalmuri."TODAY_WORD_TOPIC";
- dalmuri ์คํค๋ง์ "TODAY_WORD_TOPIC" ํ ์ด๋ธ์ ์๋ ๋ชจ๋ ํ์ ์ญ์
3๏ธโฃ ์๋ก์ด ๋ฌด์์ ํ ํฝ ์ค์
PERFORM dalmuri.get_random_topic_fix();
- PERFORM ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ํจ์์ธ dalmuri.get_random_topic_fix()๋ฅผ ํธ์ถํ๊ณ ์คํ
- PERFORM : ํจ์๊ฐ ๋ฐํํ๋ ๊ฐ์ ๋ฌด์ํ๊ณ , ํจ์์ ๋ด๋ถ ์์ (side effect)๋ง ์คํํ ๋ ์ฌ์ฉ
๐ฝ ์งํผํฐ๊ฐ ์๋ ค์ฃผ๋ PERFORM๋ฌธ๐ฝ
"๊ฒ์ผ๋ก ๋ณด๊ธฐ์ ์๋ฌด๊ฒ๋ ์ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง ๋ญ๊ฐ ํ๊ณ ์์"
ํจ์๊ฐ ๋ญ๊ฐ ๊ฐ์ ๋๋ ค์ฃผ๊ธด ํ์ง๋ง ๊ทธ ๊ฐ์ ๊ตณ์ด ๋ฐ์ ํ์๋ ์๊ณ , ํจ์์ ๋ด๋ถ ํ๋(INSERT, UPDATE, DELETE)๋ง ์คํํ๊ณ ์ถ์ ๋ ์ฐ๋ ๋ช ๋ น์ด.
๋๋ถ๋ถ ํจ์ ํธ์ถ์
SELECT dalmuri.get_random_topic_fix();
์ฒ๋ผ ์๊ฒผ์ง๋ง SELECT๋ ๋ฐํ๊ฐ์ ๋ฐ์์ ํ ์ด๋ธ ํํ๋ก ๋ณด์ฌ์ฃผ๋ ๋ชฉ์ ์ด ์๋ค.
๋ฐ๋ฉด DB ๋ด๋ถ์์ ๋์๊ฐ๋ ํจ์๋ค์ ๋๋ถ๋ถ ๋ถ์ํจ๊ณผ(side effect)๋ฅผ ์ผ์ผํค๋ ค๊ณ ์ฐ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
- ์๋์ผ๋ก insertํ๊ฑฐ๋
- ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ฑฐ๋
- ์ด๋ ํ ๊ณ์ฐ์ ํธ๋ฆฌ๊ฑฐ๋ก ์ํํ๊ฑฐ๋
์ด๋ด๋ ๊ตณ์ด SELECT๋ฌธ์ ์์ฑํ๋ฉด "๊ฒฐ๊ณผ ๊ฐ์ด ์์ต๋๋ค" ๋ฑ์ ํํ๋ก ์ธ๋ฐ์๋ ๋ฐํ๊ฐ์ด ๋ฐ๋ผ์ค๋๋ฐ,
์ด๋ PERFORM์ ์ฌ์ฉํ๋ฉด ๊ฒฐ๊ณผ๋ ๋ฒ๋ฆฌ๊ณ ("๊ฒฐ๊ณผ๊ฐ์ด ์์ต๋๋ค"๋ผ๋ ์ธ๋ฐ์๋๊ฑธ ๋ฒ๋ฆผ) ํจ์๋ง ์คํํ๋ค.
PERFORM dalmuri.get_random_topic_fix();
์ด๋ ๊ฒ ์์ฑํ๋ค๋ฉด ํจ์์ ๋ฆฌํด๊ฐ(="๊ฒฐ๊ณผ๊ฐ์ด ์์ต๋๋ค")๋ ๋ฒ๋ฆฌ๊ณ ํจ์ ์์์ ๋ฒ์ด์ง๋ ์ผ๋ง ์คํํ๋ค.
3. ์ค์ผ์ค๋ง๋ฌธ ์์ฑ
SELECT cron.schedule(
'daily-topic-reset', -- 1. ์์
์ด๋ฆ (job_name)
'0 0 * * *', -- 2. Cron ์ค์ผ์ค (schedule)
'SELECT dalmuri.reset_today_topic();' -- 3. ์คํํ SQL ๋ช
๋ น (command)
);
pg_cron ํ์ฅ ๋ชจ๋์ ์๋ก์ด ์ค์ผ์ค๋ง ์์ ์ ๋ฑ๋กํ๋ค.
- ์์ ์ด๋ฆ : ๊ด๋ฆฌ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ณ ์ ํ ์ด๋ฆ
- Cron ์ค์ผ์ค : ์์ ์ ์คํํ ์๊ฐ์ ์ ์ํ๋ ํ์ค ํฌ๋ก ํํ์
- SQL ๋ช ๋ น : ์ค์ผ์ค์ ๋ง์ถ์ด ์ค์ ๋ก ์คํ๋ SQL ์ฝ๋
๋ผ๊ณ ํ๋๋ฐ ์คํ์ด ์๋ฌ๋ฐใ ์ง์ง ์ค๋ง๊ฐ์ง ๋ช ๋ น์ด๋ฅผ ๋ค์ผ๋ค
1. ์ค์ผ์ค๋ง ๋ณ๊ฒฝ
SELECT cron.alter_job(
(SELECT jobid FROM cron.job WHERE jobname = 'daily-topic-reset'), -- 1. job_id
'0 15 * * *', -- 2. ์๋ก์ด Cron ์ค์ผ์ค
NULL -- 3. command (๊ธฐ์กด ์ ์ง)
);
jobid๋ฅผ ์ฌ์ฉํด์ผ ํ๋ฏ๋ก ์์ ์ด๋ฆ์ ํตํด jobid๋ฅผ ์กฐํํ๋ ์๋ธ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
2. ๋ฑ๋ก๋ ๋ชจ๋ ์ค์ผ์ค๋ง ์์ ์กฐํ
SELECT * FROM cron.job;
2-1 ํน์ ์ปฌ๋ผ์ ๋ฑ๋ก๋ ์์ ์กฐํ
SELECT jobid, jobname, schedule, active, database, username
FROM cron.job
WHERE jobname = 'daily-topic-reset';
3. ์ค์ผ์ค๋ฌ ํ๋ก์ธ์ค ํ์ฑํ ํ์ธ
SELECT application_name, backend_type, state
FROM pg_stat_activity
WHERE backend_type = 'pg_cron launcher' OR application_name LIKE 'pg\_cron%';
PostgreSQL ์๋ฒ์์ ์คํ ์ค์ธ ํ๋ก์ธ์ค ๋ชฉ๋ก(pg_stat_activity)๋ฅผ ์กฐํํ์ฌ pg_cron์ ํต์ฌ ํ๋ก์ธ์ค์ธ pg_cron launcher๊ฐ ์ ์์ ์ผ๋ก ์คํ๋๋์ง ํ์ธํ๋ ์ฟผ๋ฆฌ์ด๋ค.
pg_cron launcher๊ฐ ์คํ์ค์ด์ด์ผ ์ค์ผ์ค๋ง ์์ ์ด ํธ๋ฆฌ๊ฑฐ๋ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
state๊ฐ null์ผ ๋ ํ๋ก์ธ์ค๊ฐ ์ ์์ ์ผ๋ก ์์๋์ด ๋ค์ ์ค์ผ์ค ์๊ฐ ๋๊ธฐ์ค(ํด๋ฉด์ํ)์ด๋ผ๋ ์๋ฏธ๋ค.
(์ฐธ๊ณ ๋ก ๋๋ null์ด ๋์๋ค)
4. ์๊ฐ๋ ํ์ธ ๐์ฌ๊ธฐ์ ์ค๋ฅ ๋ฐ๊ฒฌ
SHOW timezone;
์ด๊ฑธ ์คํํด๋ณด๋,

์ด๊ฒ ๋์๋ค.
์ด๊ฒ ์ ๋ฌธ์ ๋๋ฉด, ํ๊ตญ ์๊ฐ์ UTC๊ฐ ์๋ KST๋ฅผ ์ฌ์ฉํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฌ๋ฏ๋ก UTC๋ฅผ ๊ธฐ์ค์ผ๋ก

์ด๋ฐ์์ผ๋ก ์์ฑํ๋ฉด, UTC ๊ธฐ์ค์ ์๊ฐ๋๋ก ์ค์ 12์์ด๊ธฐ ๋๋ฌธ์ ํ๊ตญ ์๊ฐ์ผ๋ก ์คํ 3์์ ์คํ๋๋ ์ค์ผ์ค๋ง์ด๋ค.
ํ๊ตญ ์ ์ฅ์์ ์คํ 3์๋ก ์ค์ ํด๋๊ณ ์ ์ค์ 12์์ ์ค์ผ์ค๋ง ์คํ์ด ์๋๊ฑฐ์ง?? ํ๊ฑฐ๋ค!
| UTC - ํ์ ์ธ๊ณ์ | ๊ตญ์ ์ ์ธ ์๊ฐ ํ์ค์ผ๋ก, ๋ฐ๋ ๊ทธ๋ฆฌ๋์น ์ฒ๋ฌธ๋ ์๊ฐ ๊ธฐ์ค |
| KST - ํ๊ตญ ํ์ค์ | ํ๊ตญ์ด ์ฌ์ฉํ๋ ํ์ค์๋ก, UTC๋ณด๋ค 9์๊ฐ ๋น ๋ฆ |
๋ฐ๋ผ์ ์ค์ผ์ค๋ง์์ ์๊ฐ์ ํ์ํ ๋
'0 15 * * *',
์ด๋ ๊ฒ ์์ฑํด์ผ ๋ด๊ฐ ์ํ๋ ํ๊ตญ์ ์ค์ 12์ ์คํ์ด ์์ฑ๋๋๊ฑฐ๋ค.

์ด์ฉ์ง 9์ 45๋ถ์ insert๋๊ฑธ ์ 0์ 45๋ถ์ insert๋ฌ๋ค๊ณ ๋ฌ๊ฑด์ง...!!! ๋์ค๊ฐ์์ผ ์์์ฑ๋ค...
๐ Cron ํํ์ ๊ตฌ์กฐ
Cron ํํ์์ ๋ณ๋์ 5๊ฐ ํ๋์ด๋ฉฐ ๊ฐ ํ๋๋ณ๋ก ํน์ ์๊ฐ ๋จ์๋ฅผ ๋ํ๋ธ๋ค.
| ์์ | ํ๋ | ์๋ฏธ | ํ์ฉ ๋ฒ์ |
| 1 | * | ๋ถ(Minute) | 0 ~ 59 |
| 2 | * | ์(Hour) | 0 ~ 23 |
| 3 | * | ์ผ(Day) | 1 ~ 31 |
| 4 | * | ์(Month) | 1 ~ 12 |
| 5 | * | ์์ผ(Week) | 0 ~7 |
๋ฐ๋ผ์ ๋ด๊ฐ ์์ฑํ '0 15 * * * ' ์ด๊ฑฐ๋
- ๋งค ์๊ฐ 0๋ถ(์ ๊ฐ)
- ํ๋ฃจ ์ค 15์(=์คํ 3์)
- ๋งค์ผ (*)
- ๋งค์ (*)
- ๋งค ์์ผ (*)
์ด๋ผ๋ ๋ป์ด์๋ค...
์ฐธ๊ณ ๋ก Supabase์์ ํ์์กด์ KST๋ก ๋ฐ๊พธ๋ ๋ฐฉ๋ฒ์ด ์์ด์ UTC ๊ธฐ์ค์ผ๋ก ๋ง์ท๋ค.
๊ฒฐ๊ณผ


์คํํ๋๋ก ์ฃผ์ ๋ ์ ๋ค์ด๊ฐ๊ณ , ๋๊ธ๋ ์ง์์ก๋ค. ๋ง์กฑ!
[์ฐธ๊ณ ์๋ฃ]
https://silver-line-blog.tistory.com/3
UTC์ KST, ํท๊ฐ๋ ค์ ์ ๋ฆฌํด๋ดค์ต๋๋ค
์๊ฐ์ ์ด๋์๋ ํ๋ฅด์ง๋ง, ํํ ๋ฐฉ์์ ๋ค๋ฆ ๋๋ค.์ ์ธ๊ณ๊ฐ ๊ณต์ ํ๋ UTC, ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ KST. ์ด ๋์ ์ฐจ์ด๋ฅผ ์ ํํ ์๊ณ ์์ด์ผ ์๊ฐ ์ฒ๋ฆฌ ์ค๋ฅ๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.๐ UTC๋?UTC๋ Univ
silver-line-blog.tistory.com

'Backend๐ฅ๏ธ > DB, SQL๐ข๏ธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Supabase] ๋ด ํ ์ด๋ธ์ ๊ณ์ (User), ์ญํ (Role)๊ณผ ๊ถํ(Policy)์ ๋ํด ์๊ธฐ (0) | 2025.12.01 |
|---|---|
| [SQL] Connection Pool์ด๋ ๋ฌด์์ธ๊ฐ (0) | 2025.11.26 |
| [Supabase] Supabase์ ๋ํด ์์๋ณด๊ธฐ, ๊ธฐ๋ณธ ์ธํ (0) | 2025.11.25 |