├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── demo └── load.js ├── gulpfile.js ├── package.json └── src ├── images ├── 0.01ms-2kreq-10logs.png ├── 10x.png ├── adapters.jpg ├── alan-kay.jpg ├── arch-generic.png ├── arch-microservices.png ├── arch-modules.png ├── arch-mvc.png ├── arch-services.png ├── bloomrun.png ├── bloomrun.svg ├── cc_attribution.png ├── control.png ├── dragon-scroll.jpg ├── dynamo-strategy-one.png ├── dynamodb.png ├── ecosystem-impact.jpg ├── event-loop-gc.png ├── event-loop.png ├── fg-pino-after.png ├── fg-pino-before.png ├── fire.jpg ├── five-euro.jpg ├── hashring-sharding.png ├── heartbeat.png ├── hole.jpg ├── integration.jpg ├── kaboom.gif ├── lamp.jpg ├── loadbalacing.png ├── london.jpg ├── ludicrous.gif ├── magic.gif ├── matteo.png ├── me-nodeconf-eu-2013.jpg ├── monolith.jpg ├── nearform.svg ├── no-caching.png ├── node-desktop.png ├── nodeconfeu-2013-top.jpg ├── nodejs.png ├── penguins.gif ├── piggy.jpg ├── reqhashing-with-gateway.png ├── reqhashing.png ├── sharding.png ├── shifu.png ├── snail.jpg ├── spaceballs.jpg ├── speed.jpg ├── stonehenge.JPG ├── swim.png ├── tools.jpg ├── unfeasible.gif ├── upring-events.jpeg ├── upring.svg └── wv.jpg ├── index.jade ├── scripts └── main.js └── styles └── main.styl /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | dist 4 | pdf 5 | bespoke-theme-* 6 | .DS_Store 7 | profile-* 8 | .__browserify_string_empty.js 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Matteo Collina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scaling State 2 | 3 | [**UpRing**][upring] provides application-level sharding, based on node.js streams. UpRing allocates some resources to a node, based on the hash of a `key`, and allows you to query the node using a request response pattern (based on JS objects) which can embed streams. 4 | 5 | [**UpRing**][upring] simplifies the implementation and deployment of a cluster of nodes using a gossip membership protocol and a [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing) scheme (see [swim-hashring](https://github.com/mcollina/swim-hashring)). It uses [tentacoli](https://github.com/mcollina/tentacoli) as a transport layer. 6 | 7 | ## View slides locally 8 | 9 | First, ensure you have the following installed: 10 | 11 | 1. [Node.js](http://nodejs.org) 12 | 2. [Bower](http://bower.io): `$ npm install -g bower` 13 | 3. [Gulp](http://gulpjs.com): `$ npm install -g gulp` 14 | 15 | Then, install dependencies and run the preview server: 16 | 17 | ```bash 18 | $ npm install && bower install 19 | $ gulp serve 20 | ``` 21 | 22 | [upring]: https://github.com/mcollina/upring 23 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "presentation-we-are-not-object-oriented-anymore", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "font-awesome": "~4.4.0", 6 | "prism": "gh-pages" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/load.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const UpRingKV = require('upring-kv') 4 | var dockerNames = require('docker-names') 5 | 6 | const args = require('minimist')(process.argv.slice(2), { 7 | boolean: ['help', 'verbose'], 8 | default: { 9 | port: 0, 10 | points: 100, 11 | timeout: 200, 12 | verbose: false 13 | }, 14 | alias: { 15 | port: 'p', 16 | points: 'P', 17 | timeout: 't', 18 | verbose: 'V' 19 | } 20 | }) 21 | 22 | const db = UpRingKV({ 23 | base: args._, 24 | logLevel: args.verbose ? 'debug' : 'info', 25 | client: true, 26 | hashring: { 27 | replicaPoints: args.points, 28 | joinTimeout: args.timeout 29 | } 30 | }) 31 | 32 | db.upring.on('up', function () { 33 | setInterval(function () { 34 | var name = '/' + dockerNames.getRandomName() 35 | var value = { 36 | contentType: 'text/plain', 37 | value: 'hello ' + name 38 | } 39 | db.put(name, value, function (err) { 40 | db.logger.info('published %s %j', name, value) 41 | }) 42 | }, 1000) 43 | }) 44 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pkg = require('./package.json'), 4 | gulp = require('gulp'), 5 | gutil = require('gulp-util'), 6 | plumber = require('gulp-plumber'), 7 | rename = require('gulp-rename'), 8 | connect = require('gulp-connect'), 9 | browserify = require('gulp-browserify'), 10 | uglify = require('gulp-uglify'), 11 | jade = require('gulp-jade'), 12 | stylus = require('gulp-stylus'), 13 | autoprefixer = require('gulp-autoprefixer'), 14 | csso = require('gulp-csso'), 15 | del = require('del'), 16 | through = require('through'), 17 | opn = require('opn'), 18 | ghpages = require('gh-pages'), 19 | path = require('path'), 20 | isDist = process.argv.indexOf('serve') === -1; 21 | 22 | 23 | gulp.task('js', ['clean:js'], function() { 24 | return gulp.src('src/scripts/main.js') 25 | .pipe(isDist ? through() : plumber()) 26 | .pipe(browserify({ transform: ['debowerify'], debug: !isDist })) 27 | .pipe(isDist ? uglify() : through()) 28 | .pipe(rename('build.js')) 29 | .pipe(gulp.dest('dist/build')) 30 | .pipe(connect.reload()); 31 | }); 32 | 33 | gulp.task('html', ['clean:html'], function() { 34 | return gulp.src('src/index.jade') 35 | .pipe(isDist ? through() : plumber()) 36 | .pipe(jade({ pretty: true })) 37 | .pipe(rename('index.html')) 38 | .pipe(gulp.dest('dist')) 39 | .pipe(connect.reload()); 40 | }); 41 | 42 | gulp.task('css', ['clean:css'], function() { 43 | return gulp.src('src/styles/main.styl') 44 | .pipe(isDist ? through() : plumber()) 45 | .pipe(stylus({ 46 | // Allow CSS to be imported from node_modules and bower_components 47 | 'include css': true, 48 | 'paths': ['./node_modules', './bower_components'] 49 | })) 50 | .pipe(autoprefixer('last 2 versions', { map: false })) 51 | .pipe(isDist ? csso() : through()) 52 | .pipe(rename('build.css')) 53 | .pipe(gulp.dest('dist/build')) 54 | .pipe(connect.reload()); 55 | }); 56 | 57 | gulp.task('images', ['clean:images'], function() { 58 | return gulp.src('src/images/**/*') 59 | .pipe(gulp.dest('dist/images')) 60 | .pipe(connect.reload()); 61 | }); 62 | 63 | gulp.task('clean', function(done) { 64 | del('dist', done); 65 | }); 66 | 67 | gulp.task('clean:html', function(done) { 68 | del('dist/index.html', done); 69 | }); 70 | 71 | gulp.task('clean:js', function(done) { 72 | del('dist/build/build.js', done); 73 | }); 74 | 75 | gulp.task('clean:css', function(done) { 76 | del('dist/build/build.css', done); 77 | }); 78 | 79 | gulp.task('clean:images', function(done) { 80 | del('dist/images', done); 81 | }); 82 | 83 | gulp.task('clean:fonts', function(done) { 84 | del('dist/fonts', done); 85 | }); 86 | 87 | gulp.task('font-awesome', ['clean:fonts'], function() { 88 | return gulp.src('./bower_components/font-awesome/fonts/*') 89 | .pipe(gulp.dest('dist/fonts')) 90 | .pipe(connect.reload()); 91 | }); 92 | 93 | gulp.task('connect', ['build'], function() { 94 | connect.server({ 95 | root: 'dist', 96 | livereload: true 97 | }); 98 | }); 99 | 100 | gulp.task('open', ['connect'], function (done) { 101 | opn('http://localhost:8080', done); 102 | }); 103 | 104 | gulp.task('watch', function() { 105 | gulp.watch('src/**/*.jade', ['html']); 106 | gulp.watch('src/styles/**/*.styl', ['css']); 107 | gulp.watch('src/images/**/*', ['images']); 108 | gulp.watch([ 109 | 'src/scripts/**/*.js', 110 | 'bespoke-theme-*/dist/*.js' // Allow themes to be developed in parallel 111 | ], ['js']); 112 | }); 113 | 114 | gulp.task('deploy', ['build'], function(done) { 115 | ghpages.publish(path.join(__dirname, 'dist'), { logger: gutil.log }, done); 116 | }); 117 | 118 | gulp.task('build', ['js', 'html', 'css', 'images', 'font-awesome']); 119 | 120 | gulp.task('serve', ['open', 'watch']); 121 | 122 | gulp.task('default', ['build', 'exit']); 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "presentation-the-cost-of-logging", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "bespoke-run": "^1.0.1", 6 | "bloomrun": "^2.3.0", 7 | "docker-names": "^1.0.0", 8 | "minimist": "^1.2.0", 9 | "upring-kv": "^0.4.2" 10 | }, 11 | "devDependencies": { 12 | "bespoke": "^1.0.0", 13 | "bespoke-backdrop": "^1.0.0", 14 | "bespoke-bullets": "^1.0.0", 15 | "bespoke-classes": "^1.0.0", 16 | "bespoke-hash": "^1.0.0", 17 | "bespoke-keys": "^1.0.0", 18 | "bespoke-progress": "^1.0.0", 19 | "bespoke-scale": "^1.0.0", 20 | "bespoke-touch": "^1.0.0", 21 | "debowerify": "^0.7.1", 22 | "del": "^1.1.1", 23 | "gh-pages": "^0.2.0", 24 | "gulp": "^3.8.1", 25 | "gulp-autoprefixer": "0.0.7", 26 | "gulp-browserify": "^0.5.0", 27 | "gulp-connect": "^2.0.5", 28 | "gulp-csso": "^0.2.9", 29 | "gulp-jade": "^0.6.0", 30 | "gulp-plumber": "^0.6.3", 31 | "gulp-rename": "^1.2.0", 32 | "gulp-stylus": "^1.0.2", 33 | "gulp-uglify": "^0.3.1", 34 | "gulp-util": "^2.2.17", 35 | "insert-css": "^0.2.0", 36 | "normalizecss": "^3.0.0", 37 | "opn": "^0.1.2" 38 | }, 39 | "engines": { 40 | "node": ">=0.10.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/images/0.01ms-2kreq-10logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/0.01ms-2kreq-10logs.png -------------------------------------------------------------------------------- /src/images/10x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/10x.png -------------------------------------------------------------------------------- /src/images/adapters.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/adapters.jpg -------------------------------------------------------------------------------- /src/images/alan-kay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/alan-kay.jpg -------------------------------------------------------------------------------- /src/images/arch-generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/arch-generic.png -------------------------------------------------------------------------------- /src/images/arch-microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/arch-microservices.png -------------------------------------------------------------------------------- /src/images/arch-modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/arch-modules.png -------------------------------------------------------------------------------- /src/images/arch-mvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/arch-mvc.png -------------------------------------------------------------------------------- /src/images/arch-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/arch-services.png -------------------------------------------------------------------------------- /src/images/bloomrun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/bloomrun.png -------------------------------------------------------------------------------- /src/images/bloomrun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/images/cc_attribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/cc_attribution.png -------------------------------------------------------------------------------- /src/images/control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/control.png -------------------------------------------------------------------------------- /src/images/dragon-scroll.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/dragon-scroll.jpg -------------------------------------------------------------------------------- /src/images/dynamo-strategy-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/dynamo-strategy-one.png -------------------------------------------------------------------------------- /src/images/dynamodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/dynamodb.png -------------------------------------------------------------------------------- /src/images/ecosystem-impact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/ecosystem-impact.jpg -------------------------------------------------------------------------------- /src/images/event-loop-gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/event-loop-gc.png -------------------------------------------------------------------------------- /src/images/event-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/event-loop.png -------------------------------------------------------------------------------- /src/images/fg-pino-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/fg-pino-after.png -------------------------------------------------------------------------------- /src/images/fg-pino-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/fg-pino-before.png -------------------------------------------------------------------------------- /src/images/fire.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/fire.jpg -------------------------------------------------------------------------------- /src/images/five-euro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/five-euro.jpg -------------------------------------------------------------------------------- /src/images/hashring-sharding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/hashring-sharding.png -------------------------------------------------------------------------------- /src/images/heartbeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/heartbeat.png -------------------------------------------------------------------------------- /src/images/hole.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/hole.jpg -------------------------------------------------------------------------------- /src/images/integration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/integration.jpg -------------------------------------------------------------------------------- /src/images/kaboom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/kaboom.gif -------------------------------------------------------------------------------- /src/images/lamp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/lamp.jpg -------------------------------------------------------------------------------- /src/images/loadbalacing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/loadbalacing.png -------------------------------------------------------------------------------- /src/images/london.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/london.jpg -------------------------------------------------------------------------------- /src/images/ludicrous.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/ludicrous.gif -------------------------------------------------------------------------------- /src/images/magic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/magic.gif -------------------------------------------------------------------------------- /src/images/matteo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/matteo.png -------------------------------------------------------------------------------- /src/images/me-nodeconf-eu-2013.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/me-nodeconf-eu-2013.jpg -------------------------------------------------------------------------------- /src/images/monolith.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/monolith.jpg -------------------------------------------------------------------------------- /src/images/nearform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 11 | 12 | 14 | 19 | 20 | 22 | 25 | 27 | 28 | 29 | 30 | 32 | 36 | 41 | 43 | 44 | 48 | 50 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/images/no-caching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/no-caching.png -------------------------------------------------------------------------------- /src/images/node-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/node-desktop.png -------------------------------------------------------------------------------- /src/images/nodeconfeu-2013-top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/nodeconfeu-2013-top.jpg -------------------------------------------------------------------------------- /src/images/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/nodejs.png -------------------------------------------------------------------------------- /src/images/penguins.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/penguins.gif -------------------------------------------------------------------------------- /src/images/piggy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/piggy.jpg -------------------------------------------------------------------------------- /src/images/reqhashing-with-gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/reqhashing-with-gateway.png -------------------------------------------------------------------------------- /src/images/reqhashing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/reqhashing.png -------------------------------------------------------------------------------- /src/images/sharding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/sharding.png -------------------------------------------------------------------------------- /src/images/shifu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/shifu.png -------------------------------------------------------------------------------- /src/images/snail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/snail.jpg -------------------------------------------------------------------------------- /src/images/spaceballs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/spaceballs.jpg -------------------------------------------------------------------------------- /src/images/speed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/speed.jpg -------------------------------------------------------------------------------- /src/images/stonehenge.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/stonehenge.JPG -------------------------------------------------------------------------------- /src/images/swim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/swim.png -------------------------------------------------------------------------------- /src/images/tools.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/tools.jpg -------------------------------------------------------------------------------- /src/images/unfeasible.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/unfeasible.gif -------------------------------------------------------------------------------- /src/images/upring-events.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/upring-events.jpeg -------------------------------------------------------------------------------- /src/images/upring.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Produced by OmniGraffle 6.6.1 2016-11-07 14:20:43 +0000Canvas 1Layer 1UpRingapplication-level sharding for Node.js 4 | -------------------------------------------------------------------------------- /src/images/wv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcollina/scaling-state/7219fc93c6447f2aead1f33c5ff5f81142eb3665/src/images/wv.jpg -------------------------------------------------------------------------------- /src/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset='utf-8') 5 | meta(name='viewport', content='width=device-width, initial-scale=1, maximum-scale=1') 6 | title Scaling State 7 | 8 | link(rel='stylesheet', type='text/css', href='build/build.css') 9 | 10 | body 11 | 12 | article 13 | 14 | section(data-bespoke-backdrop='black').trans 15 | div(style='text-align: left; margin-top: -2em; color: white') 16 | h1 Scaling State 17 | h3(style='color: white') by  18 | a(style="display:inline-block;margin-left: -.03em; color: white",href="http://twitter.com/matteocollina") @matteocollina 19 | 20 | br 21 | div(style="width: 50%; float: right; margin-bottom: -30em;") 22 | a(href="http://nearform.com") 23 | img(src="./images/nearform.svg") 24 | 25 | section() 26 | // h2 Stateless Architecture 27 | img(src='images/arch-generic.png' style='height: 90%') 28 | 29 | section() 30 | h3 Round-Robin Load Balancing 31 | img(src='images/loadbalacing.png' style='width: 90%') 32 | 33 | section 34 | h3 Round-Robin Load Balancing 35 | img(src="./images/no-caching.png", style="width: 120%") 36 | 37 | section 38 | h2 Any local caching is unfeasible 39 | br 40 | | as the load is shared 41 | 42 | section 43 | h3 Application-Level Sharding 44 | img(src="./images/reqhashing.png" style="width: 90%") 45 | // Given a request referencing data X, we must always route it to peer Y. 46 | 47 | section 48 | h3 How do we assign users to servers? 49 | img(src='images/sharding.png' style='height: 80%') 50 | 51 | section 52 | h2 What if the topology changes? 53 | 54 | section 55 | h2 Failure detection 56 | img(src="./images/heartbeat.png" style="width: 90%") 57 | h2 by heartbeat 58 | 59 | section 60 | img(src="./images/unfeasible.gif") 61 | 62 | section 63 | h2 ..or is it? 64 | 65 | section() 66 | img(src='images/upring.svg' style='width: 90%') 67 | 68 | section 69 | h2 DEMO 70 | a(href="https://youtu.be/fLDOCwiKbbo") https://youtu.be/fLDOCwiKbbo 71 | 72 | section(data-bespoke-backdrop='magic').trans 73 | 74 | section(data-bespoke-backdrop='dragon-scroll').trans 75 | h1(style="color: white; padding-top: 50%") Secret Sauce(s) 76 | 77 | section() 78 | img(src='images/dynamodb.png' style='width: 90%') 79 | 80 | section 81 | img(src="./images/dynamo-strategy-one.png" style="height: 90%") 82 | 83 | section() 84 | img(src='images/swim.png' style='width: 90%') 85 | 86 | section 87 | ul(style='text-align: left; font-size: 3em').bullet 88 | li 89 | b(style='color: red; font-style: italic;') S 90 | | calable 91 | li 92 | b(style='color: red; font-style: italic;') W 93 | | eakly-Consistent 94 | li 95 | b(style='color: red; font-style: italic;') I 96 | | nfection Style 97 | li 98 | b(style='color: red; font-style: italic;') M 99 | | embership protocol 100 | 101 | section(data-bespoke-backdrop='lamp').trans 102 | h3(style="color: white; margin-left: auto; margin-right: 0%; width: 30%; text-align: right; padding-bottom: 5%") 103 | | Let's organize our peers in a consistent hashring, 104 | | using SWIM to detect failures 105 | 106 | section(data-bespoke-backdrop='magic').trans 107 | 108 | section() 109 | img(src='images/hashring-sharding.png' style='height: 90%') 110 | 111 | section(data-bespoke-backdrop='upring-events').trans 112 | 113 | section 114 | h2 API 115 | 116 | section 117 | h2 Starting upring 118 | pre 119 | code.language-javascript. 120 | const upring = UpRing({ 121 | base: ['127.0.0.1:7979'], 122 | logLevel: 'debug', 123 | hashring: { 124 | replicaPoints: 10, 125 | joinTimeout: 200 126 | } 127 | }) 128 | 129 | section 130 | h2 Adding patterns 131 | pre 132 | code.language-javascript. 133 | upring.add('ns:kv,cmd:put', function (req, reply) { 134 | db.set(req.key, req.value) 135 | /* the first argument is the error 136 | the second one is the result */ 137 | reply(null, { ok: true }) 138 | }) 139 | 140 | section 141 | h2 Send requests 142 | pre 143 | code.language-javascript. 144 | upring.request({ 145 | ns: 'kv', 146 | cmd: 'get', 147 | key: 'hello' 148 | }, console.log) 149 | 150 | section 151 | h2 Do live updates 1/2 152 | pre 153 | code.language-javascript. 154 | upring.add('ns:kv,cmd:live', function (req, reply) { 155 | const updates = new Readable({ 156 | objectMode: true, 157 | read: () => {} 158 | }) 159 | // call push whenever you want to send data 160 | // se the full Readable API at nodejs.org 161 | updates.push({ hello: 'world' }) 162 | reply(null, { streams: { updates } }) 163 | }) 164 | 165 | section 166 | h2 Do live updates 2/2 167 | pre 168 | code.language-javascript. 169 | upring.request({ 170 | ns: 'kv', 171 | cmd: 'get', 172 | key: 'hello' 173 | }, function (err, res) { 174 | res.streams.updates.on('data', console.log) 175 | }) 176 | 177 | section 178 | h2 Track 179 | pre 180 | code.language-javascript. 181 | /* call upring.track(key) if you are responsible 182 | for that key, i.e. if upring.allocatedToMe(key) 183 | returns true */ 184 | const tracker = upring.track(key, { replica: true }) 185 | tracker.on('replica', function (peer) { 186 | console.log('new replica', peer) 187 | }) 188 | tracker.on('move', function (peer) { 189 | console.log('moved to', peer) 190 | }) 191 | 192 | section 193 | h2 Replica 194 | pre 195 | code.language-javascript. 196 | /* call upring.replica(key, cb) if you are not responsible 197 | for that key, i.e. if upring.allocatedToMe(key) 198 | returns false */ 199 | upring.replica(key, function () { 200 | // now upring.allocateToMe(key) returns true 201 | }) 202 | 203 | section 204 | h3 Upring is a framework to  205 | b build 206 | 207 | ul.bullet 208 | li a key value store 209 | li a cache 210 | li a pub/sub system 211 | li real-time applications 212 | li a discovery system 213 | li a metadata store for microservices 214 | li ..or anything that requires distributed state! 215 | 216 | section 217 | h2 Links 218 | ul.bullet 219 | li 220 | a(href="https://github.com/mcollina/upring"). 221 | https://github.com/mcollina/upring 222 | li 223 | a(href="https://github.com/mcollina/upring-kv"). 224 | https://github.com/mcollina/upring-kv 225 | li 226 | a(href="https://github.com/mcollina/upring-control"). 227 | https://github.com/mcollina/upring-control 228 | li 229 | a(href="https://github.com/mcollina/upring-pubsub"). 230 | https://github.com/mcollina/upring-pubsub 231 | 232 | section 233 | h2 This presentation 234 | ul.bullet 235 | li 236 | a(href="https://mcollina.github.io/scaling-state"). 237 | https://mcollina.github.io/scaling-state 238 | li 239 | a(href="https://github.com/mcollina/scaling-state"). 240 | https://github.com/mcollina/scaling-state 241 | 242 | section 243 | img(src='images/matteo.png' style="width: 95%") 244 | h3 245 | a(href='http://github.com/mcollina') http://github.com/mcollina 246 | 247 | section 248 | h1 Thanks! 249 | a(href="http://nearform.com" style="width: 20%") 250 | img(src="./images/nearform.svg") 251 | br 252 | h3 matteo.collina@nearform.com 253 | h3 @matteocollina 254 | h3 www.nearform.com 255 | 256 | section 257 | img(src='images/upring.svg' style='width: 30%') 258 | br 259 | br 260 | h2 How does it work? 261 | 262 | section 263 | h2 Transport: tentacoli 264 | ul.bullet 265 | li 266 | a(href="https://github.com/mcollina/tentacoli") 267 | | https://github.com/mcollina/tentacoli 268 | li request/response for messages 269 | li support sending binary streams 270 | li support sending object streams 271 | li works on top any binary stream 272 | 273 | section 274 | pre 275 | code.language-javascript. 276 | var stream = net.connect(4200) 277 | var instance = tentacoli() 278 | 279 | stream.pipe(instance).pipe(stream) 280 | 281 | instance.request({ 282 | cmd: 'somedata' 283 | }, function (err, res) { 284 | console.log(err, res) 285 | }) 286 | 287 | section 288 | pre 289 | code.language-javascript. 290 | var server = net.createServer(function (stream) { 291 | var instance = tentacoli() 292 | stream.pipe(instance).pipe(stream) 293 | stream.on('request', handle) 294 | }) 295 | server.listen(4200) 296 | 297 | section 298 | pre 299 | code.language-javascript. 300 | function handle (req, reply) { 301 | console.log('--> request is', req.cmd) 302 | reply(null, { 303 | data: 'some data' 304 | }) 305 | } 306 | 307 | section 308 | h2 Streams are a first level concern 309 | p 310 | | We can use them to receive live updates 311 | pre 312 | code.language-javascript. 313 | instance.request({ 314 | cmd: 'a request', 315 | }, function (err, res) { 316 | res.streams.on('data', console.log) 317 | } 318 | 319 | section 320 | pre 321 | code.language-javascript. 322 | instance.request({ 323 | cmd: 'upload', 324 | streams: { 325 | file: fs.createReadStream(__filename) 326 | } 327 | }, console.log) 328 | 329 | section 330 | h2 Streams can also be in replies 331 | pre 332 | code.language-javascript. 333 | function handle (req, reply) { 334 | console.log('--> request is', req.cmd) 335 | reply(null, { 336 | streams: { 337 | file: fs.createReadStream(__filename) 338 | } 339 | }) 340 | } 341 | 342 | section 343 | a(href="http://github.com/mcollina/bloomrun") 344 | img(src="./images/bloomrun.png" style="width: 90%") 345 | 346 | section 347 | h2 Pattern Matching! 348 | pre 349 | code(data-bespoke-autorun).language-javascript. 350 | var i = bloomrun() 351 | i.add({ cmd: 'save' }, function save (arg, cb) { 352 | alert('saving ' + JSON.stringify(arg)) 353 | cb(null, true) }) 354 | 355 | var msg = { 356 | cmd: 'save', 357 | person: { name: 'matteo' } } 358 | i.lookup(msg)(msg, function (err, result) { 359 | alert([err, result].join(' ')) }) 360 | 361 | section 362 | h1 Thanks! 363 | a(href="http://nearform.com" style="width: 20%") 364 | img(src="./images/nearform.svg") 365 | br 366 | h3 matteo.collina@nearform.com 367 | h3 @matteocollina 368 | h3 www.nearform.com 369 | 370 | script(src='build/build.js') 371 | -------------------------------------------------------------------------------- /src/scripts/main.js: -------------------------------------------------------------------------------- 1 | // Require Node modules in the browser thanks to Browserify: http://browserify.org 2 | var bespoke = require('bespoke'), 3 | classes = require('bespoke-classes'), 4 | keys = require('bespoke-keys'), 5 | touch = require('bespoke-touch'), 6 | bullets = require('bespoke-bullets'), 7 | backdrop = require('bespoke-backdrop'), 8 | scale = require('bespoke-scale'), 9 | hash = require('bespoke-hash'), 10 | progress = require('bespoke-progress'), 11 | run = require('bespoke-run'), 12 | bloomrun = require('bloomrun'); 13 | 14 | // Bespoke.js 15 | bespoke.from('article', [ 16 | classes(), 17 | keys(), 18 | touch(), 19 | run(), 20 | bullets('li, .bullet'), 21 | backdrop(), 22 | scale(), 23 | hash(), 24 | progress() 25 | ]); 26 | 27 | // Prism syntax highlighting 28 | // This is actually loaded from "bower_components" thanks to 29 | // debowerify: https://github.com/eugeneware/debowerify 30 | require('prism'); 31 | 32 | global.bloomrun = bloomrun; 33 | -------------------------------------------------------------------------------- /src/styles/main.styl: -------------------------------------------------------------------------------- 1 | 2 | @import 'prism/themes/prism-okaidia.css' 3 | @import 'font-awesome/css/font-awesome.css' 4 | 5 | slide_width = 640px 6 | slide_height = 480px 7 | slide_background = white 8 | 9 | slide_transition_length = .6s 10 | slide_transition_easing = ease 11 | slide_transition_rotate_y = 90deg 12 | // Safari bugs out on 3d transform-origins, so we have to fake it 13 | slide_transition_nudge_x = -100px 14 | 15 | bullet_transition_length = .3s 16 | bullet_transition_easing = ease 17 | bullet_transition_translate_x = 16px 18 | 19 | // base 20 | 21 | * 22 | box-sizing: border-box 23 | margin: 0 24 | padding: 0 25 | 26 | @media print 27 | * 28 | -webkit-print-color-adjust: exact 29 | 30 | @page 31 | size: landscape 32 | margin: 0 33 | 34 | // bespoke.js layout styles 35 | 36 | .bespoke-parent 37 | transition: background slide_transition_length slide_transition_easing 38 | position: absolute 39 | top: 0 40 | bottom: 0 41 | left: 0 42 | right: 0 43 | overflow: hidden 44 | @media print 45 | overflow: visible 46 | position: static 47 | 48 | .bespoke-theme-cube-slide-parent 49 | position: absolute 50 | top: 0 51 | left: 0 52 | right: 0 53 | bottom: 0 54 | perspective: 600px 55 | 56 | .bespoke-slide 57 | transition: 58 | transform slide_transition_length slide_transition_easing, 59 | opacity slide_transition_length slide_transition_easing, 60 | background slide_transition_length slide_transition_easing 61 | transform-origin: 50% 50% 0 62 | backface-visibility: hidden 63 | display: flex 64 | flex-direction: column 65 | justify-content: center 66 | align-items: center 67 | text-align: center 68 | width: slide_width 69 | height: slide_height 70 | position: absolute 71 | top: 50% 72 | margin-top: (slide_height / 2) * -1 73 | left: 50% 74 | margin-left: (slide_width / 2) * -1 75 | background: slide_background 76 | padding: 40px 77 | border-radius: 0 78 | @media print 79 | zoom: 1 !important // disable bespoke-scale 80 | height: 743px // seems to correspond with an A4, landscape page height 81 | width: 100% 82 | page-break-before: always 83 | position: static 84 | margin: 0 85 | transition: none 86 | 87 | .bespoke-before 88 | transform: translateX(slide_transition_nudge_x * -1) translateX(slide_width / -2) rotateY(slide_transition_rotate_y * -1) translateX(slide_width / -2) 89 | @media print 90 | transform: none 91 | 92 | .bespoke-after 93 | transform: translateX(slide_transition_nudge_x) translateX(slide_width / 2) rotateY(slide_transition_rotate_y) translateX(slide_width / 2) 94 | @media print 95 | transform: none 96 | 97 | .bespoke-inactive 98 | opacity: 0 99 | pointer-events: none 100 | @media print 101 | opacity 1 102 | 103 | .bespoke-active 104 | opacity: 1 105 | 106 | // bespoke-bullet styles 107 | 108 | .bespoke-bullet 109 | transition: all bullet_transition_length bullet_transition_easing 110 | @media print 111 | transition: none 112 | 113 | .bespoke-bullet-inactive 114 | opacity: 0 115 | li& 116 | transform: translateX(bullet_transition_translate_x) 117 | @media print 118 | transform: none 119 | @media print 120 | opacity: 1 121 | 122 | .bespoke-bullet-active 123 | opacity: 1 124 | 125 | // bespoke-scale styles 126 | 127 | .bespoke-scale-parent 128 | perspective: 600px 129 | position: absolute 130 | top: 0 131 | left: 0 132 | right: 0 133 | bottom: 0 134 | pointer-events: none 135 | .bespoke-active 136 | pointer-events: auto 137 | @media print 138 | transform: none !important 139 | 140 | // bespoke-progress styles 141 | 142 | .bespoke-progress-parent 143 | position: absolute 144 | top: 0 145 | left: 0 146 | right: 0 147 | height: 2px 148 | @media only screen and (min-width: 1366px) 149 | height: 4px 150 | @media print 151 | display: none 152 | 153 | .bespoke-progress-bar 154 | transition: width .6s ease 155 | position: absolute 156 | height: 100% 157 | background: #0089f3 158 | border-radius: 0 4px 4px 0 159 | 160 | // bespoke-backdrop styles 161 | 162 | .spaceballs 163 | background: url('../images/spaceballs.jpg') center 164 | background-size: contain 165 | background-repeat: no-repeat 166 | background-color: black 167 | 168 | .nodeconfeu 169 | background-image: url('../images/nodeconfeu-2013-top.jpg') 170 | background-size: cover 171 | 172 | .menodeconfeu 173 | background-image: url('../images/me-nodeconf-eu-2013.jpg') 174 | background-size: cover 175 | 176 | .adapters 177 | background-image: url('../images/adapters.jpg') 178 | background-size: cover 179 | 180 | .dragon-scroll 181 | background-image: url('../images/dragon-scroll.jpg') 182 | background-size: cover 183 | 184 | .steps 185 | background-image: url('../images/steps.jpg') 186 | background-size: cover 187 | 188 | .wv 189 | background-image: url('../images/wv.jpg') 190 | background-size: cover 191 | 192 | .snail 193 | background-image: url('../images/snail.jpg') 194 | background-size: cover 195 | 196 | .extreme 197 | background-image: url('../images/extreme.jpg') 198 | background-size: cover 199 | 200 | .lamp 201 | background-image: url('../images/lamp.jpg') 202 | background-size: cover 203 | 204 | .tools 205 | background-image: url('../images/tools.jpg') 206 | background-size: cover 207 | 208 | .fire 209 | background-image: url('../images/fire.jpg') 210 | background-size: cover 211 | 212 | .integration 213 | background-image: url('../images/integration.jpg') 214 | background-size: cover 215 | 216 | .monolith 217 | background-image: url('../images/monolith.jpg') 218 | background-size: cover 219 | 220 | .stonehenge 221 | background-image: url('../images/stonehenge.JPG') 222 | background-size: cover 223 | 224 | .ecosystem-impact 225 | background-image: url('../images/ecosystem-impact.jpg') 226 | background-size: cover 227 | 228 | .piggy 229 | background-image: url('../images/piggy.jpg') 230 | background-size: cover 231 | 232 | .five-euro 233 | background-image: url('../images/five-euro.jpg') 234 | background-size: cover 235 | 236 | .london 237 | background-image: url('../images/london.jpg') 238 | background-size: cover 239 | 240 | .nap-mp 241 | background-image: url('../images/nap-mp.jpg') 242 | background-size: cover 243 | 244 | .pines 245 | background-image: url('../images/pines.jpg') 246 | background-size: cover 247 | 248 | .kay 249 | background-image: url('../images/alan-kay.jpg') 250 | background-size: cover 251 | 252 | .magic 253 | background-image: url('../images/magic.gif') 254 | background-size: cover 255 | 256 | .node-desktop 257 | background-image: url('../images/node-desktop.png') 258 | background-size: cover 259 | 260 | .upring-events 261 | background-image: url('../images/upring-events.jpeg') 262 | background-size: contain 263 | background-repeat: no-repeat 264 | background-position: center 265 | 266 | .hole 267 | background-image: url('../images/hole.jpg') 268 | background-size: cover 269 | 270 | .bespoke-backdrop 271 | position: absolute 272 | top: 0 273 | left: 0 274 | right: 0 275 | bottom: 0 276 | transform: translateZ(0) 277 | transition: opacity slide_transition_length slide_transition_easing 278 | opacity: 0 279 | z-index: -1 280 | &-active 281 | opacity: 1 282 | 283 | // prism styles 284 | 285 | pre 286 | padding: 26px !important 287 | border-radius: 8px 288 | 289 | // content styles 290 | 291 | body 292 | font-family: helvetica, arial, sans-serif 293 | font-size: 18px 294 | color: #404040 295 | 296 | h1 297 | font-size: 72px 298 | line-height: 82px 299 | letter-spacing: -2px 300 | margin-bottom: 16px 301 | 302 | h2 303 | font-size: 42px 304 | letter-spacing: -1px 305 | margin-bottom: 8px 306 | 307 | h3 308 | font-size: 24px 309 | font-weight: normal 310 | margin-bottom: 24px 311 | color: #606060 312 | 313 | hr 314 | visibility: hidden 315 | height: 20px 316 | 317 | ul 318 | list-style: none 319 | 320 | li 321 | margin-bottom: 12px 322 | 323 | p 324 | margin: 0 100px 12px 325 | line-height: 22px 326 | 327 | a 328 | color: #0089f3 329 | text-decoration: none 330 | 331 | .full-imgs 332 | img 333 | width: 100% 334 | height: 100% 335 | 336 | .sensortag 337 | background-image: url('../images/sensortag.jpg') 338 | background-size: cover 339 | 340 | .sensortag-slide 341 | background: transparent !important 342 | color: white 343 | 344 | button 345 | padding: 10px 346 | 347 | .transgray 348 | background: rgba(0,0,0,0.3) !important 349 | h3 350 | color: white 351 | 352 | .transwhite 353 | background: rgba(255,255,255,0.8) !important 354 | 355 | .black 356 | background: black !important 357 | 358 | .trans 359 | background: rgba(0,0,0,0) !important 360 | 361 | .copyright 362 | font-size: 10px 363 | text-align: right 364 | position: absolute 365 | bottom: 0px 366 | right: 20px; 367 | margin: 0px 368 | padding: 0px 369 | opacity: 0.5 370 | 371 | a 372 | color: #404040 373 | 374 | img 375 | height: 10px; 376 | --------------------------------------------------------------------------------