Skip to content

feat!: replace redis with sqlite file storage - #84

Merged
derogab merged 16 commits into
masterfrom
feat/sqlite
Aug 1, 2026
Merged

derogab merged 16 commits into
masterfrom
feat/sqlite

Conversation

@derogab

@derogab derogab commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

Replace the Redis server with a single SQLite file using Node's built-in node:sqlite module. 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

  • Message history now lives in a messages table in a local SQLite file (SQLITE_PATH, default summarygram.sqlite), replacing Redis lists.
  • Messages are kept forever; /summary and the daily cron only consider messages from the last 24 hours (previously the whole history expired 8 hours after the last message).
  • Each message stores chat_id, user_id, username (nullable), user_firstname, user_lastname, message and created_at. Summaries display username, falling back to first name or user id.
  • Messages containing ### are no longer truncated (columns replaced the old string-encoded format).

Code simplification

  • Dropped the Storage class and its Redis-style connect/disconnect lifecycle: the db handle is opened lazily at module level and the storage functions are plain synchronous calls.
  • Removed the chat: key prefix and the storage parameter threaded through every handler.

Deployment

  • Removed the Redis container from docker-compose.yml; the app now bind-mounts ../data and reads/writes the db file there.
  • Removed the redis npm dependency; Node.js >= 22.13.0 is now required for node:sqlite.
  • Updated grammy, @types/node and TypeScript (7.x); renamed vitest config to .mts.

Breaking changes

  • REDIS_URL is replaced by SQLITE_PATH.
  • Requires Node.js >= 22.13.0.
  • Existing Redis data is not migrated: history restarts empty.

Test plan

  • npm run compile passes (TypeScript 7)
  • npm test passes (27 tests, storage tests run against a real in-memory SQLite db)
  • Smoke-tested the compiled storage layer against a real .sqlite file
  • Deploy via docker compose -f docker/docker-compose.yml up -d and verify messages are stored in ./data/summarygram.sqlite and /summary works

Summary by cubic

Replaced Redis with a single SQLite file via Node’s node:sqlite to simplify deployment and remove an external service. Indexed queries, filtered stale chats, and run bounded startup ANALYZE to speed up /summary and the daily cron.

  • New Features

    • Store chat history in a local SQLite file at SQLITE_PATH (default summarygram.sqlite). Each message saves chat_id, user_id, username (nullable), user_firstname, user_lastname, message, created_at; summaries display @username when present, otherwise first name or user id. Messages containing ### are preserved.
    • Simpler deploy and code: removed the Redis container and redis dependency; bind-mount ../data and set SQLITE_PATH. Dropped the Storage class and its connect/disconnect flow; storage calls are synchronous. Updated grammy, @types/node, and TypeScript.
    • Performance: added an index on (chat_id, created_at), made getActiveChats ignore chats with only old messages, and run ANALYZE at startup with analysis_limit so SQLite uses skip-scan plans without growing startup cost.
    • CI: use Node 24 in the docker-publish workflow to support node:sqlite.
  • Migration

    • Replace REDIS_URL with SQLITE_PATH.
    • Require Node.js >= 22.13.0.
    • Existing Redis data is not migrated.
    • Docker: mount ../data and set SQLITE_PATH=/data/summarygram.sqlite.

Written for commit c39e90f. Summary will update on new commits.

Review in cubic

derogab added 9 commits July 31, 2026 21:51
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).

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/utils/data.ts
Comment thread src/utils/data.ts Outdated
Comment thread src/utils/data.ts Outdated
derogab added 4 commits August 1, 2026 02:11
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.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/utils/data.ts
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.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/utils/data.ts Outdated
Comment thread src/utils/data.ts
Comment thread src/controller/core.ts
Comment thread package.json
Comment thread src/controller/core.ts Outdated
derogab added 2 commits August 1, 2026 03:37
Authors without a Telegram username were rendered as @alice or @12345
in the LLM prompt, looking like malformed mentions. getHistory now
returns a ready-to-use author display name: @username when one exists,
otherwise the plain first name or user id.
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
derogab merged commit cd93807 into master Aug 1, 2026
5 checks passed
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant