惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
V
V2EX
博客园 - 【当耐特】
Vercel News
Vercel News
雷峰网
雷峰网
爱范儿
爱范儿
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
F
Full Disclosure
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog
NISL@THU
NISL@THU
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
P
Proofpoint News Feed
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
K
Kaspersky official blog
I
InfoQ
Google Online Security Blog
Google Online Security Blog
L
LINUX DO - 最新话题
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
V
Visual Studio Blog
AI
AI
Schneier on Security
Schneier on Security
B
Blog RSS Feed
T
Tor Project blog
H
Help Net Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LINUX DO - 热门话题
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
T
Threat Research - Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Cyber Attacks, Cyber Crime and Cyber Security
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
V2EX - 技术
V2EX - 技术
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
Arctic Wolf
Webroot Blog
Webroot Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main

blag

SQLite prefixes its temp files with `etilqs_` - blag Setsum - order agnostic, additive, subtractive checksum - blag Oldest recorded transaction - blag Replacing a cache service with a database - blag SQLite commits are not durable under default settings - blag PSA: SQLite WAL checksums fail silently and may lose data - blag Rickrolling Turso DB (SQLite rewrite in Rust) - blag Collection of insane and fun facts about SQLite - blag How bloom filters made SQLite 10x faster - blag In search of a faster SQLite - blag Galloping Search - blag Building a distributed log using S3 (under 150 lines of Go) - blag Zero Disk Architecture - blag PSA: Most databases do not do checksums by default - blag PSA: SQLite does not do checksums - blag Disaggregated Storage - a brief introduction - blag Why does SQLite (in production) have such a bad rep? - blag SQLite Slaps - blag Now - blag Learning C - blag Snapshot Testing - blag Win: contribution to libSQL (SQLite) codebase - blag Errata in Hekaton MVCC paper - blag Internet is wholesome: MVCC edition - blag It is becoming difficult for me to be productive in Python - blag MongoDB secondary only index - blag Introducing CaskDB – a project to teach you writing a key-value store - blag Recurse Center: Winter Break - blag Recurse Center Day 24: Hacking Go compiler to add a new keyword - blag Recurse Center Day 20: Django v4 upgrade (from v1) - blag Recurse Center Day 19 - blag Recurse Center Day 18 - blag Recurse Center Day 17 - blag Recurse Center Day 16: Open Source - blag Recurse Center Day 15: B Tree Algorithms - blag Recurse Center Day 14: NoSQL Transactions - blag Recurse Center Day 13: Why 'Raft'? - blag Recurse Center Day 12: Isolation Anomalies - blag Recurse Center Day 11: B Tree Insertions - blag Recurse Center Day 10: Learning Distributed Systems - blag Recurse Center Day 9: Papers We Love - blag Recurse Center Day 8: B Tree Fill Factor (Part 2) - blag Recurse Center Day 7: Basics of ncurses - blag Recurse Center Day 6: B Tree Root - blag Recurse Center First Week - blag Recurse Center Day 5: Garbage Collection Algorithms - blag Recurse Center Day 4: B Tree fill factor - blag Recurse Center Day 3: Hammock Driven Development - blag Recurse Center Day 2: BTree Node - blag Recurse Center Day 1: init - blag What I want to do at Recurse Center - blag Accepted to the Recurse Center! - blag Towards Inserting One Billion Rows in SQLite Under A Minute - blag Marshaling Struct with Special Fields to JSON in Golang - blag Setting up Github Actions for Hugo - blag Moving to Hugo - blag Catching SIGTERM in Python - blag Git/Github fork-pull request-update cycle - blag Using uWSGI with Python 3 - blag When is my Cake Day? - blag Staying Ahead of Amazon, in Amazon Treasure Hunt Contest - blag How I Am Maintaining Multiple Emails For Git On A Same Machine - blag An exploit on Gaana.com gave me access to their entire User Database - blag Flashing Asus-WRT Merlin by XVortex on NetGear NightHawk R7000 - blag Install Windows 8 UEFI on Legacy BIOS with Clover (and Dual boot with Yosemite) - blag Scraping Javascript page using Python - blag Installing Transmission (remote and CLI) client on Raspberry Pi - blag About - blag Projects - blag
I ended up adding duplicate records on a unique index in MongoDB - blag
2021-03-31 · via blag

Note: The intention of this post is not to shit on MongoDB. They specifically forbid the steps I am about to explain. This post is a chronicle of my curiosity, exploration, and fun learning experience. If you still find it interesting, then continue to read on (:

In MongoDB (or generally in any database), the following rules apply when it comes to the unique indexes:

  1. You cannot add duplicate records which violate the unique constraints
  2. If there is a collection that has duplicate records, then you cannot add a unique index to the collection unless you delete those dupes first

Yet, I ended up breaking these set rules and bringing my MongoDB in a weird state in which:

  1. The secondary was replicating from the primary with data which conflicted the unique index constraints (breaking the rule 1)
  2. I was able to have a unique index with data that conflicted the unique constraints (breaking the rule 2)

The Problem

I have a three node cluster, with a primary and two secondaries. If you want to create an index on a large collection, MongoDB docs suggest you do something called Rolling Index Builds:

  1. Remove a member from the replica set
  2. Create the index
  3. Add the follower back to the replica set
  4. Repeat for all the followers
  5. Make the primary to step down and let some other followers become the primary
  6. Then create the index on the last remaining follower (the earlier primary)

However, these steps change when you want to create a unique index. The Mongo docs specifically highlight this:

To create unique indexes using the following procedure, you must stop all writes to the collection during this procedure.

If you cannot stop all writes to the collection during this procedure, do not use the procedure on this page. Instead, build your unique index on the collection by issuing db.collection.createIndex() on the primary for a replica set.

Mongo has two modes of creating indexes:

  1. Foreground: This uses more efficient data structures, but this blocks all read, write operations on the collection while the index is being built. This is not good if you don’t want to have downtime. There are ways to speed this process up, but Mongo index creation process is single threaded. Ouch.
  2. Background: This runs in the background, but uses an inefficient index data structure and also, this is very very slow. No way to speed this up.

I measured and both of them were terrible. Foreground mode took about 3 hours (with the maximum RAM I could add in Google Cloud). Background indexing took about 18 hours on a single replica. The constraints apply only when the index is fully built. During the index creation if dupes get inserted, then the whole index process will be aborted and needs to be restarted again. (The older version of Mongo had a nice option of specifying dropping the dupes during index creation.)

I disliked both and I was tempted to try the Rolling Index builds for the unique indexes, the very thing which Mongo docs forbade. The docs said ‘do not do this’, but did not say what would happen if someone did it. Of course, I had to take one for the team and try it.

The (Anti) Solution

I diligently followed the instructions given here and took out one of the followers. This was simple, I had to change the mongod config (usually located at /etc/mongod.conf):

net:
   bindIp: localhost
   port: 27217
#   port: 27017
#replication:
#   replSetName: myRepl
setParameter:
   disableLogicalSessionCacheRefresh: true
// snipped conf file

Then I built an unique index on a collection:

> db.records.createIndex({user: 1}, {unique: true})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}

While the index was being built, I inserted a duplicate document in the primary. Since primary did not have any indexes, it happily obliged to my commands:

rs0:PRIMARY> db.records.insert({user: "avi"})
WriteResult({ "nInserted" : 1 })

I undid the conf file changes in the secondary and added it back to the replica set, crossed my fingers, and asked my friends to guess what would happen:

  1. The new follower would be rejected because it has an index which primary isn’t aware of
  2. The unrecognised index would be dropped silently from the secondary
  3. The replication will be failing in a loop whenever it encountered the duplicate record from the primary
  4. The replication would complete successfully, dropping the duplicate records silently

But, as you have guessed already, none of these things happened. The follower was welcomed into the replica set and replication completed successfully. I first checked the primary, if there were any new indexes. Rightly so, there were none. I checked the secondary if the unique index had gotten dropped. But nope.

Then the following blew my mind:

rs0:SECONDARY> db.records.getIndexes()
{ "v" : 2, "unique" : true, "key" : { "user" : 1 }, "name" : "user_1" }
// snipped output
rs0:SECONDARY> db.records.find()
{ "_id" : ObjectId("6066e0f278d5455b08f3920b"), "user" : "avi" }
{ "_id" : ObjectId("6066e80385a02d98945e380e"), "user" : "avi" }

Then I forced this particular secondary to become the primary. The index existed and it wasn’t replicated to other nodes, as expected. When I tried to insert dupes, it failed:

db.records.insert({user: "avi"})
WriteResult({
	"nInserted" : 0,
	"writeError" : {
		"code" : 11000,
		"errmsg" : "E11000 duplicate key error collection: users.records index: user_1 dup key: { : \"avi\" }"
	}
})

I made this new primary step down and I inserted dupes again from the primary. Checked them happily being replicated into the secondary:

rs0:SECONDARY> db.records.find()
{ "_id" : ObjectId("6066e0f278d5455b08f3920b"), "user" : "avi" }
{ "_id" : ObjectId("6066e80385a02d98945e380e"), "user" : "avi" }
{ "_id" : ObjectId("6066ea3b85a02d98945e380f"), "user" : "avi" }
rs0:SECONDARY> db.records.getIndexes()
{ "v" : 2, "unique" : true, "key" : { "user" : 1 }, "name" : "user_1" }
// snipped output

Bonus

Thanks to Piyush Mishra for reading a draft of this.


1. This post references Mongo DB v4.0, the version I was using. However, this behaviour exists in the latest version of v4.4 as well.
2. The index creation mechanisms have greatly improved in the newer versions of Mongo DB, starting with 4.2.