aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord Botları

discord.py SQLite: aiosqlite ile Asenkron Veri Saklama

Bir Discord botu kullanıcı verisi tutmaya başladığı anda discord.py SQLite ikilisi en pratik çözümlerden biri hâline gelir: kurulum gerektirmeyen, tek dosyalık, sunucu istemeyen bir veritabanı. Ancak burada kritik bir tuzak var; standart sqlite3 modülü eşzamanlıdır (blocking) ve her sorguda botun event loop'unu dondurarak tüm komutları yavaşlatır. Çözüm aiosqlite: SQLite'ı async/await dünyasına taşıyan, discord.py 2.x ile mükemmel uyumlu bir kütüphane. Bu yazıda ekonomi/seviye verisini asenkron ve güvenli biçimde saklayan bir altyapı kuruyoruz.

Neden aiosqlite, neden sqlite3 değil?

discord.py tamamen asenkron bir mimari üzerine kuruludur: tek bir thread içinde dönen event loop, gelen mesajları ve etkileşimleri sırayla işler. Standart sqlite3 ile yaptığınız her sorgu bu döngüyü bekletir; veritabanı diske yazarken botunuz ping'lere bile cevap veremez. aiosqlite ise sorguyu arka plandaki bir thread'e devreder ve sonucu await edilebilir bir coroutine olarak döndürür. Pratikteki kazanımlar:

  • Bloklamayan G/Ç: Yavaş bir sorgu diğer komutları kilitlemez.
  • Doğal söz dizimi: async with ve await ile discord.py kodunuza birebir oturur.
  • Bağımsızlık: MySQL/PostgreSQL gibi ayrı bir sunucu kurmaya gerek yoktur; veritabanı tek bir .db dosyasıdır.

Kurulum tek satır: pip install aiosqlite. Altında yatan SQLite zaten Python ile birlikte gelir.

Veritabanını bota bağlamak

En temiz yöntem, bağlantıyı bot başlarken setup_hook içinde açıp bot nesnesine bir nitelik olarak iliştirmektir. Böylece tüm cog'lar self.bot.db üzerinden aynı bağlantıyı paylaşır. Bağlantıyı kapanışta da düzgünce kapatırız:

import discord
from discord.ext import commands
import aiosqlite

class MyBot(commands.Bot):
    def __init__(self):
        intents = discord.Intents.default()
        super().__init__(command_prefix="!", intents=intents)
        self.db: aiosqlite.Connection | None = None

    async def setup_hook(self):
        self.db = await aiosqlite.connect("data.db")
        # Eşzamanlı okuma/yazmayı iyileştirir
        await self.db.execute("PRAGMA journal_mode=WAL;")
        await self.init_db()

    async def init_db(self):
        await self.db.execute("""
            CREATE TABLE IF NOT EXISTS users (
                user_id INTEGER PRIMARY KEY,
                balance INTEGER NOT NULL DEFAULT 0,
                xp      INTEGER NOT NULL DEFAULT 0
            )
        """)
        await self.db.commit()

    async def close(self):
        if self.db is not None:
            await self.db.close()
        await super().close()

PRAGMA journal_mode=WAL (Write-Ahead Logging) okuyucuların yazıcıyı bloklamamasını sağlar; çok kullanıcılı bir botta belirgin fark yaratır. CREATE TABLE IF NOT EXISTS sayesinde tablo her açılışta güvenle hazırlanır.

Veri yazmak: parametreli sorgular

Asla kullanıcı girdisini doğrudan SQL metnine yapıştırmayın; bu SQL injection kapısını açar. Bunun yerine ? yer tutucularını ve bir parametre demetini kullanın. SQLite'ın INSERT ... ON CONFLICT sözdizimi, kaydın olmadığı durumda ekleme, varsa güncelleme (upsert) işini tek sorguda yapar:

async def add_balance(db, user_id: int, amount: int):
    await db.execute(
        """
        INSERT INTO users (user_id, balance)
        VALUES (?, ?)
        ON CONFLICT(user_id)
        DO UPDATE SET balance = balance + excluded.balance
        """,
        (user_id, amount),
    )
    await db.commit()

Kritik nokta: aiosqlite değişiklikleri otomatik kalıcı kılmaz. Her yazma işleminden sonra await db.commit() çağırmazsanız veriler diske işlenmez ve bot yeniden başladığında kaybolur.

Veri okumak: fetchone ve fetchall

Okuma tarafında imleç (cursor) üzerinden ilerlersiniz. async with bloğu imleci otomatik kapatır:

async def get_balance(db, user_id: int) -> int:
    async with db.execute(
        "SELECT balance FROM users WHERE user_id = ?",
        (user_id,),
    ) as cursor:
        row = await cursor.fetchone()
        return row[0] if row else 0

Birden çok satır için fetchall kullanın; örneğin bir seviye sıralaması (leaderboard):

async def top_users(db, limit: int = 10):
    async with db.execute(
        "SELECT user_id, xp FROM users ORDER BY xp DESC LIMIT ?",
        (limit,),
    ) as cursor:
        return await cursor.fetchall()

Sonuçlara sütun adıyla erişmek isterseniz db.row_factory = aiosqlite.Row ayarını yapın; o zaman row["balance"] gibi okuyabilirsiniz.

Bir komut içinde kullanmak

Yardımcı fonksiyonları bir cog içindeki komuta bağlamak çok kısadır. self.bot.db her yerden erişilebilir olduğundan, komut yalnızca iş mantığına odaklanır:

from discord.ext import commands

class Economy(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.command()
    async def daily(self, ctx):
        await add_balance(self.bot.db, ctx.author.id, 100)
        bal = await get_balance(self.bot.db, ctx.author.id)
        await ctx.send(f"Günlük 100 coin alındı. Bakiyen: {bal}")

async def setup(bot):
    await bot.add_cog(Economy(bot))

Performans ve güvenlik ipuçları

  • Tek bağlantı, paylaşımlı: Her komutta yeni bağlantı açıp kapatmak yerine bot ömrü boyunca tek bağlantı tutun.
  • İndeks ekleyin: Sık sorguladığınız sütunlara (örn. sıralama için xp) CREATE INDEX tanımlamak büyük tablolarda hız kazandırır.
  • Toplu işlemler: Çok sayıda satırı tek seferde işlerken executemany kullanıp sonunda tek commit atın.
  • Yedekleme: SQLite tek dosyadır; bota dokunmadan dosyayı kopyalamak için VACUUM INTO ya da çevrimiçi yedek API'sini değerlendirin.

SQLite tek sunuculu, orta ölçekli botlar için fazlasıyla yeterlidir. Yüz binlerce kullanıcıya ulaşıp ağır eşzamanlı yazma ihtiyacı doğduğunda PostgreSQL'e geçiş düşünülebilir; ama o noktaya kadar aiosqlite sade ve hızlı bir tercihtir.

Sık Sorulan Sorular

commit çağırmayı unutursam ne olur?

Yazdığınız veriler yalnızca o oturumdaki bağlantı içinde görünür kalır ama diske kalıcı olarak işlenmez. Bot yeniden başladığında son commit'ten sonraki tüm değişiklikler kaybolur. Her INSERT/UPDATE/DELETE sonrası await db.commit() çağırmayı alışkanlık hâline getirin.

Normal sqlite3 modülünü kullanamaz mıyım?

Teknik olarak çalışır, ama her sorgu botun event loop'unu bloklar ve tüm komutları geciktirir. Düşük trafikli bir bot için fark edilmeyebilir; gerçek kullanımda aiosqlite tek doğru tercihtir çünkü asenkron mimariye uyum sağlar.

Birden çok cog aynı veritabanını güvenle kullanabilir mi?

Evet. Bağlantıyı self.bot.db üzerinde paylaştığınız sürece tüm cog'lar aynı tek bağlantıyı kullanır. WAL modu eşzamanlı okumaları rahatlatır; yazmalar SQLite tarafından sıraya alınarak güvenle yürütülür.

Botunuza sağlam bir veri katmanı mı kurmak istiyorsunuz? aiosqlite altyapısı tasarlamak, mevcut sqlite3 kodunuzu asenkrona taşımak ya da ekonomi/seviye sistemi kurmak için benimle iletişime geçin.

Bu kategorideki tüm yazılar →

Devamı için