Telegram Bots

Storing Telegram Bot State: SQLite, MySQL or PostgreSQL

How to store users, settings and conversation state for a Telegram bot — choosing SQLite, MySQL or PostgreSQL, handling chat IDs correctly and surviving restarts.

On this page
  1. Memory is not storage
  2. The options
  3. What Kerit Cloud plans include
  4. Telegram IDs: get the column types right
  5. Conversation state (FSM)
  6. Connecting safely
  7. Backups and migrations
  8. Summary

Every bot beyond the simplest echo bot needs to remember things: who its users are, their settings, where they are in a conversation, what they’ve subscribed to. Where you keep that data decides whether your bot survives a restart, a redeploy or a growth spurt. This article compares the options and covers the Telegram-specific details that trip people up.

Memory is not storage

Dictionaries and in-memory session stores are fine for development. In production they have one fatal flaw: a restart erases them. Deploys, crashes, plan upgrades and host maintenance all restart your bot. If a user was halfway through a sign-up flow, or subscribed to daily reminders, that state is gone.

Anything that must outlive the process belongs in persistent storage. The question is which kind.

The options

JSON files

Easy to start, easy to read — and easy to corrupt. If two updates write the file at once, one overwrites the other; if the bot crashes mid-write, the file can be left half-written and unparseable on the next start. JSON is acceptable for read-mostly configuration, not for user data.

SQLite

A full SQL database in a single file, built into Python and available for every language. No server to run, no network latency, transactions and indexes included. For a single bot process with modest traffic, SQLite is an excellent choice.

It works on Kerit Cloud because server storage is persistent NVMe — the database file survives restarts and redeploys. Just keep it out of git so a deploy never overwrites it.

SQLite’s limits: one writer at a time, awkward to share between several processes, and no remote access for a dashboard or a second service.

MySQL

A client-server database: the bot connects over the network. It handles concurrent writers, several bot processes, dashboards and analytics queries. It’s widely supported by every library and ORM.

PostgreSQL

Also client-server, with a richer feature set: excellent JSON support with JSONB, powerful indexing, strict data integrity and advanced SQL. A great fit for bots with complex data or analytics.

SQLite MySQL PostgreSQL
Setup None — a file Server (managed for you) Server (managed for you)
Multiple processes Awkward Yes Yes
Remote access (dashboards) No Yes Yes
JSON data Basic Good Excellent (JSONB)
Best for One small-to-medium bot Growing bots, several services Complex data, analytics

For a deeper comparison, see MySQL vs PostgreSQL for bots and small apps.

What Kerit Cloud plans include

Telegram plans come with databases built in:

  • Free — a shared MySQL database.
  • Starter (₹99/mo) — a MySQL database and daily backups.
  • Pro (₹199/mo) — MySQL and PostgreSQL, with one-click restore.
  • Ultra (₹399/mo) — MySQL, PostgreSQL and Redis.

If your data outgrows the included database, standalone database plans give MySQL 8 or PostgreSQL 16 dedicated resources and longer backup retention.

Telegram IDs: get the column types right

This is the most common schema mistake in Telegram bots.

  • User IDs can exceed 32 bits. Telegram has said identifiers can be up to 52 significant bits. Store them as BIGINT, never INT.
  • Chat IDs can be negative. Groups have negative IDs, and supergroups and channels use IDs beginning with -100. A signed BIGINT handles all of them; an unsigned column breaks.
  • Don’t key on usernames. Usernames are optional, can change at any time, and can be claimed by someone else. Store them for display, but identify users by ID.

A reasonable starting schema (PostgreSQL):

CREATE TABLE users (
  user_id     BIGINT PRIMARY KEY,
  username    TEXT,
  first_name  TEXT,
  language    TEXT DEFAULT 'en',
  is_active   BOOLEAN NOT NULL DEFAULT TRUE,   -- false once they block the bot
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE chat_settings (
  chat_id     BIGINT PRIMARY KEY,               -- negative for groups
  settings    JSONB NOT NULL DEFAULT '{}'
);

Update username and first_name whenever you see the user, since they change. Flip is_active to false when sending fails with “bot was blocked by the user”, so broadcasts skip them.

Conversation state (FSM)

Multi-step conversations need to remember which step each user is on. Frameworks provide this as sessions or FSM storage:

  • aiogram — FSM storage; in-memory by default, RedisStorage for production.
  • grammY — the session plugin with storage adapters for files, Redis, and SQL databases.
  • telebot — state storage with memory and Redis backends.

Use a persistent backend in production. Redis is the natural fit — fast, with expiry so abandoned conversations clean themselves up — and it’s included on the Ultra plan. Without Redis, store state in a table keyed by user and chat, with an updated_at column you can use to expire stale conversations.

Connecting safely

  • Keep credentials in environment variables, such as a DATABASE_URL. Never commit them.
  • Use connection pools in async bots (asyncpg pools, aiomysql pools, SQLAlchemy async engines) rather than opening a connection per update.
  • Use parameterised queries — never build SQL by formatting user input into a string.
  • Close the pool on shutdown, in your framework’s shutdown hook, so restarts are clean.

A minimal asyncpg pool with aiogram:

import asyncpg

async def on_startup(dispatcher):
    dispatcher["db"] = await asyncpg.create_pool(os.environ["DATABASE_URL"], min_size=1, max_size=5)

async def on_shutdown(dispatcher):
    await dispatcher["db"].close()

Keep pool sizes small — databases have connection limits, and a handful of connections serves a bot well. See connection limits and pooling.

Backups and migrations

  • Back up. Paid Kerit Cloud plans back up automatically, and database restores create a new database rather than overwriting the old one, so a restore never destroys data. For SQLite, copy the file while the bot is stopped, or use SQLite’s online backup API.
  • Version your schema. Use migrations (Alembic, Prisma Migrate, or plain numbered SQL files) instead of editing tables by hand. See schema migrations for bot developers.

Summary

Keep bot state out of memory. SQLite is ideal for a single small-to-medium bot on persistent storage; MySQL or PostgreSQL suit growing bots, multiple processes and dashboards; Redis is the best home for conversation state. Store Telegram IDs as signed BIGINT, identify users by ID rather than username, use pooled parameterised queries, and back up. Your bot will then survive every restart with its memory intact.