Skip to content

Add option to disable automatic last message loading - #3731

Open
sunlei wants to merge 1 commit into
tdlib:masterfrom
sunlei:sunlei/disable-automatic-last-message-loading
Open

Add option to disable automatic last message loading#3731
sunlei wants to merge 1 commit into
tdlib:masterfrom
sunlei:sunlei/disable-automatic-last-message-loading

Conversation

@sunlei

@sunlei sunlei commented Aug 24, 2026

Copy link
Copy Markdown

Summary

This PR adds a writable Boolean option named disable_automatic_last_message_loading.

When use_message_database is false and this option is true, TDLib no longer automatically requests message history through load_last_dialog_message to populate a chat's last message.

The option defaults to false, so the existing behavior remains unchanged for current clients.

Context: #62, #1605, #2286, #2893

Background

TDLib's local database grows over time, and the message database currently has no size limit. This has been discussed in #62 and #1605.

In #2893, disabling all databases through setTdlibParameters is suggested for applications that don't need them, so that db.sqlite is no longer used.

However, when the message database is disabled, TDLib can't restore a dialog's last message from the local database. load_last_dialog_message currently calls get_history_impl, which may eventually send a remote messages.getHistory request.

#2286 documents this behavior: when the last message of a chat becomes unknown, TDLib automatically calls getChatHistory to repair it. A large number of such requests may result in FLOOD_WAIT errors.

Automatic last-message loading can be triggered by multiple internal paths, including:

  • initializing or loading a dialog;
  • updating a dialog position;
  • completing a channel difference;
  • deleting the current last message.

Disabling the databases can therefore cause a history-request fan-out that the application can't control. For accounts with many dialogs, these automatic requests may result in FLOOD_WAIT errors.

Some applications intentionally disable the message database and can handle chats whose last_message is temporarily unknown. For these applications, automatically loading history isn't required.

Changes

This PR adds the following check to the common MessagesManager::load_last_dialog_message entry point:

if (!G()->use_message_database() &&
    td_->option_manager_->get_option_boolean("disable_automatic_last_message_loading")) {
  return;
}

It also registers disable_automatic_last_message_loading as a writable Boolean option in OptionManager.

Placing the check in the common entry point covers all current automatic loading paths that use load_last_dialog_message, without changing each call site separately.

Option semantics

disable_automatic_last_message_loading:

  • Type: Boolean
  • Writable: yes
  • Default: false
  • Effective only when use_message_database == false

When enabled, TDLib doesn't automatically load message history to populate a chat's last message. The application must be able to handle chats with an unknown last message.

The option has no effect when the message database is enabled.

Unaffected behavior

This change doesn't prevent:

  • explicit application calls to getChatHistory;
  • pagination and continuation of explicit history requests;
  • getDifference or getChannelDifference requests;
  • new-message updates from updating a chat's last message;
  • other TDLib background requests.

The change only gates automatic history loads made through load_last_dialog_message.

Why this is opt-in

Unconditionally skipping automatic last-message loading whenever the message database is disabled would change the behavior of all existing database-less clients.

Some clients may rely on TDLib to populate chat.last_message automatically. A disabled-by-default option preserves backward compatibility while allowing applications that don't need this behavior to opt out explicitly.

Why not use ignore_background_updates

ignore_background_updates changes offline update recovery and update-state persistence, so its scope is much broader than last-message loading.

The new option only controls automatic last-message loading and doesn't modify update-difference code paths.

Usage

The setOption request needs to be sent to TDLib before the setTdlibParameters request. The client doesn't need to wait for the setOption response before sending the initialization parameters.

First send:

{
  "@type": "setOption",
  "name": "disable_automatic_last_message_loading",
  "value": {
    "@type": "optionValueBoolean",
    "value": true
  }
}

Then initialize a database-less client with the relevant fields set to:

{
  "@type": "setTdlibParameters",
  "use_file_database": false,
  "use_chat_info_database": false,
  "use_message_database": false
}

Trade-offs

When the option is enabled:

  • last_message may remain unknown for some chats until TDLib learns it from a subsequent update, a dialog response, or an explicit application request;
  • less local message context is available without the message database;
  • the application needs to accept the existing functional limitations of running without a local message cache.

These changes apply only to clients that explicitly enable the option.

Non-goals

This PR doesn't address:

  • size limits or retention while the message database is enabled;
  • SQLite cleanup, compaction, or vacuuming;
  • other background requests such as getPeerDialogs, getPeerSettings, or getBirthdays;
  • full semantic equivalence between database-less mode and mode with all databases enabled.

Validation

  • tdcore builds successfully.
  • run_all_tests passes.
  • The clang-format check passes.
  • git diff --check passes.
100% tests passed, 1 test out of 1
Total Test time: 417.54 sec

- Skip automatic last-message history requests without message database.
- Keep the current behavior by default.
@levlam

levlam commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

The last message is required for all chats in the user chat list to maintain the correct position of the chat in the list. Any such change will break app UX and therefore can't be accepted to the upstream, but you are free to add it to your fork of TDLib.

@sunlei

sunlei commented Aug 24, 2026

Copy link
Copy Markdown
Author

@levlam Thanks, I understand the concern now.

The option being disabled by default preserves behavior for existing clients, but I agree that enabling it can leave the last message unknown and therefore keep the chat position stale. I also noticed that the TDLib API explicitly allows messages to be added without a corresponding updateNewMessage while the last message is unknown, so leaving this state unrepaired isn't a safe general-purpose mode.

The problem I am trying to solve is the per-dialog getChatHistory fan-out in database-less mode, rather than removing the last-message invariant itself.

Would an implementation that preserves automatic last-message repair, but batches or coalesces repairs for multiple dialogs, be acceptable upstream?

@levlam

levlam commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

There is no way to "batch" the requests. The last message hardly ever needs to be repaired, therefore regular users have no issues. Bots don't need the last chat message at all. This behavior isn't optional, it is something that must be done always.

@omd2you-a11y

Copy link
Copy Markdown

Hi

@sunlei

sunlei commented Aug 25, 2026

Copy link
Copy Markdown
Author

@levlam I agree that the last-message invariant must not be optional. If the proposed option can violate that invariant, then this PR is not the correct implementation.

However, the cold-start behavior we observed with a real user account still seems unresolved:

  • With all three databases disabled, one restart produced 192 FLOOD_WAIT log entries, including 149 messages.getHistory queries with distinct internal query IDs.
  • After re-enabling all three databases, the first restart with an empty database produced flood log entries for 1,142 distinct query IDs, including:
    • 354 messages.getHistory
    • 306 messages.getPeerDialogs
    • 374 channels.getFullChannel
    • 108 stories.getPeerStories

This suggests that the issue is not merely whether last-message loading can be skipped. When TDLib starts without reusable local state, it sends a concentrated fan-out of automatic RPCs. Disabling all databases makes every restart such a cold start.

Could you please clarify the intended support contract?

  1. For a user account, is setting all of the following to false a supported configuration?
    • use_file_database
    • use_chat_info_database
    • use_message_database
  2. If it is supported, how is TDLib expected to restore the mandatory last message and other required state without producing this request fan-out and repeatedly triggering FLOOD_WAIT after restarts?
  3. If this configuration is actually intended only for bot accounts, should that restriction be documented or enforced when setting the parameters?

I accept that this PR is not the correct solution if it breaks the last-message invariant. But the cold-start request amplification still appears to need an upstream solution, for example by persisting bounded essential state when the full message database is disabled, restoring non-immediate state lazily, or coordinating automatic background requests so that they are not fanned out at startup.

The goal is not to make TDLib's state invariants optional. It is to restore them in a way that allows supported database-less or empty-database startup without repeatedly triggering FLOOD_WAIT.

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.

3 participants