feat!: replace redis with sqlite file storage - #84
Merged
Merged
Conversation
Store message history in a local SQLite file using the built-in node:sqlite module instead of a Redis server. This removes the redis dependency and the Redis container from the Docker deployment: the database is now a single file on a bind-mounted ./data directory. Messages are kept forever, but summaries only consider those from the last 24 hours (replacing the previous 8-hour key expiration). BREAKING CHANGE: REDIS_URL is replaced by SQLITE_PATH and Node.js >= 22.5.0 is now required.
Replace the Storage class with a module-level SQLite handle opened on first use. SQLite has no connection to manage, so the connect/disconnect lifecycle, the null checks on the handle, the async signatures and the storage parameter threaded through every handler were all residue of the old Redis client and can go away.
Silence the Vite deprecation warning about ESM syntax in a file loaded as CommonJS.
Save user_id, user_firstname and user_lastname alongside the username, which becomes nullable since not every Telegram user has one. Summaries display the username when present, falling back to first name or user id.
The test job ran on Node 20, which predates the built-in node:sqlite module (added in 22.5).
Contributor
There was a problem hiding this comment.
Review completed against the latest diff
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The history query filters on created_at but the index only covered chat_id, so summaries scanned a chat's entire retained history instead of seeking to the 24-hour cutoff.
The daily cron iterated over every chat that ever wrote a message, querying each one's history just to discard chats with nothing in the 24-hour window. Filter them out in the query instead.
Node 22.5 through 22.12 only expose node:sqlite behind the --experimental-sqlite flag; the module is available without flags from 22.13.0.
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Without planner statistics the active-chats query falls back to scanning the whole covering index, growing with all retained history. With them, SQLite skip-scans the existing (chat_id, created_at) index.
Contributor
There was a problem hiding this comment.
1 issue found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/controller/core.ts">
<violation number="1" location="src/controller/core.ts:127">
P2: Every incoming message now performs a synchronous SQLite write that blocks the Node.js event loop, and each /summary / cron digest performs synchronous reads as well. Because the store is synchronous (node:sqlite DatabaseSync) and often lives on a mounted volume, storage latency can stall all concurrent message processing, transcription, and LLM calls in the process. This is a deliberate simplification, but it trades the previous non-blocking async storage for blocking I/O in the hottest path. Consider keeping the sync API for simplicity or, if responsiveness matters, moving writes/reads to an async worker pool or a write queue — at minimum the tradeoff is worth confirming against expected message volume.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Full ANALYZE scans every index row, so startup time grew with retained history. Sampling keeps the skip-scan plan while making the cost constant.
derogab
added a commit
to derogab/apps
that referenced
this pull request
Aug 1, 2026
Summarygram dropped its Redis dependency in derogab/summarygram#84; chat history now lives in a SQLite file bind-mounted from ./data. Existing Redis history is not migrated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replace the Redis server with a single SQLite file using Node's built-in
node:sqlitemodule. Message reads and writes are marginally slower than an in-memory store, but for this workload the difference is irrelevant — and in exchange the bot loses an entire external service: no Redis container, no connection lifecycle, no extra dependency. Data management and storage also get simpler: history is plain rows in one queryable file, easy to inspect, copy and back up, instead of TTL-bound string-encoded lists in a separate server.Changes
Storage
messagestable in a local SQLite file (SQLITE_PATH, defaultsummarygram.sqlite), replacing Redis lists./summaryand the daily cron only consider messages from the last 24 hours (previously the whole history expired 8 hours after the last message).chat_id,user_id,username(nullable),user_firstname,user_lastname,messageandcreated_at. Summaries display username, falling back to first name or user id.###are no longer truncated (columns replaced the old string-encoded format).Code simplification
Storageclass and its Redis-style connect/disconnect lifecycle: the db handle is opened lazily at module level and the storage functions are plain synchronous calls.chat:key prefix and the storage parameter threaded through every handler.Deployment
docker-compose.yml; the app now bind-mounts../dataand reads/writes the db file there.redisnpm dependency; Node.js >= 22.13.0 is now required fornode:sqlite..mts.Breaking changes
REDIS_URLis replaced bySQLITE_PATH.Test plan
npm run compilepasses (TypeScript 7)npm testpasses (27 tests, storage tests run against a real in-memory SQLite db).sqlitefiledocker compose -f docker/docker-compose.yml up -dand verify messages are stored in./data/summarygram.sqliteand/summaryworksSummary by cubic
Replaced Redis with a single SQLite file via Node’s
node:sqliteto simplify deployment and remove an external service. Indexed queries, filtered stale chats, and run bounded startupANALYZEto speed up/summaryand the daily cron.New Features
SQLITE_PATH(defaultsummarygram.sqlite). Each message saveschat_id,user_id,username(nullable),user_firstname,user_lastname,message,created_at; summaries display@usernamewhen present, otherwise first name or user id. Messages containing###are preserved.redisdependency; bind-mount../dataand setSQLITE_PATH. Dropped theStorageclass and its connect/disconnect flow; storage calls are synchronous. Updatedgrammy,@types/node, andTypeScript.(chat_id, created_at), madegetActiveChatsignore chats with only old messages, and runANALYZEat startup withanalysis_limitso SQLite uses skip-scan plans without growing startup cost.docker-publishworkflow to supportnode:sqlite.Migration
REDIS_URLwithSQLITE_PATH.../dataand setSQLITE_PATH=/data/summarygram.sqlite.Written for commit c39e90f. Summary will update on new commits.