Why it's hard to write a dupefinder
rmlint.readthedocs.org
rmlint.readthedocs.org
- Files are indexed by inode and device, files with the same inode + device are considered equal. (My main use case for this was to bundle up JS files.)
- Files are then indexed by size; only files with the same size are compared.
- During comparison, the files are read at block sizes increasing in powers of two, starting with 2k. The blocks are hashed and compared, and if they do not match the comparison is stopped early (often without having to read the full file). If all the hashes are equal, then the files are considered to be equal.
- Hashes are only computed when needed and cached in memory. Since the hash block size increases in powers of two, only a few dozen hashes are needed even for large files (reducing memory usage compared to a fixed hash block size).
To find out how many files of each size you have:
find ~ -type f -printf '%s\n' | sort | uniq -c | sort -n
The core of the duplicate detection is the shell script here:
https://github.com/pixelb/fslint/blob/ffcd3b85/fslint/findup...
(meanwhile there's stuff in the wild using things like Java hashCode() to determine identities, and the birthday point on that is only around 75k IIRC)
If you crunch your way through files (and lets assume they're random from the hash functions point of view) for 10 years, you have to calculate the hash of 60.000.000.000 files / second - to have 50% chance of finding a collision. If the average filesize is 1 KB, that is ~56TB data every single second.
Md5 is not bad as long as you're not expecting malicious input, or gigantic datasets.
So I'd say it's certain that the requisite number of MD5 hashes have been generated across the aggregate of those systems (assuming enough of them use MD5 in some fashion), and I'd personally bet money there's at least one real-world deployment out there that's handled enough all on its own to hit the birthday point.
300 hours every minute, equals 3006024*365.25 = 157.788.000 hours / year.
Lets assume it's all HD and 2 gb an hour, that is ~30pb / year. Storing all that video surely uses large capacity harddrive with a 4k sector size, so that it the smallest it makes sense to perform dedupe on.
That is ~82726 billion blocks / year.
To have 50% probability of a collision, we need to calculate 2^64 blocks, so we need 222985 years of video before there is more than 50% probability.
Lets move away from YouTube for a moment, and move towards the IP trafic as a whole. Cisco has estimated the total IP trafic to 27483 pb / month in 2011, if we're looking at the growth it is growing less and less for every year, and from 2010 to 2011 it was only 34% down from 65% from 2005 to 2006. I assume 30% growth from 2011 and forward, and we end up with ~291 exabytes / month, or 3497 exabytes / year in 2020, this is total IP trafic and will include a lot of duplicated data (e.g. Netflix streams). Again, lets assume a 4kb block, and the total estimated IP trafic in 2020 generates ~961e10^15 blocks. We need 2^64 blocks to have a 50% probability of collision, that is more than 19 times of the total estimated IP trafic in 2020, the vast majority of which is duplicated.
My claim is that unless you're google, md5 is fine. If you're google, md5 is still fine, but you should be looking to prepare your system to accept a hash with a larger output space.
X!/((X^n) * (X-n)!)
It only takes 23 people to have a 50/50 shot of sharing a birthday.
On the plus side MD5 is 128bits.
Makes me wonder now. Might've been a rogue rsync at the time or something, then. /shrug.
https://s3-eu-west-1.amazonaws.com/md5collisions/a.php https://s3-eu-west-1.amazonaws.com/md5collisions/b.php
Obviously a contrived example, but given the above and other weaknesses md5 should not be considered safe for verifying file contents (at least from a security perspective).
... but accidental collisions? That's still hugely more likely to be "you're misremembering" or "you misread" than a real collision.
But I do believe you thought it did, if that's any consolation ;)
... but I would loved to be proved wrong.
A 64 bit checksum + 64 bit filesize or a 128 bit checksum?
$ find / -ls | awk '{print $7}' >/tmp/filesizes
$ rev /tmp/filesizes | cut -b1 | sort -n | uniq -c | sort -n
5718 5
5816 3
5823 1
5901 9
6463 7
7549 4
7958 8
8240 0
8991 6
14507 2Changed the command up a bit since it was including zero byte files and directories:
sudo find / -size +0 -type f -ls | awk '{print $7,$NF}' > /tmp/filesizes
110920 7
111306 5
111312 1
111345 9
111362 3
130138 0
130511 4
131528 8
139000 2
152213 6
Some other interesting things (dirname is too slow...): cat filesizes | grep "6 " | awk '{print $NF}' | sed 's/\/[^/]*$//' | sort | uniq -c | sort -nr | head
1607 /usr/share/man/man3
906 /home/matt/.cache/mozilla/firefox/8jc2n3qa.default/cache2/entries
825 /usr/bin
808 /home/matt/.tmux/resurrect
Then again a lot of this is flawed because it's looking at /sys. Too lazy to fix it now :)https://github.com/rburchell/fastdup
:-)
Size is a fast initial differentiator because scanning for file size only needs to read metadata, but how good does it differentiate?
# all files
find ~ -type f | wc -l
1098045
# files with a unique size
find ~ -type f -printf '%s\n'|sort|uniq -c|grep ' 1 '|wc -l
48437
# most prevalent sizes (prevalence vs size)
find ~ -type f -printf '%s\n'|sort |uniq -c |sort -nr|head
36972 0
12707 24
9480 22
8036 39
5025 12
4804 26
3879 6
3232 76
2946 38
2855 20
In my home dir, less than 5% of files have a unique size.http://perlmonks.org/?node_id=1067570
discussion at http://mail.pm.org/pipermail/dfw-pm/2013-December/thread.htm...
=> http://dfw.pm.org/winners.html
it is really not easy. I used as only one bloom hashes, which explains why mine was fastest and smallest, but unfortunately not correct.
one way to mitigate that might be take a _few_ random, say, 1k chunks of file content, and hash those. as long as you have a map of (offset, hash) de-duping should be just fine.
the only thing is, i don't really know, what an upper bound on 'few samples' would be like. asymptotically, it should approach file-size/chunk-size, but ideally, it should be way less than that.
any ideas on how to determine a minimal but complete set ?
Consider the case of a log file and its backup made one short log message earlier.
If you want to gamble on using only a partial hash, I would hash the last few kb of each file, as that (at least based on this 30-second 'analysis' of the problem domain) likely would work better in practice. Theoretically, it is as vulnerable, though.
Edit: if you have a VM image on your disk, and that small append happens to a log file in the image, that small change can happen anywhere. It also can be even smaller; someone might run 'touch' on an already existing file. Theoretically, that could be a one-byte change.
1. take a sha1 hash of the last 4KB of each file.
2. for any equal hashes, compare the whole file.
With this method you should be able to skip reading many large files in their entirety.
As an optimization, you might want to check for files with holes in them ("sparse" files). Not sure how to do that, though.