Skip to content

Commit 3884ea1

Browse files
committed
Hi again! CouchDB: docker-compose and scripts
1 parent 092126e commit 3884ea1

11 files changed

Lines changed: 229 additions & 3 deletions

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ This is a collection of ready-to-go experiments with databases.
66
Every database has a Hello World setup with
77
[Docker compose](https://docs.docker.com/compose/) to run on any OS,
88
usually in a distributed (multi-host) fashion. The hosts run in a local
9-
Docker network, which is probably the closest you can get to a simulation
10-
of an actual cluster of hosts on a local machine.
9+
Docker network, which makes it easy to simulate an actual cluster of hosts,
10+
running on a local machine. When desired, the containers can be reused with
11+
more advanced container orchestration tools
12+
(e.g., [Kubernetes](https://github.com/kubernetes/kompose)).
1113

1214
Start any database with:
15+
1316
`docker-compose up`
1417

15-
Some databases might need to scale to multiple nodes (`--scale node=N`).
18+
Some databases allow dynamic scaling of the number of nodes (`--scale node=N`).
1619

1720
All databases have some example scripts that try to highlight some of the
1821
strengths and peculiarities, and each database provides the following scripts:
@@ -21,6 +24,8 @@ strengths and peculiarities, and each database provides the following scripts:
2124
- `docker-compose run scripts ./aggregate.sh`: perform some exemplar aggregation on the data
2225

2326
Read the corresponding README's for the database for specific instructions.
27+
The scripts usually include both local database shell commands, and an actual
28+
client (in Ruby) connecting to the cluster.
2429

2530
This database lab was inspired by the "7 Databases in 7 Weeks" book, but might
2631
grow in database scope over time. I tried to pin all Docker containers to

couchdb/docker-compose.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Version from: May 2020
2+
3+
version: "3.5"
4+
services:
5+
scripts:
6+
build: scripts
7+
depends_on:
8+
- node0
9+
environment:
10+
- COORDINATOR_NODE=node0
11+
networks:
12+
- cluster
13+
14+
node0: # first node
15+
image: couchdb:3
16+
ports:
17+
- 5984:5984
18+
environment:
19+
- COUCHDB_USER=admin
20+
- COUCHDB_PASSWORD=secret
21+
#- NODENAME=node0.cluster
22+
- ERL_FLAGS=-setcookie "relax"
23+
networks:
24+
- cluster
25+
volumes:
26+
- data:/opt/couchdb/data # persistent; remove for ephemeral tasks
27+
28+
node: # additional nodes (hard-coded because of the node name)
29+
image: couchdb:3
30+
ports:
31+
- 59841:5984
32+
depends_on:
33+
- node0
34+
environment:
35+
- COUCHDB_USER=admin
36+
- COUCHDB_PASSWORD=secret
37+
#- NODENAME=node1.cluster
38+
- ERL_FLAGS=-setcookie "relax"
39+
networks:
40+
- cluster
41+
42+
networks:
43+
cluster:
44+
name: cluster
45+
46+
volumes:
47+
data: {}

couchdb/scripts/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM ubuntu:20.04
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
RUN apt-get update \
5+
&& apt-get -y install \
6+
httpie ruby ruby-dev libxslt-dev libxml2-dev \
7+
build-essential \
8+
net-tools dnsutils # network debugging
9+
10+
RUN gem install libxml-ruby couchrest
11+
12+
COPY . /scripts
13+
WORKDIR /scripts

couchdb/scripts/aggregate.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
3+
export DB_HOST=http://admin:secret@$COORDINATOR_NODE:5984
4+
5+
## Save some views
6+
# Map only:
7+
http PUT $DB_HOST/music/_design/artists < view_map_artists.json
8+
# Map + Reduce:
9+
http PUT $DB_HOST/music/_design/tags < view_map_reduce_tags.json
10+
echo "Views:"
11+
http GET $DB_HOST/music/_design/artists
12+
http GET $DB_HOST/music/_design/tags
13+
14+
## Run aggregations
15+
echo "Aggregations:"
16+
http GET "$DB_HOST/music/_design/artists/_view/by-name?limit=5"
17+
# second time is quicker due to caching
18+
http GET "$DB_HOST/music/_design/artists/_view/by-name?limit=5&descending=true"
19+
# Map-reduce job is pretty slow
20+
http GET "$DB_HOST/music/_design/tags/_view/counts?limit=5&reduce=true&group=true"
21+
22+
## Changes API
23+
echo "Recent changes:"
24+
http GET "$DB_HOST/music/_changes?limit=3&include_docs=false" # docs are large
25+
19.9 MB
Binary file not shown.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#---
2+
# Excerpted from "Seven Databases in Seven Weeks",
3+
# published by The Pragmatic Bookshelf.
4+
# Copyrights apply to this code. It may not be used to create training material,
5+
# courses, books, articles, and the like. Contact us if you are in doubt.
6+
# We make no guarantees that this code is fit for any purpose.
7+
# Visit http://www.pragmaticprogrammer.com/titles/pwrdata for more book information.
8+
#---
9+
require 'libxml'
10+
require 'couchrest'
11+
12+
include LibXML
13+
14+
class JamendoCallbacks
15+
include XML::SaxParser::Callbacks
16+
17+
def initialize
18+
@db = CouchRest.database!(ENV["DB_HOST"] + "/music")
19+
@count = 0
20+
@max = 10000 # maximum number to insert
21+
@stack = []
22+
@artist = nil
23+
@album = nil
24+
@track = nil
25+
@tag = nil
26+
@buffer = nil
27+
end
28+
29+
def on_start_element(element, attributes)
30+
case element
31+
when 'artist'
32+
@artist = { :albums => [] }
33+
@stack.push @artist
34+
when 'album'
35+
@album = { :tracks => [] }
36+
@artist[:albums].push @album
37+
@stack.push @album
38+
when 'track'
39+
@track = { :tags => [] }
40+
@album[:tracks].push @track
41+
@stack.push @track
42+
when 'tag'
43+
@tag = {}
44+
@track[:tags].push @tag
45+
@stack.push @tag
46+
when 'Artists', 'Albums', 'Tracks', 'Tags'
47+
# ignore
48+
else
49+
@buffer = []
50+
end
51+
end
52+
53+
def on_characters(chars)
54+
@buffer << chars unless @buffer.nil?
55+
end
56+
57+
def on_end_element(element)
58+
case element
59+
when 'artist'
60+
@stack.pop
61+
@artist['_id'] = @artist['id'] # reuse Jamendo's artist id for doc _id
62+
@artist[:random] = rand
63+
@db.save_doc(@artist, false, true)
64+
@count += 1
65+
if !@max.nil? && @count >= @max
66+
on_end_document
67+
end
68+
if @count % 500 == 0
69+
puts " #{@count} records inserted"
70+
end
71+
when 'album', 'track', 'tag'
72+
top = @stack.pop
73+
top[:random] = rand
74+
when 'Artists', 'Albums', 'Tracks', 'Tags'
75+
# ignore
76+
else
77+
if @stack[-1] && @buffer
78+
@stack[-1][element] = @buffer.join.force_encoding('utf-8')
79+
@buffer = nil
80+
end
81+
end
82+
end
83+
84+
def on_end_document
85+
puts "TOTAL: #{@count} records inserted"
86+
exit(0)
87+
end
88+
end
89+
90+
parser = XML::SaxParser.io(ARGF)
91+
parser.callbacks = JamendoCallbacks.new
92+
parser.parse

couchdb/scripts/item.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"_id": "turiphro:myid",
3+
"albums": [
4+
"help me!",
5+
"Lonely Hearts Club Band",
6+
"Abbey Road"
7+
],
8+
"name": "The Beatles"
9+
}

couchdb/scripts/populate.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
# Designed to be run inside container 'scripts'
3+
4+
export DB_HOST=http://admin:secret@$COORDINATOR_NODE:5984
5+
6+
http PUT $DB_HOST/testdb
7+
http POST $DB_HOST/testdb <item.json
8+
9+
echo "Populating database with data from Jamendo"
10+
echo "Check results via the UI exposed to the host OS:"
11+
echo "http://127.0.0.1:5984/_utils/#/_all_dbs"
12+
13+
zcat dbdump_artistalbumtrack.xml.gz | ruby import_from_jamendo.rb

couchdb/scripts/view_map.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function(doc) {
2+
// emit yields the output of the map function
3+
emit(doc._id, {
4+
rev: doc._rev,
5+
number: doc.number
6+
})
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"views": {
3+
"by-name": {
4+
"map": "function (doc) { if ('name' in doc && 'albums' in doc) { doc.albums.forEach(function(album) { let key = album.title || album.name; let value = {by: doc.name, album_name: album.name}; emit(key, value); /* can be multiple per doc */ }) } }"
5+
}
6+
}
7+
}

0 commit comments

Comments
 (0)