Python Motor创建TTL索引后文档未按过期时间自动删除的问题
Let's break down why your test might not be behaving as expected, and fix it step by step.
First, here's your test code formatted for clarity:
import asyncio from datetime import datetime, timedelta import motor.motor_asyncio async def test_expire_at(aiohttp_client, mongo_collection): await mongo_collection.delete_many({}) await mongo_collection.create_index([("expireAt", 1)], expireAfterSeconds=0) expire = datetime.utcnow() + timedelta(seconds=5) await mongo_collection.update_one( {'key': '123'}, { "$set": { 'key': '123', 'data': {}, 'expireAt': expire } }, upsert=True ) await asyncio.sleep(10) count = await mongo_collection.count() assert count == 0
Common Issues & Fixes
1. MongoDB's TTL Index Scanning Delay
The most likely culprit here is MongoDB's background TTL monitor: it scans for expired documents every 60 seconds by default. Even though your document expires after 5 seconds, the background thread won't pick it up immediately—waiting 10 seconds isn't long enough for the first scan to run.
To fix this in a test environment, you can manually trigger a TTL scan right after your sleep:
# Add this line after await asyncio.sleep(10) await mongo_collection.database.command({"delete": mongo_collection.name, "expire": True})
Alternatively, if you have admin access, you can shorten the monitor's interval at the start of your test suite to make scans run more frequently:
# Run once before your tests await mongo_collection.database.admin_command({"setParameter": 1, "ttlMonitorSleepSecs": 1})
2. Deprecated count() Method
Motor's count() method is deprecated and may return approximate counts in some scenarios. Replace it with count_documents({}) for an accurate, up-to-date count:
count = await mongo_collection.count_documents({})
3. Verify Index & Field Type
Even though you're awaiting create_index, it's wise to confirm the index was created correctly, and that your expireAt field is stored as a BSON Date (not a string or other type):
# After creating the index indexes = await mongo_collection.index_information() assert "expireAt_1" in indexes assert indexes["expireAt_1"]["expireAfterSeconds"] == 0 # After upserting the document doc = await mongo_collection.find_one({"key": "123"}) assert isinstance(doc["expireAt"], datetime) # Motor converts BSON Date to Python datetime
4. UTC Time Consistency
You're already using datetime.utcnow() which is correct (MongoDB uses UTC for TTL calculations), but double-check that your MongoDB server's system clock is synchronized to UTC to avoid time mismatches.
Modified Test Case
Here's the updated test incorporating all these fixes:
import asyncio from datetime import datetime, timedelta import motor.motor_asyncio async def test_expire_at(aiohttp_client, mongo_collection): await mongo_collection.delete_many({}) # Create and verify TTL index await mongo_collection.create_index([("expireAt", 1)], expireAfterSeconds=0) indexes = await mongo_collection.index_information() assert "expireAt_1" in indexes assert indexes["expireAt_1"]["expireAfterSeconds"] == 0 expire = datetime.utcnow() + timedelta(seconds=5) await mongo_collection.update_one( {'key': '123'}, { "$set": { 'key': '123', 'data': {}, 'expireAt': expire } }, upsert=True ) # Confirm the document has a valid Date field doc = await mongo_collection.find_one({"key": "123"}) assert isinstance(doc["expireAt"], datetime) await asyncio.sleep(10) # Force immediate TTL cleanup await mongo_collection.database.command({"delete": mongo_collection.name, "expire": True}) # Get accurate document count count = await mongo_collection.count_documents({}) assert count == 0
Final Notes
The delete command with expire: True works for MongoDB 4.0+. If you're using an older version, stick to shortening the TTL monitor interval instead.
内容的提问来源于stack exchange,提问作者Alexander

