Intro
ํ์ฌ ๊ธ๋ 9๊ธฐ์์ ๋ง๋ ํ์๋ค๊ณผ ๊ฐ์ด Resumait ๋ผ๋ ์ ํ์ ๋ง๋ค๊ณ ์๋ค. CS ์คํฐ๋๋ฅผ ์งํํ๋ค๊ฐ ๋ป์ด ๋ง์์ ์ฌ์ด๋ ํ๋ก์ ํธ๊น์ง ๊ฐ๋ฐ์ ํ๊ฒ ๋์๋ค. Resumait ์ LLM ์ ๊ธฐ๋ฐ์ผ๋ก, ์ฌ์ฉ์์ ์ด๋ ฅ์ ์์ฑ์ ๋์์ฃผ๋ ์๋น์ค์ด๋ค. Resumait๋ ํนํ ์ฃผ๋์ด ๋ฐ ๊ฒฝ๋ ฅ ๊ฐ๋ฐ์๋ฅผ ์ฃผ์ ํ๊ฒ์ผ๋กํ๊ณ ์๋ค. ๊ฐ๋ฐ์ ์ฑ์ฉ ์์ฅ ํน์ฑ์ ํ๋์ ์ด๋ ฅ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ๋ฌ ํ์ฌ์ ๊ณต๊ณ ์ ์ง์ํ๊ฒ ๋๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฟ๋ง ์๋๋ผ, ์ข ์ข ๊ฐ๋ฐ์์๊ฒ๋ ์๊ธฐ์๊ฐ์๋ฅผ ์๊ตฌํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
Resumait ์์๋ Credit ์ด ์๋น์ค๋ฅผ ์ฌ์ฉํ ์ ์๋ ๋จ์์ด๋ค. ์ฌ์ฉ์๋ ๋ณธ์ธ์ ์ด๋ ฅ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๊ธฐ์๊ฐ์ ๊ธ์ ์์ฑํ ๋ Credit ์ ์๋นํ๊ฒ ๋๋ค. ๊ทธ๋ฐ๋ฐ ์ ์ํ ์ ์ ์ด Credit์ ์ฌ์ฉ์๊ฐ Resumait์์ ์๋น์ค๋ฅผ ์ฌ์ฉํ ์ ์๋ ํ ๋น๋๊ณผ๋ ๊ฐ๊ธฐ ๋๋ฌธ์,
Credit ์ ์ฌ์ฉํ๊ฑฐ๋ ์ถฉ์ ํ๋ ๊ณผ์ ์์ Credit ๊ฐ์ ํญ์ ๋ฌธ์ ๊ฐ ์์ด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด์, Credit ์ ์ฌ์ฉํ๋ ๊ณผ์ ์์ API ํต์ ์ ๋ฌธ์ ๊ฐ ์๊ฒจ ๋๋ฒ ์ฌ์ฉ๋๊ฒ ํ๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ์ฆ, Credit ์ ์ฌ์ฉํ ๊ฒฐ์ API ์์๋ '๋ฉฑ๋ฑ์ฑ'์ ๋ณด์ฅํด์ผ ํ๋ค. ๋ฉฑ๋ฑ์ฑ์ ์ด๋ป๊ฒ ๋ณด์ฅํ๋ฉฐ, ๋ฉฑ๋ฑ์ฑ์ด๋ผ๋ ๊ฐ๋
์ด ์ ํํ๊ฒ ๋ฌด์์ธ์ง ์ ๋ฆฌํด๋ณด์๋ค!
๋ฉฑ๋ฑ์ฑ(Idempotency)์ด๋?
๋ฉฑ๋ฑ์ฑ(Idempotency)์ด๋, ์ฐ์ฐ์ ์ฌ๋ฌ๋ฒ ์คํํ๋๋ผ๋ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง์ง ์๋ ์ฑ์ง์ ์๋ฏธํ๋ค. ๋ฉฑ๋ฑ์ฑ์ ๊ฐ๋ฐํ ๋์๋ ์ข ์ข ๋ฑ์ฅํ๊ฒ ๋๋ ๊ฐ๋ ์ธ๋ฐ, ๊ฐ๋ ์์ฒด๋ ๋์ผํ์ง๋ง ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํด์ผ ํ๋ ๋ค์ํ ์ผ์ด์ค๊ฐ ์๋ค.
1. HTTPS ๋ฉ์๋์์์ ๋ฉฑ๋ฑ์ฑ
ํํ ๋ฉฑ๋ฑ์ฑ์ด๋ผ๋ ๋จ์ด๋ฅผ HTTP ํต์ ์์ ๋ง์ด ๋ฃ๊ฒ ๋๋ค. HTTP ๋ฉ์๋์์ ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ๋ ๋ฉ์๋๋ ์๊ณ , ๊ทธ๋ ์ง ์์ ๋ฉ์๋๋ ์๋ค. ์๋ฅผ ๋ค์ด์ GET ๋ฉ์๋์ ๊ฒฝ์ฐ์๋ ๋ฆฌ์์ค๋ฅผ ์กฐํํ๊ธฐ๋ง ํ๋ค. ๋ฐ๋ผ์ ์ฌ๋ฌ๋ฒ GET ์์ฒญ์ ๋ณด๋ด๋๋ผ๋, ๋์ผํ ๊ฐ์ ๋ฆฌํด๊ฐ์ผ๋ก ๋ฐ๊ฒ ๋ ๊ฒ์ด๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ GET๋ฉ์๋๋ ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ๋ค ๋ผ๊ณ ํ ์ ์๋ค.
HTTP ๋ฉ์๋ ์ข ๋ฅ | ๋ฉฑ๋ฑ์ฑ ์ ๋ฌด |
GET | O |
POST | X |
PUT | O |
PATCH | X |
DELETE | O |
GET ๋ฉ์๋์ผ ๋น์ฐํ ๊ฐ์ ๋ํด '์กฐํ' ๋งํ๋๊น ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ๋ค๋ ๊ฒ์ด ์ฝ๊ฒ ์ดํด๋๋๋ฐ ๋ค๋ฅธ ๋ฉ์๋์ ๊ฒฝ์ฐ ํท๊ฐ๋ฆฌ๊ธฐ ๋ํ๋ค. PUT์ ๊ฒฝ์ฐ ๊ฐ์ ๋ํด ์ ๋ฐ์ดํธํ๋ ๋ฉ์๋์ด๋ค. ๊ทธ๋ ์ง๋ง ์ฌ๋ฌ๋ฒ ํธ์ถํ๊ฒ ๋์ด๋ ํญ์ ๊ฐ์ ๊ฐ์ ๋ํด์ ์ ๋ฐ์ดํธ ํ๊ธฐ ๋๋ฌธ์, ์ด ๊ฒฝ์ฐ์๋ ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ๋ค๊ณ ํ ์ ์๋ค.
DELETE์ ๊ฒฝ์ฐ์๋ ๊ฐ์ ์ญ์ ํ ํ, ๋ค์ ์์ฒญ์ ๋ณด๋ด๋๋ผ๋ ๊ฐ์ด ์ด๋ฏธ ์ญ์ ๋ ์ํ๋ ๋์ผํ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ DELETE๋ ๋ฉฑ๋ฑํ๋ค.
2. ETL ํ์ดํ๋ผ์ธ์์์ ๋ฉฑ๋ฑ์ฑ
๋ฐ์ดํฐ ETL ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ ๋์๋ ๋ฉฑ๋ฑ์ฑ์ ์ค์ํ๋ค. ๋ด๊ฐ ๋ฐ์ดํฐ์์ง๋์ด๋ง ์ ๋ฌด๋ฅผ ํ๋ฉฐ ๊ฒฝํํ ETL ํ์ดํ๋ผ์ธ์ ์ฑ๊ฒฉ์ ํฌ๊ฒ 2๊ฐ์ง์๋ค.
- ๋ง์คํฐ ์ฑ ํ ์ด๋ธ์ ๋ํ ETL ์์ : ์ผ์ ํ ์ฃผ๊ธฐ (ex. ์ผ ๋จ์) ๋ง๋ค ํ ์ด๋ธ์ ์ ์ฒด ๋ฐ์ดํฐ์ ๋ํด์ ๋ชจ๋ ๊ฐ์ด update ํ๋ ETL ์์
- ์ฆ๋ถ ์ ์ฌ๊ฐ ํ์ํ ํ ์ด๋ธ์ ๋ํ ETL ์์ : ์ผ์ ํ ์ฃผ๊ธฐ๋ง๋ค ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ๋์ ํ์ฌ ์์๊ฐ๋ ETL ์์
ํนํ, 2๋ฒ์ ์ฆ๋ถ์ ์ฌ๊ฐ ํ์ํ ํ ์ด๋ธ์ธ ๊ฒฝ์ฐ์ table row์ ์๋ณ๊ฐ์ด ๋๋ key ์ปฌ๋ผ๋ค์ ๊ธฐ์ค์ผ๋ก ๋ฐ์ดํฐ๊ฐ ์ฆ๋ถ์ ์ฌ ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฐ๋ฐ ํ์ดํ๋ผ์ธ์ ์ฅ์ ๊ฐ ์ผ์ด๋์ ๊ทธ๋ ํ์ดํ๋ผ์ธ์ 2๋ฒ ์คํ์์ผฐ์ ๋ ๋ฐ์ดํฐ๊ฐ ์ค๋ณต๋์ด์ 2๋ฒ ๋ค์ด๊ฐ๊ฒ ๋๋ฉด ๋ฌธ์ ๊ฐ ๋๋ค. ์ฆ, ETL ํ์ดํ๋ผ์ธ์์๋ ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํด ์ฃผ์ด์ผ ํ๋ ๊ฒ์ด๋ค. ์ฃผ๋ก ETL ํ์ดํ๋ผ์ธ์์ ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํด์ฃผ๊ธฐ ์ํด์ UPSERT ํธ๋์ญ์ ์ ์ง์ํ๋ค๋ฉด UPSERT ์ฐ์ฐ์ ์ฌ์ฉํ๋ค.
์๋๋ DataLake์์ ACID ํธ๋์ญ์ ์ ์ง์ํ๋ DeltaTable ์์ pyspark ๋ฅผ ์ฌ์ฉํ UPSERT ์์์ด๋ค. key ์ปฌ๋ผ์ ๊ธฐ์ค์ผ๋ก ๋์ผํ ๊ฐ์ ๋ํด์ ๋ชจ๋ ์ ๋ฐ์ดํธํ๊ณ , ์๋ ๊ฒฝ์ฐ์๋ insert ํ๊ฒ ๋๋ค.
์๋์ ์ฝ๋์์๋ target_table์ด ๋ฐ์ดํฐ๋ฅผ ์ฆ๋ถ์ ์ฌํ๋ ๋์ ํ ์ด๋ธ์ด๋ฉฐ, sourceDF๊ฐ ์๋ก ๋ค์ด์จ ๋ฐ์ดํฐ์ ํด๋นํ๋ค.
from delta.tables import DeltaTable
target_table = DeltaTable.forName(spark, "existing delta table name")
(target_table.alias("t")
.merge(sourceDF.alias("s"), "s.key = t.key")
.whenMatchedUpdateAll()
.whenNotMatchedInsertAll()
.execute())
์ฐ์ฐ์ ์ง์ํ์ง ์๋ ๊ฒฝ์ฐ์๋ key ์ปฌ๋ผ ๊ฐ๋ค์ ์ฌ์ฉํ์ฌ ์ด๋ฏธ ํ๊ฒ ํ ์ด๋ธ์ ๋ฐ์ดํฐ๊ฐ ์ ์ฌ๊ฐ ๋์ด์์ด์ ์ค๋ณต๋๋ ๊ฐ๋ค์ DELETE ํ ํ์, ํ๊ฒ ํ ์ด๋ธ์ ๊ทธ๋ ์ฆ๋ถ ์ ์ฌํ ๋ฐ์ดํฐ๋ฅผ INSERT ํ๊ฒ ๋๋ค.
-- 1. ๋ ์ง ๋ฒ์์ ํด๋นํ๋ ์ค๋ณต๋๋ ๋ฐ์ดํฐ ์ญ์
DELETE FROM target_table
WHERE key_column IN (
SELECT key_column
FROM source_table
WHERE 1=1
AND key_column >= '2024-10-12'
AND key_column < '2024-10-13'
);
-- 2. ์๋ก์ด ์ฆ๋ถ ๋ฐ์ดํฐ๋ฅผ ํ๊ฒ ํ
์ด๋ธ์ ์ฝ์
INSERT INTO target_table
SELECT *
FROM source_table
WHERE 1=1
AND key_column >= '2024-10-12'
AND key_column < '2024-10-13';
3. ๊ฒฐ์ (Credit) ์์คํ ์์ ๋ฉฑ๋ฑ์ฑ
๊ฒฐ์ ์์คํ ์์ ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด์ ํ ์ค ํ์ด๋จผ์ธ ๊ฒฐ์ ์์คํ ์ ๊ธ์ ๋ง์ด ์ฐธ๊ณ ํ์๋ค. ๋ฉฑ๋ฑ์ฑ key API์ ํฌํจ์์ผ์, ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ๊ณ ์์ ํ ๊ฒฐ์ ์์ฒญ์ ํ ์ ์๊ฒ ํ์๋ค.
์ด์ ๋ฐ๋ผ์ Litestar Application์ Credit API ๋ฅผ ๊ฐ๋ฐํ ๋์๋ ๋ฉฑ๋ฑํค (idemponency_key) ๋ผ๋ ๊ฐ์ ๋ง๋ค๊ฒ ๋์๋ค. ๊ทธ๋ฆฌ๊ณ API ์์ฒญ์ด ๋ค์ด์ฌ ๋, ๋ฉฑ๋ฑํค๊ฐ ํฌํจ๋์ด ์๋์ง ํ์ธํ๋ค. ์ด ๋ฉฑ๋ฑํค๋ ๋ฉฑ๋ฑํค๋ง์ ์ ์ฅํ๊ธฐ ์ํ table์ ๋ง๋ค์๋ค. ๋ฉฑ๋ฑํค๊ฐ ํฌํจ๋ ์์ฒญ์ด ๋ค์ด์์ ๋, ๋ฉฑ๋ฑํค table์ ํด๋น ๋ฉฑ๋ฑํค์ ๋งค์นญ๋๋ ์์ฒญ ๊ธฐ๋ก์ด ์๋์ง ํ์ธํด์ ์ค๋ณต๋๋ ์ฒ๋ฆฌ๋ฅผ ๋ฐฉ์งํ์๋ค.
Resumait์์ LLM ๊ณผ ๊ด๋ จ๋ ๋น์ฆ๋์ค ๋ก์ง์ ๊ฐ๋ฐํ๊ธฐ ์ํ ์น ๊ฐ๋ฐ ํ๋ ์์ํฌ๋ก๋ Litestar ๋ฅผ ์ฌ์ฉํ๋ค. ์ฐ์ , Credit API ์์ ์ ์ํ entity ์ ์๋ ์๋์ ๊ฐ์ด ํ๋ค. Wallet ์ด๋ผ๋ ๊ฐ๋ ์ด ์์ผ๋ฉฐ, Wallet Entity ์ credit ๊ฐ์ ์ ์ฅํ๋ค. CreditHistory๋ credit์ ์ฌ์ฉํ ๋ด์ฉ์ ๋ก๊น ํ๊ธฐ ์ํ Entity์ด๋ค. ๋ง์ง๋ง์ผ๋ก IdempotencyKey๊ฐ ๋ฉฑ๋ฑ์ฑ ํค๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ํ ์ด๋ธ๊ณผ ์ฐ๋๋๋ค.
from uuid import UUID
from advanced_alchemy.base import UUIDAuditBase
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import UUID as psql_UUID
from typing import Literal
class CreditWallet(UUIDAuditBase):
user_id: Mapped[UUID] = mapped_column(psql_UUID(as_uuid=True), unique=True, nullable=False)
credit: Mapped[int] = mapped_column(Integer, nullable=False)
class CreditHistory(UUIDAuditBase):
user_id: Mapped[UUID] = mapped_column(psql_UUID(as_uuid=True), nullable=False)
current_credit: Mapped[int] = mapped_column(Integer, nullable=False)
transaction_type: Mapped[Literal['use', 'charge']] = mapped_column(String, nullable=False)
amount: Mapped[str] = mapped_column(Integer, nullable=False)
class IdempotencyKey(UUIDAuditBase):
idempotency_key: Mapped[UUID] = mapped_column(psql_UUID(as_uuid=True), unique=True, nullable=False)
Credit ์ ์ฌ์ฉํ์ฌ ์ถฉ์ , ์ฌ์ฉํ๋ ๋น์ฆ๋์ค ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
class CreditWalletImpl(SQLAlchemyAsyncRepositoryService[CreditWallet]):
repository_type = CreditWalletRepo
async def check(self
, user_id: UUID) -> CreditWallet | None:
"""check current credit"""
credit = await self.repository.get_one_or_none(user_id=user_id)
if credit is None:
raise HTTPException(detail="Not Found", status_code=404)
return credit
async def create_wallet(self
, user_id:UUID) -> CreditWallet:
"""create wallet entity"""
wallet = CreditWallet(user_id=user_id, credit=1500)
await self.repository.add(wallet)
await self.repository.session.commit()
await self.repository.session.refresh(wallet)
return wallet
async def charge(self, user_id: UUID, amount: int) -> bool:
"""charge credit"""
wallet = await self.repository.get_one_or_none(user_id=user_id)
if wallet is None:
raise HTTPException(detail="Not Found", status_code=404)
wallet.credit += amount
await self.repository.update(wallet)
await self.repository.session.commit()
return True
async def use(self, user_id: UUID, amount: int) -> bool:
"""use credit"""
wallet = await self.repository.get_one_or_none(user_id=user_id)
if wallet and wallet.credit >= amount:
wallet.credit -= amount
await self.repository.update(wallet)
return True
return False
async def delete_wallet(self, user_id:UUID) -> bool:
"""delete wallet entity when the user account deleted"""
wallet = await self.repository.get_one_or_none(user_id=user_id)
if wallet is None:
raise HTTPException(detail="Not Found", status_code=404)
await self.repository.delete(wallet.id)
await self.repository.session.commit()
return True
๋ค์์ผ๋ก, ๋ฉฑ๋ฑ์ฑํค๋ฅผ ์กฐํํ๊ณ ์ถ๊ฐํ๋ ๋ก์ง์ด๋ค.
class IdempotencyKeyImpl(SQLAlchemyAsyncRepositoryService[IdempotencyKey]):
repository_type = IdempotencyKeyRepo
async def get_by_key(self, idempotency_key:UUID) -> IdempotencyKey | None:
"""search by idempotency key"""
return await self.repository.get_one_or_none(idempotency_key=idempotency_key)
async def add_new_key(self, idempotency_key:UUID) -> IdempotencyKey:
"""add a new idempotency key"""
key = IdempotencyKey(idempotency_key=idempotency_key)
await self.repository.add(key)
await self.repository.session.commit()
await self.repository.session.refresh(key)
return key
๋ฉฑ๋ฑ์ฑ ํค๋ฅผ ์ฌ์ฉํ์ฌ, ์ค๋ณต๋๋ ์์ฒญ ์ฒ๋ฆฌ๋ฅผ ๋ฐฉ์งํ๋ ์๋น์ค ๋ ์ด์ด์ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค. charge_credit ํจ์(credit ์ถฉ์ )์ use_credit ํจ์(credit ์ฌ์ฉ)์์ ๋ฉฑ๋ฑ์ฑ ํค๋ฅผ parameter๋ก ๋ฐ๊ณ ์๋ค.
@dataclass
class CreditService:
wallet_impl: CreditWalletImpl
history_impl: CreditHistoryImpl
idempotency_impl: IdempotencyKeyImpl
async def check_credit_wallet(self, user_id:UUID) -> CreditWalletSchema:
wallet = await self.wallet_impl.check(user_id=user_id)
return self.wallet_impl.to_schema(wallet, schema_type=CreditWalletSchema)
async def create_credit_wallet(self, user_id:UUID) -> CreditWalletSchema:
wallet = await self.wallet_impl.create_wallet(user_id=user_id)
return self.wallet_impl.to_schema(wallet, schema_type=CreditWalletSchema)
async def charge_credit(self
, user_id:UUID
, amount: int
, idempotency_key:UUID) -> CreditHistorySchema | dict[str:str]:
existing_key = await self.idempotency_impl.get_by_key(idempotency_key=idempotency_key)
if existing_key:
"""์ด๋ฏธ ์ฒ๋ฆฌ๋ ์์ฒญ"""
return {"message": "request was already processed"}
await self.idempotency_impl.add_new_key(idempotency_key=idempotency_key)
result = await self.wallet_impl.charge(user_id=user_id, amount=amount)
if result:
obj = await self.wallet_impl.check(user_id=user_id)
history = await self.history_impl.record_history(user_id=user_id
, amount=amount
, current=obj.credit
, transaction_type="charge")
return self.history_impl.to_schema(history, schema_type=CreditHistorySchema)
else:
return {"message": "user_id is not valid !"}
async def use_credit(self
, user_id:UUID
, amount: int
, idempotency_key:UUID) -> CreditHistorySchema | dict[str:str]:
existing_key = await self.idempotency_impl.get_by_key(idempotency_key=idempotency_key)
if existing_key:
"""์ด๋ฏธ ์ฒ๋ฆฌ๋ ์์ฒญ"""
return {"message": "request was already processed"}
await self.idempotency_impl.add_new_key(idempotency_key=idempotency_key)
result = await self.wallet_impl.use(user_id=user_id, amount=amount)
if result:
obj = await self.wallet_impl.check(user_id=user_id)
history = await self.history_impl.record_history(user_id=user_id
, amount=amount
, current=obj.credit
, transaction_type="use")
return self.history_impl.to_schema(history, schema_type=CreditHistorySchema)
else:
obj = await self.wallet_impl.check(user_id=user_id)
if obj:
return {"message": "insufficient credit !"}
return {"message": "user_id is not valid !"}
async def delete_credit_wallet(self, user_id:UUID) -> bool:
return await self.wallet_impl.delete_wallet(user_id=user_id)
๋ฉฑ๋ฑ์ฑ์ ๊ดํ ๊ฐ๋ ๊ณผ ์์, ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ ์๋น์ค Resumait์ Credit API ์ ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ๊ณ ์๋ ์์์ ๋ํด์ ์๊ฐํ๋ค. Resumait์ ์ ์ ๋ค์ด ์์ฑ๋ ์๊ธฐ์๊ฐ์๋ฅผ ์ ๋ฐ์๋ณด๊ณ , Credit์ด ์๋ชป ์ฐ์ด๋ ๋ฌธ์ ๋ค์ด ์์์ผ๋ฉด ์ข๊ฒ ๋ค
Reference
https://docs.tosspayments.com/blog/what-is-idempotency
'Backend ๐ป' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[sqlalchemy] Entity.metadata.create_all() ์๋์ผ๋ก ํ ์ด๋ธ ์์ฑํ๊ธฐ (0) | 2024.08.08 |
---|---|
[GitHub] REMOTE HOST IDENTIFICATION HAS CHANGED ํด๊ฒฐ๋ฐฉ๋ฒ (0) | 2023.03.26 |