I've had the "SQL versus NoSQL" argument in enough Slack threads that I finally decided to settle it for myself rather than repeat opinions I'd absorbed secondhand. I built the same feature, a content management system storing articles with flexible, author-defined custom fields, twice, once against Postgres and once against MongoDB, and ran both in parallel against real traffic on a side project for three months.
Both databases ran the same application logic, the same API layer, and the same load, with a feature flag deciding which database handled a given request during the test period.
The custom fields feature, where each author could define their own arbitrary metadata fields per article, mapped naturally onto MongoDB's document model, each article document simply included whatever fields that author had defined, with zero schema migration required when a new field type showed up. Building the equivalent in Postgres meant either a sparse table with dozens of nullable columns, which felt genuinely wrong, or a JSONB column holding the flexible fields, which is what I actually landed on.
Once I started storing custom fields in a JSONB column with GIN indexing for queryable fields, the practical flexibility gap between the two databases narrowed considerably more than I expected going in. Querying custom fields in Postgres via JSONB operators was slightly more verbose than MongoDB's native document queries, but performance was close enough that this stopped feeling like a meaningful differentiator.
-- Postgres JSONB query for a custom field
SELECT * FROM articles
WHERE custom_fields @> '{"category": "opinion"}'
AND author_id = 42;
CREATE INDEX idx_articles_custom_fields
ON articles USING GIN (custom_fields);
Partway through the test period, I found a data integrity bug on the MongoDB side that simply couldn't have happened on the Postgres side, a handful of articles referencing an author ID that had been deleted, leaving orphaned documents with no valid author. Postgres's foreign key constraints would have rejected that deletion outright at the database level. MongoDB let it happen silently, and I only caught it because a page crashed trying to render a null author's name.
Building an analytics dashboard showing article counts grouped by author and month, Postgres's GROUP BY with standard SQL aggregation ran noticeably faster and was significantly easier to write correctly than MongoDB's aggregation pipeline syntax, which took me considerably longer to get right and ran measurably slower on the same dataset size, around 40 percent slower on our roughly 80,000-document test set.
SELECT author_id, DATE_TRUNC('month', created_at) AS month, COUNT(*)
FROM articles
GROUP BY author_id, month
ORDER BY month DESC;
Under a simulated burst of concurrent article creation, roughly two hundred requests per second sustained for a few minutes, MongoDB handled the raw write throughput slightly better than Postgres out of the box, though the gap closed considerably once I tuned Postgres's connection pooling settings, which I hadn't bothered doing initially and which made a real difference once I did.
Every team member on this project already knew SQL well from other projects, and debugging a slow Postgres query using EXPLAIN ANALYZE was something everyone could do independently. MongoDB's query planning tools required a genuinely separate learning investment that, on a small team, meant I became the sole person capable of debugging performance issues on that side of the test.
For this specific project, I moved forward with Postgres, not because MongoDB performed badly, it didn't, but because the JSONB column closed most of the flexibility gap I originally thought would be decisive, while relational integrity and team-wide SQL familiarity turned out to matter more in practice than I'd predicted before actually running the comparison. I don't think this settles the argument generally, our project's specific shape favored Postgres, and I'd genuinely expect a different result for a project with less structured, more genuinely document-shaped data throughout.