-
Notifications
You must be signed in to change notification settings - Fork 908
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Considerable delay to enter to WizardScene after adding '@telegraf/session/mongodb' #1914
Comments
When an update comes in, session is trying to fetch the latest session from MongoDB. If the database is located far from the bot, or is running on poor hardware, it can respond slowly, causing session to wait for it. You can investigate this by enabling debug logs. On Linux/Unixy shells, you can do: DEBUG=telegraf:* node bot.js On Windows, cmd.exe: set DEBUG=telegraf:*
node bot.js Powershell: $env:DEBUG = 'telegraf:*'
node bot.js The logs should give you an idea for where it's spending most of its time waiting. |
Thanks for the fast answer but I run the same code right now with the long polling mode and the bot run as expected. |
TLDR: webhookReply is very problematic, and we recommend leaving it off. webhookReply is a minor but problematic optimisation. In webhook mode, Bot API sends you an update and you can send one operation (which would normally be a separate API request you make, like sendMessage or answerCallbackQuery) back in the response body. That saves you from making one API call of your own, for example to call sendMessage. But because you're sending the request in the response body, you cannot know whether it was successful, and there will be no return value. Telegraf only uses webhookReply for a handful of API methods, particularly those that do not have a response body. With webhookReply,
But most importantly, the first API call while handling a webhook update will cause that update to be marked as handled. This is problematic because in webhook mode, Telegram sends one update per chat and waits for us to finish processing before sending the next update. An early webhookReply will mean Telegram considers that request handled and sends the next update. This may or may not be preferable. So in any case, we do not recommend using webhookReply. The feature exists for completion, but it causes many an unexpected behaviour. |
Thanks for the detailed explanation @MKRhere. Don't you have any idea why could this be? I mean, the undesired behavior I am getting... One question, even though I am pretty sure my problem is not related to the mongodb session because I execute the same code in long polling mode and have no problem, I couldnt set the debug as you explained me. I am using nodemon and have a dev script to launch the bot in development mode, how could I do this? |
Just set it in the shell, like |
Are you able to run webhook mode locally? Since you mention it only happens in webhook mode. |
No I am not, I can only run webhook inside AWS lambda. But I can execute the bot in local environment using mongodb session and I have no problem, it works as expected and I can even check the mongodb collection. Maybe it could help if you have any example of a telegraf bot written in typescript which uses webhook and wizard scenes |
@MKRhere I just run the bot in local mode using long polling mode with the debug enabled and as I told you everything works fine
I mean, definitely there is n problem when using the mongodb session, but in some way it doesn't work correctly when I combine it with webhooks inside lambda Is there any way to enable this debug for lambda functions? |
Install the debug package, then in the start of your bot: import debug from "debug";
debug.enable("telegraf:*"); You can also set the env var in AWS Lambda's settings. That will also work. |
I already found the problem (but still don't know how to solve it)
As you can see, comparing it to the logs received in long polling mode, it doesn't wait for it to update cache
Definitely there is something related to my code that is not waiting for the promise to be resolved Take a look at my code: wizardsStage.hears(UserInteractionConstants.CURRENCY_QUERY_ACTION, async ctx => {
const customState: BotState = (ctx as any).customState
if (customState.isAdmin) {
ctx.reply('Consultar divisas', CurrencyQueryKeyboard)
} else if (customState.isSalesman) {
ctx.scene.enter(SceneIDS.QUERY_CURRENCIES_SALESMAN)
}
}) Maybe I should return the promises or something for it to wait for them |
The difference of 10 seconds between those logs for the same update definitely means it is waiting 10s for the database query. There's nothing your code could influence this. For some reason the database request is just extremely slow. When you test this locally, you're connected to the same database? |
My bad, I was not explicit enough, as you said there is a 10 seconds difference but this is because it is what I printed, I mean, I waited some time to make a New request and when I make another random request it printed that in the logs. |
But they have the same update_id (in parens), so they belong to the same update. |
So then, just to rule out something weird with Lambda, you should try running webhook mode elsewhere, in a VPS or preferably locally. |
Ok, I will run it inside an ec2 instance and will let you know, but tomorrrow I would like to see if this could help me (#567) |
I don't think it's related. |
You are right, but I was reading this (#1786) right now and I am pretty sure it is related, Actually I think it is some problem related to the way lambda functions work. It is a shame that this one does not have a solution either. |
I just realised webhookReply is set to true by default. That's a bad default, and you should set new Telegraf(token, { telegram: { webhookReply: false } }); It should be defaulted to false in v5. I'm not sure this will help in your case, but might as well try. What I expect with WHR is that the first Bot API call you make causes AWS Lambda to terminate the request, since it's marked as handled. Database calls should not count. |
Thanks but in the first comment I shared my bot initialization code and there is webhookReply set to false! |
In that case, the linked issue is not relevant. There is no reason to believe AWS Lambda will terminate a request after a database query. If I stretch my imagination, I could consider that AWS Lambda will sleep the isolate and wake it back up when a response was received, which is consistent with the observation, but it is honestly a far stretch. Let us eliminate other possibilities by testing an identical bot on EC2 first. |
I just found the problem and a solution even thought it is not a good one @MKRhere export const message = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
const body = JSON.parse(event.body!)
console.log(body)
initBot()
await webhookHandle(body)
return { body: JSON.stringify({ message: 'Ok' }), statusCode: 200 }
} catch (error) {
return { body: JSON.stringify({ message: 'Error' }), statusCode: 500 }
}
} webhookHandle function: const webhookHandle = (update: Update) => {
return bot.handleUpdate(update)
} My problem basically is that I don´t know why but the webhook handle function is not waiting for the bot to completely handle the update I added this to my code and everything works perfectly now: console.log('Finished webhook handle')
await sleep(500) I added this above the return statement inside the handler function and everything worked now. But it is not an elegant solution and I added 500 ms of billing time to every request, so the question is: Why do you think it is not waiting for the bot to handle the update? Let me show you my initBot function const initBot = () => {
const config = { telegram: { webhookReply: false } }
bot = new Telegraf<BotContext>(process.env.BOT_TOKEN, config)
const wizardsStage = new Scenes.Stage<any>([])
const store = Mongo({
url: process.env.DATABASE_URL,
database: process.env.SESSION_DATABASE_NAME,
})
bot.use(checkUserType)
bot.use(allowedUsers)
bot.start(ctx => {
console.log('start bot')
return ctx.reply('Menú Principal', ctx.customState.isSalesman ? SalesmanMainmenuKeyboard : MainmenuKeyboard)
})
bot.hears(UserInteractionConstants.MAIN_MENU, ctx => {
return ctx.reply('Menú Principal', ctx.customState.isSalesman ? SalesmanMainmenuKeyboard : MainmenuKeyboard)
})
initBrandsRoutes(bot, wizardsStage)
initModulesRoutes(bot, wizardsStage)
initInventoriesRoutes(bot, wizardsStage)
initProductsRoutes(bot, wizardsStage)
initCurrenciesRoutes(bot, wizardsStage)
initSalesRoutes(bot, wizardsStage)
initUsersRoutes(bot, wizardsStage)
initPaymentsRoutes(bot, wizardsStage)
initDoubtsRoutes(bot, wizardsStage)
initSettingsRoutes(bot, wizardsStage)
bot.use(session({ store }))
bot.use(wizardsStage.middleware())
} Now inside initCurrenciesRoute function (this is just an example of how it is implemented all routes for each entity) export const initCurrenciesRoutes = (bot: Telegraf<BotContext>, wizardsStage: WizardsStageType) => {
registerScenes(wizardsStage)
bot.hears(UserInteractionConstants.CURRENCIES_MENU, ctx => {
ctx.reply('Menú divisas', ctx.customState.isAdmin ? CurrencyActionsKeyboard : SalesmanCurrencyActionsKeyboard)
})
bot.hears(UserInteractionConstants.CURRENCY_INCREASE_ACTION, ctx => {
ctx.reply('Incrementar divisas', CurrencyIncreaseKeyboard)
})
bot.hears(UserInteractionConstants.CURRENCY_DECREASE_ACTION, ctx => {
ctx.reply('Decrementar divisas', CurrencyDecreaseKeyboard)
})
bot.hears(UserInteractionConstants.CURRENCY_EXCHANGE_ACTION, ctx => {
ctx.reply('Intercambiar divisas', CurrencyExchangeKeyboard)
})
wizardsStage.hears(UserInteractionConstants.CURRENCY_QUERY_ACTION, async ctx => {
console.log(`Inside ${UserInteractionConstants.CURRENCY_QUERY_ACTION}`);
const customState: BotState = (ctx as any).customState
if (customState.isAdmin) {
ctx.reply('Consultar divisas', CurrencyQueryKeyboard)
} else if (customState.isSalesman) {
ctx.scene.enter(SceneIDS.QUERY_CURRENCIES_SALESMAN)
}
})
bot.command('query_currencies', ctx => {
const customState: BotState = (ctx as any).customState
if (customState.isAdmin) {
ctx.reply('Consultar divisas', CurrencyQueryKeyboard)
} else if (customState.isSalesman) {
ctx.scene.enter(SceneIDS.QUERY_CURRENCIES_SALESMAN)
}
})
} My question is, according to this code is there something I am missing that makes this to happen? |
Any thought about this? |
telegraf/session#5 |
Context
I used to have a telegram bot using long polling mode inside an EC2 instance and everything worked well. I decided to run this bot in webhook mode using AWS lambda service.
In this project I have a lot of WizardScenes and the first problem I had was that after getting into the first step of the WizardScene it didn't advance to the following step. I solved this problem by using '@telegraf/session/mongodb'.
After this, I can complete a Wizard Scene but the bot become very unresponsive and unreliable every time a wizard stage listen for the event to enter a WizardScene, I mean, it just doesn't answer or answer after I sent him two other actions for example.
I just need to execute my bot in webhook mode and that it works as it used to do in long polling mode, right now the only problem I had is related to the WizardScenes
Minimal Example Code Reproducing the Issue
Bot initialization
Lisening to events
Expected Behavior
Screencast.from.2023-12-19.14-25-29.webm
Current Behavior
Screencast.from.2023-12-19.14-27-24.webm
The text was updated successfully, but these errors were encountered: