├── .gitignore ├── static ├── crashdb.jpg └── targets.txt ├── Makefile ├── main.go ├── sadness.sh ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | crashdb 2 | -------------------------------------------------------------------------------- /static/crashdb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunnihinn/crashdb/HEAD/static/crashdb.jpg -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | src=main.go 2 | bin=crashdb 3 | 4 | $(bin): $(src) 5 | go build -o $(bin) 6 | 7 | .PHONY: sadness 8 | sadness: $(bin) 9 | ./sadness.sh 10 | 11 | .PHONY: clean 12 | clean: 13 | rm -f $(bin) 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | db := make(map[string]interface{}) 12 | 13 | http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { 14 | fmt.Fprintf(w, "pong") 15 | }) 16 | 17 | type Request struct { 18 | Key string 19 | Value interface{} 20 | } 21 | 22 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 23 | switch r.Method { 24 | case "GET": 25 | dec := json.NewDecoder(r.Body) 26 | req := Request{} 27 | err := dec.Decode(&req) 28 | if err != nil { 29 | panic("User error") 30 | } 31 | 32 | val, ok := db[req.Key] 33 | if !ok { 34 | panic("User error") 35 | } 36 | 37 | resp, err := json.Marshal(val) 38 | if err != nil { 39 | panic("User error") 40 | } 41 | 42 | _, err = w.Write(resp) 43 | if err != nil { 44 | panic("User error") 45 | } 46 | 47 | case "POST": 48 | dec := json.NewDecoder(r.Body) 49 | req := Request{} 50 | err := dec.Decode(&req) 51 | if err != nil { 52 | panic("User error") 53 | } 54 | 55 | db[req.Key] = req.Value 56 | 57 | default: 58 | panic("User error") 59 | } 60 | }) 61 | 62 | log.Fatal(http.ListenAndServe(":8080", nil)) 63 | } 64 | -------------------------------------------------------------------------------- /sadness.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [[ -f crashdb ]] || go build -o crashdb main.go 4 | rm -f core.* 5 | 6 | 7 | if ! type slapper &> /dev/null; then 8 | echo -n "slapper is not installed, should we install it?" 9 | read -p " [y/N]" -n 1 -r 10 | if [[ $REPLY =~ ^[Yy]$ ]]; then 11 | echo 12 | go get -u -v github.com/ikruglov/slapper 13 | else 14 | exit 1 15 | fi 16 | fi 17 | 18 | if ! type dlv &> /dev/null; then 19 | echo -n "delve is not installed, should we install it?" 20 | read -p " [y/N]" -n 1 -r 21 | if [[ $REPLY =~ ^[Yy]$ ]]; then 22 | echo 23 | go get -u -v github.com/derekparker/delve/cmd/dlv 24 | else 25 | exit 1 26 | fi 27 | fi 28 | 29 | 30 | echo "Killing previous crashdb instances" 31 | kill $(pidof crashdb) 32 | echo "Starting crashdb..." 33 | GOTRACEBACK=crash ./crashdb & 34 | echo -n "Populating keys." 35 | curl -X POST --data '{"Key": "foo", "Value": 1}' localhost:8080/ 36 | echo -n "." 37 | curl -X POST --data '{"Key": "bar", "Value": 2}' localhost:8080/ 38 | echo "." 39 | 40 | echo "Stop slapper by pressing 'q' once crashdb gets sad" 41 | echo "Press enter to continue" 42 | read 43 | 44 | slapper -targets static/targets.txt -rate 10000 45 | 46 | clear 47 | 48 | if pidof systemd &> /dev/null; then 49 | echo "copying coredumps /var/lib/systemd/coredump/core.crashdb*" 50 | cp /var/lib/systemd/coredump/core.crashdb* . 51 | fi 52 | 53 | dlv core ./crashdb core.* 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Gunnar Þór Magnússon 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /static/targets.txt: -------------------------------------------------------------------------------- 1 | POST http://127.0.0.1:8080/ 2 | $ {"Key": "foo", "Value": "spam"} 3 | POST http://127.0.0.1:8080/ 4 | $ {"Key": "bar", "Value": "eggs"} 5 | GET http://127.0.0.1:8080/ 6 | $ {"Key": "foo"} 7 | GET http://127.0.0.1:8080/ 8 | $ {"Key": "bar"} 9 | GET http://127.0.0.1:8080/ 10 | $ {"Key": "foo"} 11 | GET http://127.0.0.1:8080/ 12 | $ {"Key": "bar"} 13 | GET http://127.0.0.1:8080/ 14 | $ {"Key": "foo"} 15 | GET http://127.0.0.1:8080/ 16 | $ {"Key": "bar"} 17 | GET http://127.0.0.1:8080/ 18 | $ {"Key": "foo"} 19 | GET http://127.0.0.1:8080/ 20 | $ {"Key": "bar"} 21 | GET http://127.0.0.1:8080/ 22 | $ {"Key": "foo"} 23 | GET http://127.0.0.1:8080/ 24 | $ {"Key": "bar"} 25 | GET http://127.0.0.1:8080/ 26 | $ {"Key": "foo"} 27 | GET http://127.0.0.1:8080/ 28 | $ {"Key": "bar"} 29 | GET http://127.0.0.1:8080/ 30 | $ {"Key": "foo"} 31 | GET http://127.0.0.1:8080/ 32 | $ {"Key": "bar"} 33 | GET http://127.0.0.1:8080/ 34 | $ {"Key": "foo"} 35 | GET http://127.0.0.1:8080/ 36 | $ {"Key": "bar"} 37 | GET http://127.0.0.1:8080/ 38 | $ {"Key": "foo"} 39 | GET http://127.0.0.1:8080/ 40 | $ {"Key": "bar"} 41 | GET http://127.0.0.1:8080/ 42 | $ {"Key": "foo"} 43 | GET http://127.0.0.1:8080/ 44 | $ {"Key": "bar"} 45 | GET http://127.0.0.1:8080/ 46 | $ {"Key": "foo"} 47 | GET http://127.0.0.1:8080/ 48 | $ {"Key": "bar"} 49 | GET http://127.0.0.1:8080/ 50 | $ {"Key": "foo"} 51 | GET http://127.0.0.1:8080/ 52 | $ {"Key": "bar"} 53 | GET http://127.0.0.1:8080/ 54 | $ {"Key": "foo"} 55 | GET http://127.0.0.1:8080/ 56 | $ {"Key": "bar"} 57 | GET http://127.0.0.1:8080/ 58 | $ {"Key": "foo"} 59 | GET http://127.0.0.1:8080/ 60 | $ {"Key": "bar"} 61 | GET http://127.0.0.1:8080/ 62 | $ {"Key": "foo"} 63 | GET http://127.0.0.1:8080/ 64 | $ {"Key": "bar"} 65 | GET http://127.0.0.1:8080/ 66 | $ {"Key": "foo"} 67 | GET http://127.0.0.1:8080/ 68 | $ {"Key": "bar"} 69 | GET http://127.0.0.1:8080/ 70 | $ {"Key": "foo"} 71 | GET http://127.0.0.1:8080/ 72 | $ {"Key": "bar"} 73 | GET http://127.0.0.1:8080/ 74 | $ {"Key": "foo"} 75 | GET http://127.0.0.1:8080/ 76 | $ {"Key": "bar"} 77 | GET http://127.0.0.1:8080/ 78 | $ {"Key": "foo"} 79 | GET http://127.0.0.1:8080/ 80 | $ {"Key": "bar"} 81 | GET http://127.0.0.1:8080/ 82 | $ {"Key": "foo"} 83 | GET http://127.0.0.1:8080/ 84 | $ {"Key": "bar"} 85 | GET http://127.0.0.1:8080/ 86 | $ {"Key": "foo"} 87 | GET http://127.0.0.1:8080/ 88 | $ {"Key": "bar"} 89 | GET http://127.0.0.1:8080/ 90 | $ {"Key": "foo"} 91 | GET http://127.0.0.1:8080/ 92 | $ {"Key": "bar"} 93 | GET http://127.0.0.1:8080/ 94 | $ {"Key": "foo"} 95 | GET http://127.0.0.1:8080/ 96 | $ {"Key": "bar"} 97 | GET http://127.0.0.1:8080/ 98 | $ {"Key": "foo"} 99 | GET http://127.0.0.1:8080/ 100 | $ {"Key": "bar"} 101 | GET http://127.0.0.1:8080/ 102 | $ {"Key": "foo"} 103 | GET http://127.0.0.1:8080/ 104 | $ {"Key": "bar"} 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrashDB 2 | 3 | CrashDB is an ephemeral in-memory NoSQL database for the confident developer. 4 | 5 | ## Features 6 | 7 | - CrashDB is webscale. 8 | - Data is not persisted between process restarts. 9 | - Using the wrong HTTP method will crash the database. 10 | - Getting a key that isn't in the database will crash the database. 11 | - Using malformed JSON will crash the database. 12 | - Any system error will crash the database. 13 | 14 | For privacy and security, CrashDB does not provide methods that reveal what 15 | keys are currently stored in the database. 16 | 17 | ![That's a paddlin'](https://raw.githubusercontent.com/gunnihinn/crashdb/master/static/crashdb.jpg) 18 | 19 | ## Build 20 | 21 | $ make 22 | 23 | CrashDB accepts no command-line options or arguments. 24 | 25 | ## Use 26 | 27 | Health check: 28 | 29 | $ curl localhost:8080/ping 30 | 31 | Get a value from the DB: 32 | 33 | $ curl -X GET --data '{"Key": "mykey"}' localhost:8080/ 34 | 35 | Put a value in the DB: 36 | 37 | $ curl -X POST --data '{"Key": "mykey", "Value": "myvalue"}' localhost:8080/ 38 | 39 | The keys must be strings, but CrashDB supports arbitrary values that can be 40 | encoded in JSON: 41 | 42 | $ curl -X POST --data '{"Key": "mykey", "Value": {"foo": ["bar"]}}' localhost:8080/ 43 | 44 | ## But why? 45 | 46 | You have to learn how to improve and debug things somehow. 47 | 48 | CrashDB has no shortage of problems. Discover them, fix them, and by improving 49 | CrashDB, improve yourself as a developer. 50 | 51 | The author recommends doing any or all of the following. Some of those points 52 | are aimed at making any single instance of CrashDB less terrible, and some of 53 | them are aimed at treating CrashDB like a distributed system. 54 | 55 | - Run `sadness.sh`. Then Google [`delve`](https://github.com/derekparker/delve) 56 | to find out what you're looking at. Become sad that 57 | [this](https://fntlnz.wtf/post/gopostmortem/) was not a part of your life 58 | until now. (The author notes that this only really works on Linux, as he 59 | sadly discovered while in the last throes of pretending that OSX was a decent 60 | operating system for doing development. You can bend Docker to your will to 61 | make this work on OSX. Find out how!) 62 | - Improve the error messages to get a better idea of what went wrong. Collect 63 | those error messages somewhere so you can search them. 64 | - Use an issue tracker - any issue tracker - to keep track of what you think is 65 | wrong with this thing. A folder with two folders called "open" and "closed" 66 | and some text files under version control is a perfectly good issue tracker 67 | for beginners. 68 | - Decide whether to fix the race conditions (you did run `sadness.sh`, right?) 69 | by embracing channels or just using a fucking mutex like everyone in the 70 | world apparently does. Reflect on Erlang/Elixir and debate whether you would 71 | have this problem there. 72 | - Find your own things to care about and improve CrashDB in those directions. 73 | Realize after weeks of work that some of those things were bad ideas and all 74 | that work needs to be thrown away. Be OK with that. 75 | - Question some of the original design decisions. Is it really necessary to 76 | serialize and deserialize the values all the time? Does this add unacceptable 77 | overhead for intricate keys? Is it a potential DOS vector? Can you craft 78 | key/value pairs that result in arbitrarily bad performance? 79 | - [Profile](https://blog.golang.org/profiling-go-programs) CrashDB. Get your 80 | own torture tests with sample queries that exhibit some of the problems you 81 | want to fix, or some of the performance characteristics of the program. 82 | Profile before and after fixes to see their impact. 83 | - Make CrashDB persistent. Figure out how and where to store its data between 84 | restarts or crashes. Do you need to worry about data getting corrupted? 85 | - Add monitoring. CrashDB is a service, so the 86 | [RED method](https://www.weave.works/blog/the-red-method-key-metrics-for-microservices-architecture/) 87 | seems appropriate. Decide whether to use "push" monitoring (à la Graphite) or 88 | "pull" monitoring (à la Prometheus). 89 | - Support alternative serialization formats. JSON is fine for some things (the 90 | more you use numbers, the less fine it is), but consider binary formats like 91 | Protocol buffers. Use schemas to autogenerate a client for CrashDB for at 92 | least one language. 93 | - Run CrashDB in Docker. Now run two of them at once and setup a master/slave 94 | replication chain. Direct writes to the master and reads to the slave. Do 95 | service discovery for the outside world. Run three instances at once and have 96 | the slaves elect a new master if the original one becomes sad. Find out why 97 | Zookeeper is a thing when you try to do any of this or query the cluster from 98 | the outside. 99 | 100 | --------------------------------------------------------------------------------