MongoDB + Python: From Basic CRUD to Advanced Aggregation with PyMongo

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Why are MongoDB and Python a “Perfect Match”?

If you’re tired of having to run database migrations every time your application logic changes, MongoDB is your savior. With its flexible Document structure, it allows you to store JSON-like data without declaring a rigid schema. When combined with Python—a language famous for its minimalist syntax—data processing becomes smoother than ever.

Based on my experience implementing many projects, PyMongo is the most reliable driver. It is not only stable but also provides full support for the latest MongoDB features. This article dives straight into practical techniques, from basic operations to optimizing queries for millions of records.

Quick Environment Setup

First, ensure you have a MongoDB instance (Docker or MongoDB Atlas). Install the PyMongo library using the following command:

pip install "pymongo[srv]"

Pro tip: Using "pymongo[srv]" will automatically install the necessary dependencies for you to connect to a Cloud cluster (Atlas) without worrying about DNS errors.

Implementing Connections and Data Handling

1. Setting Up a Standard Connection Pool

Don’t open connections haphazardly. Instead, encapsulate it in a function to reuse the connection pool, which helps save server resources.

from pymongo import MongoClient
from pymongo.errors import ConnectionFailure

def get_database(uri="mongodb://localhost:27017/"):
    try:
        # Timeout after 5 seconds if connection fails
        client = MongoClient(uri, serverSelectionTimeoutMS=5000)
        client.admin.command('ping')
        return client['dev_database']
    except ConnectionFailure:
        print("Server not responding, check your config!")
        return None

db = get_database()

2. CRUD: Don’t Let Performance Bottleneck

Working with PyMongo is very similar to working with a dict in Python. However, performance lies in how you call the functions.

Insert (Adding data):

collection = db['users']

# Amateur way: Inserting each record in a loop
# Professional way: Use insert_many
users = [
    {"name": "Hoang", "role": "DevOps", "skills": ["Python", "Docker"]},
    {"name": "An", "role": "Data", "skills": ["SQL", "Python"]}
]
result = collection.insert_many(users)
print(f"Inserted {len(result.inserted_ids)} records.")

Real-world numbers: In a test case inserting 10,000 records, using insert_one takes about 15-20 seconds. Conversely, insert_many takes less than 1 second. Always prioritize batch processing.

Smart Update: Instead of overwriting the entire document, use the $set or $push operators to update only the necessary fields.

collection.update_one(
    {"name": "Hoang"},
    {"$push": {"skills": "MongoDB"}}
)

3. Aggregation Pipeline: Process Data Like a Pro

Aggregation is MongoDB’s most powerful tool. It allows you to calculate, filter, and transform data directly at the database level. You don’t need to pull gigabytes of data back to Python to process with Pandas, which significantly reduces bandwidth usage.

Example: Statistics on the number of experts per skill.

pipeline = [
    {"$unwind": "$skills"}, 
    {"$group": {
        "_id": "$skills",
        "total": {"$sum": 1}
    }},
    {"$sort": {"total": -1}}
]

for stat in collection.aggregate(pipeline):
    print(f"{stat['_id']}: {stat['total']} people")

4. Index Optimization Tips

Many systems experience CPU spikes simply due to missing indexes. When a collection exceeds 1 million records, a query without an index will force the database to scan the entire hard drive.

# Create an index for frequently searched fields
collection.create_index([("role", 1)])

Additionally, use Projection. If you only need to retrieve Emails, don’t force MongoDB to return the entire heavy Bio array. Compare: find({}, {"email": 1}) will be 30-50% faster than a standard find({}).

Monitoring and Control

How do you know if your query is running optimally? Use the explain() command. It will break down the details of how MongoDB executes the statement.

stats = collection.find({"role": "DevOps"}).explain()
print(stats['executionStats']['nReturned']) # Number of records found
print(stats['executionStats']['totalDocsExamined']) # Number of records scanned

If totalDocsExamined is many times larger than nReturned, that’s when you need to review your Index immediately. Don’t wait until the system reports a 500 error in the middle of the night to start optimizing. Happy building powerful backend systems!

Share: