I Accidentally Deleted All Our Data
taylor.fausak.me
taylor.fausak.me
But besides the obvious takeaway of always having recent, tested backups, I think it's also important to get out of the habit of ever using a REPL (or SSH for that matter) to connect to production servers. Write a one-off script, document when and why you made it, check it into your VCS of choice, and run it against the target server(s). Of course within the script itself feel free to make any connections you need; my point is to never do it "manually" from the command-line. This also prepares you for when you move past having a single live server for web or db. If you're in Python-land, Fabric is the go-to tool these days for automating this.
$ python manage.py shell <~/tmp-script.py
In this instance, I didn't even consider that what I was doing could be catastrophic. But you're right, you want to avoid doing things manually. In the future, I'll track all the scripts I run. And thanks for the tip about Fabric, I'll check it out.Or, you know, have a staging server.
This is the reason why enterprises always use staging servers with test suites.
It is irrelevant if you use NoSQL, SQL, or magic, if you don't have development+staging+production platforms for a business reliant on an information system, you're doing it wrong and it will bite you hard eventually.
You should never, ever be running test scripts and code against a production db.
I understand the SQL standard, and I understand the behavior. Why is the behavior of the software defaulting to "fuck up your life" in deference to some book on a shelf? (Especially considering you could revert to "fuck up your life" mode easily with a flag.)
SET autocommit=0;Update <table> set <field>=<value>
instead of
Update <table> set <field>=<value> where <condition>
The code was executed on development server(thankfully) and it created a big mess. Full day work of 5 guys was lost. Project Manager banned her from developing any critical program and told her she will only write abap/4 reports from then onwards.
Agreed, sounds like an absolutely abysmal manager. As long as the developer wasn't in the habit of making such mistakes, I would have put her in charge of any future similar changes.
So this happens, and I devise a complex, clever, surgical, just-this-once query. Focused on the correctness of the cleverness, naturally I miss the elided WHERE clause deep within the SQL of a derived table. I run the query, and then spend several reflective hours restoring from backup and writing angry SQL to make things be as they were.
Having learned my lesson, I meticulously document the incident and the recovery steps. I'll certainly never make that mistake again, but I'll be magnanimous and help the next poor fool. A few months later I'm facing the same situation. This time, thank goodness, I have a clear process well documented. There's even big red warning text. A little copying and pasting of that clever SQL, execute the query, and -- what the...?!
Like a virus, the bad query with the missing WHERE clause had worked its way into my documentation of the event -- as the good code. It was convenient to have the restoration queries ready to go, shaving a bit of time off the two hour restoration process. Never assume you're not the next poor fool, I guess.
The worst was executing "DELETE FROM <table>" instead of "DELETE FROM <table> where userid='X' on a production server.
I realized what I did the instant I hit enter, and all the blood drained from my face.
Thank god for backups.
SELECT * from some_table where idx >= 5
Then, once you are staring at the records that came back
(and are sure they are the ones you want to delete),
change SELECT * to DELETE (and change nothing else) and
rerun. DELETE from some_table where idx >= 5
Pretty hard to get it wrong with this approach ;-)As it, when I'm writing sensitive SQL to fix an issue I end up writing the where clause first as a SELECT, then issue it.. check the results, history up, ctl-a and replace with a DELETE. Harrowing stuff.
One of my first perl scripts (c'mon. it was 12 years ago) was to adjust something about user accounts. I dont remember what it was, just that the result was deleting the home folder of every single teacher at the school.
a very honest mistake.
week? That seems kind of off. I'd suggest setting up backups more often than that, especially for production data.
Sorry to hear about that. I destroyed a drive once a long time ago (~1994) as well. Happens to the best of us.
Even non-mission critical data loss can be bad.
I don't use mongo so it's not 100% relevant, but I found setting up a postgres slave and running my backups from that is a nice solution. There's no additional load on my master and I just dump the backups into S3.
disk is cheap enough for daily backups and it's simpler to backup everything than make a big project out of slicing and dicing your backup script.
This allows you to go back in time to an earlier snapshot of your data, without having to do a full database backup.
Unfortunately, it's still a pain to setup, even with tools like https://github.com/greg2ndQuadrant/repmgr. Wish there was more convention over configuration.
(I use Heroku's WAL-E to backup the data to S3. https://github.com/heroku/WAL-E)
$ chd version mydb
$ chd revert -d <txn_id> mydb
Neither replication nor backups protect from accidentally deleting data. And restoring from binary logs requires downtime.You said you were working from the interactive shell so you didn't have history? Now I always use IPython, which does keep a history (which I set to very long), isn't there some way you can config the default Python interactive shell to keep a history as well? Sounds like that would be useful for all sorts of unexpected things. Otherwise, get IPython, it's really good, but still a plain interactive shell only with extra fancy features.
Glad to hear it turned out mostly all right though. I always feel for these data-loss stories because I can imagine what it'd be like ... that sinking feeling. Brrrr.
Doesn't django let you check if models are valid without saving them?
In rails I can do:
Family.all.select {|f| !f.valid?}Care to elaborate?
With that in mind, I'm going to do a test restore from our backups right now!
This is a good example of why ACID and declarative constraints are a very good idea for data management.You can do a bad delete from query if you don't have the proper safeguards (though querying ability of SQL lets you more easily preview your changes), but the initial corruption of the data is an easily avoidable problem.
`defscrollback 100000` is probably the single most important thing in my .screenrc. And if you're working in production, you should really have `deflog on` too.
It just seems hard to believe you confused 'save' and 'delete'...
I, too, have a hard time believing I confused "save" and "delete", but I'm reasonably sure that's what happened. I think I misused Python's shell history. I'll bet I hit the up arrow, which resurrected "family.delete()" instead of "family.save()".
I once opened a transaction, did an ALTER TABLE, and the site hung until I either committed or rolled back the transaction.
Also, my database typically refuses to start a transaction if the entire database is locked, and my application handles failing transactions by waiting a while and retrying.
If you mean why is it a problem at all, it's usually because of database size. On any large scale deployment (ie you have at least a million users) schema modifications will take hours. The only way to do reliable schema modifications is to have extra capacity and do it in stages. Also, your forward changes have to be backwards compatible. (AKA you're not allowed to both add and remove a column at the same time.)
The way to do it is to take some of your slaves out of the request pool and run the alter tables on them. You do this many times depending on your available capacity. (You probably can't just rip out half your slaves, you probably need to do at least 3 batches.) After you've altered all your slaves you can promote one to master and take the master offline to do its own alter. Then you push the code changes to production and add the old master back into the pool as a slave once it's done its alter.
In this scenario you need 3x the time the alter takes. So if the alter takes 6-7 hours (common in mysql if you have a large-ish table) it's going to take you at least 18 hours before you can push your code that depends on a database change.
Doing this manually at scale instead of an automated deployment process is extremely risky and will almost certainly be screwed up often.
This is one of the main reasons people are hoping schemaless databases work out in practice.
There's no reason to not be allowed to both add and remove a column at the same time, or to merge and split whole tables. In your example, these kinds of changes would not be possible.
There's also no reason to not be able to run old and new code at the same time, or to revert a schema change.
With ChronicDB we reduced schema changes to:
$ chd change -f upgrade_map mydb
Schemaless databases don't solve this, just as an instantaneous ALTER TABLE won't solve this.When you defeat a safety interlock system, expect to be injured.
Even if confident your test passed, what happens when you later realize it hadn't, and at what cost?