telegem 3.3.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +19 -1
- data/README.md +147 -0
- data/bin/telegem-ssl +71 -25
- data/contributing.md +375 -0
- data/docs/api.md +663 -0
- data/docs/bot.md +332 -0
- data/docs/changelog.md +182 -0
- data/docs/context.md +554 -0
- data/docs/core_concepts.md +218 -0
- data/docs/deployment.md +702 -0
- data/docs/error_handling.md +435 -0
- data/docs/examples.md +752 -0
- data/docs/getting_started.md +151 -0
- data/docs/handlers.md +580 -0
- data/docs/keyboards.md +446 -0
- data/docs/middleware.md +536 -0
- data/docs/plugins.md +384 -0
- data/docs/scenes.md +517 -0
- data/docs/sessions.md +544 -0
- data/docs/testing.md +612 -0
- data/docs/troubleshooting.md +574 -0
- data/docs/types.md +538 -0
- data/docs/webhooks.md +456 -0
- data/lib/api/client.rb +38 -10
- data/lib/api/types.rb +129 -106
- data/lib/core/composer.rb +3 -3
- data/lib/core/context.rb +17 -11
- data/lib/plugins/cc +3 -0
- data/lib/plugins/translate.rb +43 -0
- data/lib/session/redis.rb +91 -0
- data/lib/telegem.rb +3 -3
- data/lib/webhook/server.rb +5 -3
- metadata +41 -29
- data/Contributing.md +0 -161
- data/Readme.md +0 -298
- data/bin/telegem-init +0 -32
- data/examples/.gitkeep +0 -0
- data/public/.gitkeep +0 -0
data/docs/sessions.md
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# Session Management
|
|
2
|
+
|
|
3
|
+
Sessions persist data between updates, enabling stateful conversations and user preferences. Telegem supports multiple storage backends with automatic loading and saving.
|
|
4
|
+
|
|
5
|
+
## How Sessions Work
|
|
6
|
+
|
|
7
|
+
- Sessions are user-specific key-value stores
|
|
8
|
+
- Data persists across messages and bot restarts
|
|
9
|
+
- Automatic loading before handlers, saving after
|
|
10
|
+
- TTL (time-to-live) support for automatic cleanup
|
|
11
|
+
|
|
12
|
+
## Basic Usage
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
bot.command('start') do |ctx|
|
|
16
|
+
ctx.session[:visit_count] ||= 0
|
|
17
|
+
ctx.session[:visit_count] += 1
|
|
18
|
+
|
|
19
|
+
ctx.reply("Visit ##{ctx.session[:visit_count]}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
bot.command('set_name') do |ctx|
|
|
23
|
+
name = ctx.command_args
|
|
24
|
+
ctx.session[:name] = name
|
|
25
|
+
ctx.reply("Name set to #{name}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
bot.command('my_name') do |ctx|
|
|
29
|
+
name = ctx.session[:name] || 'unknown'
|
|
30
|
+
ctx.reply("Your name is #{name}")
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Session Storage Backends
|
|
35
|
+
|
|
36
|
+
### Memory Store (Development)
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# Default store
|
|
40
|
+
store = Telegem::Session::MemoryStore.new
|
|
41
|
+
|
|
42
|
+
bot = Telegem.new('TOKEN', session_store: store)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Features:
|
|
46
|
+
- Fast in-memory storage
|
|
47
|
+
- No persistence across restarts
|
|
48
|
+
- Automatic cleanup with TTL
|
|
49
|
+
- Good for development/testing
|
|
50
|
+
|
|
51
|
+
### Redis Store (Production)
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require 'redis'
|
|
55
|
+
|
|
56
|
+
redis = Redis.new(url: ENV['REDIS_URL'])
|
|
57
|
+
store = Telegem::Session::RedisStore.new(redis)
|
|
58
|
+
|
|
59
|
+
bot = Telegem.new('TOKEN', session_store: store)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Features:
|
|
63
|
+
- Persistent across deployments
|
|
64
|
+
- Scalable and fast
|
|
65
|
+
- Automatic serialization
|
|
66
|
+
- Redis clustering support
|
|
67
|
+
|
|
68
|
+
### Custom Store
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
class DatabaseStore
|
|
72
|
+
def initialize(db_connection)
|
|
73
|
+
@db = db_connection
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def get(user_id)
|
|
77
|
+
data = @db.query("SELECT session_data FROM sessions WHERE user_id = ?", user_id)
|
|
78
|
+
data ? JSON.parse(data) : {}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def set(user_id, data)
|
|
82
|
+
json_data = data.to_json
|
|
83
|
+
@db.execute("INSERT OR REPLACE INTO sessions (user_id, session_data) VALUES (?, ?)", user_id, json_data)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
store = DatabaseStore.new(my_db_connection)
|
|
88
|
+
bot = Telegem.new('TOKEN', session_store: store)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Session Configuration
|
|
92
|
+
|
|
93
|
+
### Memory Store Options
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
store = Telegem::Session::MemoryStore.new(
|
|
97
|
+
default_ttl: 3600, # 1 hour default TTL
|
|
98
|
+
cleanup_interval: 300, # Cleanup every 5 minutes
|
|
99
|
+
backup_path: './sessions.json', # Backup file path
|
|
100
|
+
backup_interval: 60 # Backup every minute
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Redis Store Options
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
store = Telegem::Session::RedisStore.new(
|
|
108
|
+
redis: redis_client,
|
|
109
|
+
key_prefix: 'mybot:', # Key prefix
|
|
110
|
+
ttl: 86400 # 24 hours TTL
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Session Data Types
|
|
115
|
+
|
|
116
|
+
Sessions can store any JSON-serializable data:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# Simple values
|
|
120
|
+
ctx.session[:name] = "John"
|
|
121
|
+
ctx.session[:age] = 25
|
|
122
|
+
ctx.session[:active] = true
|
|
123
|
+
|
|
124
|
+
# Complex objects
|
|
125
|
+
ctx.session[:preferences] = {
|
|
126
|
+
theme: 'dark',
|
|
127
|
+
language: 'en',
|
|
128
|
+
notifications: true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Arrays
|
|
132
|
+
ctx.session[:favorites] = ['item1', 'item2']
|
|
133
|
+
|
|
134
|
+
# Dates (as timestamps)
|
|
135
|
+
ctx.session[:last_login] = Time.now.to_i
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Session Operations
|
|
139
|
+
|
|
140
|
+
### Reading Data
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# Safe access with defaults
|
|
144
|
+
name = ctx.session[:name] || 'Anonymous'
|
|
145
|
+
count = ctx.session.fetch(:count, 0)
|
|
146
|
+
|
|
147
|
+
# Check existence
|
|
148
|
+
if ctx.session.key?(:user_data)
|
|
149
|
+
# Data exists
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Get all keys
|
|
153
|
+
keys = ctx.session.keys
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Modifying Data
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# Set values
|
|
160
|
+
ctx.session[:key] = value
|
|
161
|
+
|
|
162
|
+
# Update existing data
|
|
163
|
+
ctx.session[:count] += 1
|
|
164
|
+
ctx.session[:list] << new_item
|
|
165
|
+
|
|
166
|
+
# Delete data
|
|
167
|
+
ctx.session.delete(:temp_key)
|
|
168
|
+
|
|
169
|
+
# Clear all data
|
|
170
|
+
ctx.session.clear
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Atomic Operations
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# Increment with default
|
|
177
|
+
ctx.session[:counter] ||= 0
|
|
178
|
+
ctx.session[:counter] += 1
|
|
179
|
+
|
|
180
|
+
# Array operations
|
|
181
|
+
ctx.session[:items] ||= []
|
|
182
|
+
ctx.session[:items] << 'new_item'
|
|
183
|
+
|
|
184
|
+
# Hash operations
|
|
185
|
+
ctx.session[:user] ||= {}
|
|
186
|
+
ctx.session[:user][:last_seen] = Time.now.to_i
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Session Lifecycle
|
|
190
|
+
|
|
191
|
+
### Automatic Loading
|
|
192
|
+
|
|
193
|
+
Session data loads automatically before each handler:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
bot.use Telegem::Session::Middleware.new(store)
|
|
197
|
+
# Sessions load here
|
|
198
|
+
# Handler executes
|
|
199
|
+
# Sessions save here
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Manual Control
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
# Force save
|
|
206
|
+
ctx.session[:data] = 'value'
|
|
207
|
+
# Automatically saved after handler
|
|
208
|
+
|
|
209
|
+
# Access raw session data
|
|
210
|
+
raw_data = ctx.session.to_h
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## TTL and Expiration
|
|
214
|
+
|
|
215
|
+
### Setting TTL
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
# Per key TTL (memory store)
|
|
219
|
+
ctx.session.set_with_ttl(:temp_data, 'value', ttl: 300) # 5 minutes
|
|
220
|
+
|
|
221
|
+
# Global TTL
|
|
222
|
+
store = Telegem::Session::MemoryStore.new(default_ttl: 3600)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Expiration Handling
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
# Check if key exists and not expired
|
|
229
|
+
if ctx.session.key?(:temp_data)
|
|
230
|
+
# Use data
|
|
231
|
+
else
|
|
232
|
+
# Data expired, handle gracefully
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Session Security
|
|
237
|
+
|
|
238
|
+
### Data Sanitization
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
# Don't store sensitive data
|
|
242
|
+
# BAD
|
|
243
|
+
ctx.session[:password] = user_input
|
|
244
|
+
|
|
245
|
+
# GOOD - store user ID, look up in secure storage
|
|
246
|
+
ctx.session[:user_id] = verified_user_id
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Session Hijacking Protection
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
# Validate user identity
|
|
253
|
+
bot.use do |ctx, next_middleware|
|
|
254
|
+
if ctx.from&.id != ctx.session[:verified_user_id]
|
|
255
|
+
ctx.session.clear # Clear suspicious session
|
|
256
|
+
end
|
|
257
|
+
next_middleware.call(ctx)
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Session Best Practices
|
|
262
|
+
|
|
263
|
+
### Use Appropriate Data Types
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
# Good: store IDs, not objects
|
|
267
|
+
ctx.session[:user_id] = user.id
|
|
268
|
+
ctx.session[:chat_id] = chat.id
|
|
269
|
+
|
|
270
|
+
# Bad: store large objects
|
|
271
|
+
ctx.session[:user_object] = user # Large, changes frequently
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Implement Cleanup
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
# Clean up old data
|
|
278
|
+
bot.command('logout') do |ctx|
|
|
279
|
+
ctx.session.clear
|
|
280
|
+
ctx.reply("Logged out")
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Periodic cleanup for memory store
|
|
284
|
+
store = Telegem::Session::MemoryStore.new(
|
|
285
|
+
default_ttl: 86400, # 24 hours
|
|
286
|
+
cleanup_interval: 3600 # Cleanup hourly
|
|
287
|
+
)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Handle Session Errors
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
bot.use do |ctx, next_middleware|
|
|
294
|
+
begin
|
|
295
|
+
next_middleware.call(ctx)
|
|
296
|
+
rescue => e
|
|
297
|
+
ctx.logger.error("Session error: #{e.message}")
|
|
298
|
+
# Continue without session
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Monitor Session Usage
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
# Log session size
|
|
307
|
+
bot.use do |ctx, next_middleware|
|
|
308
|
+
next_middleware.call(ctx)
|
|
309
|
+
|
|
310
|
+
if ctx.session.size > 100
|
|
311
|
+
ctx.logger.warn("Large session for user #{ctx.from&.id}: #{ctx.session.size} keys")
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Advanced Session Patterns
|
|
317
|
+
|
|
318
|
+
### User Preferences
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
def get_user_preference(ctx, key, default = nil)
|
|
322
|
+
ctx.session[:preferences] ||= {}
|
|
323
|
+
ctx.session[:preferences][key] || default
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def set_user_preference(ctx, key, value)
|
|
327
|
+
ctx.session[:preferences] ||= {}
|
|
328
|
+
ctx.session[:preferences][key] = value
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
bot.command('set_theme') do |ctx|
|
|
332
|
+
theme = ctx.command_args
|
|
333
|
+
set_user_preference(ctx, :theme, theme)
|
|
334
|
+
ctx.reply("Theme set to #{theme}")
|
|
335
|
+
end
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Conversation State
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
bot.command('quiz') do |ctx|
|
|
342
|
+
ctx.session[:quiz] = {
|
|
343
|
+
active: true,
|
|
344
|
+
question: 1,
|
|
345
|
+
score: 0,
|
|
346
|
+
answers: []
|
|
347
|
+
}
|
|
348
|
+
ctx.reply("Quiz started! Question 1...")
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
bot.hears(/.+/) do |ctx|
|
|
352
|
+
quiz = ctx.session[:quiz]
|
|
353
|
+
if quiz&.[](:active)
|
|
354
|
+
# Process quiz answer
|
|
355
|
+
process_quiz_answer(ctx, quiz, ctx.message.text)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Rate Limiting
|
|
361
|
+
|
|
362
|
+
```ruby
|
|
363
|
+
bot.use do |ctx, next_middleware|
|
|
364
|
+
user_id = ctx.from&.id
|
|
365
|
+
return next_middleware.call(ctx) unless user_id
|
|
366
|
+
|
|
367
|
+
key = "rate_limit:#{user_id}"
|
|
368
|
+
ctx.session[key] ||= { count: 0, window_start: Time.now.to_i }
|
|
369
|
+
|
|
370
|
+
window_size = 60 # 1 minute
|
|
371
|
+
max_requests = 10
|
|
372
|
+
|
|
373
|
+
now = Time.now.to_i
|
|
374
|
+
rate_data = ctx.session[key]
|
|
375
|
+
|
|
376
|
+
# Reset window if needed
|
|
377
|
+
if now - rate_data[:window_start] > window_size
|
|
378
|
+
rate_data[:count] = 0
|
|
379
|
+
rate_data[:window_start] = now
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
if rate_data[:count] >= max_requests
|
|
383
|
+
ctx.reply("Rate limit exceeded")
|
|
384
|
+
return
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
rate_data[:count] += 1
|
|
388
|
+
next_middleware.call(ctx)
|
|
389
|
+
end
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Session Migration
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
# Migrate old session format
|
|
396
|
+
bot.use do |ctx, next_middleware|
|
|
397
|
+
if ctx.session[:old_format]
|
|
398
|
+
# Migrate data
|
|
399
|
+
ctx.session[:new_format] = transform_old_data(ctx.session[:old_format])
|
|
400
|
+
ctx.session.delete(:old_format)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
next_middleware.call(ctx)
|
|
404
|
+
end
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Session Storage Implementations
|
|
408
|
+
|
|
409
|
+
### File-Based Store
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
class FileStore
|
|
413
|
+
def initialize(file_path)
|
|
414
|
+
@file_path = file_path
|
|
415
|
+
@data = load_data
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def get(user_id)
|
|
419
|
+
@data[user_id.to_s] || {}
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def set(user_id, data)
|
|
423
|
+
@data[user_id.to_s] = data
|
|
424
|
+
save_data
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
private
|
|
428
|
+
|
|
429
|
+
def load_data
|
|
430
|
+
File.exist?(@file_path) ? JSON.parse(File.read(@file_path)) : {}
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def save_data
|
|
434
|
+
File.write(@file_path, @data.to_json)
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Database Store
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
class PostgresStore
|
|
443
|
+
def initialize(connection_string)
|
|
444
|
+
@db = PG.connect(connection_string)
|
|
445
|
+
create_table
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def get(user_id)
|
|
449
|
+
result = @db.exec_params("SELECT data FROM sessions WHERE user_id = $1", [user_id])
|
|
450
|
+
result.any? ? JSON.parse(result[0]['data']) : {}
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def set(user_id, data)
|
|
454
|
+
json_data = data.to_json
|
|
455
|
+
@db.exec_params(
|
|
456
|
+
"INSERT INTO sessions (user_id, data) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE SET data = $2",
|
|
457
|
+
[user_id, json_data]
|
|
458
|
+
)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
private
|
|
462
|
+
|
|
463
|
+
def create_table
|
|
464
|
+
@db.exec(%q{
|
|
465
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
466
|
+
user_id BIGINT PRIMARY KEY,
|
|
467
|
+
data JSONB NOT NULL,
|
|
468
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
469
|
+
)
|
|
470
|
+
})
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Session Monitoring and Debugging
|
|
476
|
+
|
|
477
|
+
### Session Inspector
|
|
478
|
+
|
|
479
|
+
```ruby
|
|
480
|
+
bot.command('debug_session') do |ctx|
|
|
481
|
+
session_data = ctx.session.to_h
|
|
482
|
+
ctx.reply("Session keys: #{session_data.keys.join(', ')}")
|
|
483
|
+
ctx.reply("Session size: #{session_data.to_json.size} bytes")
|
|
484
|
+
end
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Session Analytics
|
|
488
|
+
|
|
489
|
+
```ruby
|
|
490
|
+
class SessionAnalyticsMiddleware
|
|
491
|
+
def initialize
|
|
492
|
+
@stats = {}
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def call(ctx, next_middleware)
|
|
496
|
+
user_id = ctx.from&.id
|
|
497
|
+
return next_middleware.call(ctx) unless user_id
|
|
498
|
+
|
|
499
|
+
@stats[user_id] ||= { messages: 0, session_size: 0 }
|
|
500
|
+
@stats[user_id][:messages] += 1
|
|
501
|
+
@stats[user_id][:session_size] = ctx.session.size
|
|
502
|
+
|
|
503
|
+
next_middleware.call(ctx)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def report
|
|
507
|
+
total_users = @stats.size
|
|
508
|
+
total_messages = @stats.values.sum { |s| s[:messages] }
|
|
509
|
+
avg_session_size = @stats.values.sum { |s| s[:session_size] } / total_users.to_f
|
|
510
|
+
|
|
511
|
+
puts "Session Analytics:"
|
|
512
|
+
puts "Total users: #{total_users}"
|
|
513
|
+
puts "Total messages: #{total_messages}"
|
|
514
|
+
puts "Avg session size: #{avg_session_size.round(2)}"
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Performance Considerations
|
|
520
|
+
|
|
521
|
+
### Memory Usage
|
|
522
|
+
|
|
523
|
+
- Large sessions increase memory usage
|
|
524
|
+
- Use TTL to automatically clean up
|
|
525
|
+
- Monitor session sizes in production
|
|
526
|
+
|
|
527
|
+
### Database Performance
|
|
528
|
+
|
|
529
|
+
- Index user_id in database stores
|
|
530
|
+
- Use connection pooling for database stores
|
|
531
|
+
- Consider caching frequently accessed data
|
|
532
|
+
|
|
533
|
+
### Redis Optimization
|
|
534
|
+
|
|
535
|
+
```ruby
|
|
536
|
+
# Use Redis pipelines for bulk operations
|
|
537
|
+
redis.pipelined do
|
|
538
|
+
redis.set("session:#{user_id}", data.to_json)
|
|
539
|
+
redis.expire("session:#{user_id}", ttl)
|
|
540
|
+
end
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Sessions are essential for creating stateful, personalized bot experiences. Choose the right storage backend and use sessions wisely to maintain good performance.</content>
|
|
544
|
+
<parameter name="filePath">/home/slick/telegem/docs/sessions.md
|