Redisをインストールするには

「7つのデータベース 7つの世界」第8章の学習のために、Redisをインストールします。

radish(大根)を連想してしまうよ...

7つのデータベース 7つの世界

  • PotgreSQL
  • Riak
  • HBase
  • MongoDB
  • CouchDB
  • Neo4j
  • Redis
Redis

書籍で使っているRedishのバージョンは、2.4

最新バージョンは、6.0.4。

インストール

Windows 10

公式のWindowsバイナリはないようです。

githubにmicrosoftarchivesの3.2.100が残っています。msiインストーラとzipファイルをダウンロードできます。

microsoftarchive/redis
Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted...

zipファイル

ここでは、zipファイルをダウンロードして、Windowsサービスではなくフォアグラウンドでredis-serverを起動してみます。

エクスプローラで、ダウンロードしたzipファイルを選択して、右クリックして、「すべて展開...」を選びます。Redis-x64-3.2.100フォルダに展開します。

コマンドプロンプトまたはPoweShellを起動して、展開したフォルダに移動します。

C:> cd Downloads\Redis-x64-3.2.100

バージョンを確認します。

C:> redis-cli --version redis-cli 3.2.100 C:> redis-server --version Redis server v=3.2.100 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=dd26f1f93c5130ee

redis-cliを起動すると、「not connected」プロンプトになってしまいました。quitで終了します。

C:> redis-cli Could not connect to Redis at 127.0.0.1:6379: 対象のコンピューターによって拒否されたため、接続できませんでした。 Could not connect to Redis at 127.0.0.1:6379: 対象のコンピューターによって拒否されたため、接続できませんでした。 not connected> quit

redis-serverを起動します。confファイルは、ファイル名に「-service」がつかない「redis.windows.conf」を指定します。

redis-serverを停止するには、Ctrl+Cを押します。

C:> redis-server redis.windows.conf _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 3.2.100 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 22904 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' [22904] 29 May 09:09:34.136 # Server started, Redis version 3.2.100 [22904] 29 May 09:09:34.139 * DB loaded from disk: 0.000 seconds [22904] 29 May 09:09:34.140 * The server is now ready to accept connections on port 6379

別のコマンドプロンプトを起動して、redis-cliを起動します。

C:> cd Downloads\Redis-x64-3.2.100 C:> redis-cli 127.0.0.1:6379>

msiインストーラ

msiインストーラで、Windowsサービスとしてインストールする手順は、次の記事を参考にしてください。
https://weblabo.oscasierra.net/redis-windows-install/
https://qiita.com/okoi/items/3bb5ae26ad559e4f39a0

Mac OS X

homebrewで6.0.4を、homebrew caskで4.0.2をインストールできます。

ここでは、homebrewの6.0.4をインストールしました。

$ brew install redis (省略) ==> Caveats To have launchd start redis now and restart at login: brew services start redis Or, if you don't want/need a background service you can just run: redis-server /usr/local/etc/redis.conf (省略)
$ redis-server --version Redis server v=6.0.4 sha=00000000:0 malloc=libc bits=64 build=992d36c7134f1fc8 $ redis-cli --version redis-cli 6.0.4

バージョンを確認します。

$ redis-server --version Redis server v=6.0.4 sha=00000000:0 malloc=libc bits=64 build=992d36c7134f1fc8 $ redis-cli --version redis-cli 6.0.4

起動方法は2種類あります。サービスとして起動する方法、フォアグラウンドで起動する方法です。

サービスとして起動する方法

$ brew services start redis ==> Successfully started `redis` (label: homebrew.mxcl.redis) $ redis-cli 127.0.0.1:6379> quit $ brew services stop redis Stopping `redis`... (might take a while) ==> Successfully stopped `redis` (label: homebrew.mxcl.redis)

フォアグラウンドで起動する方法。別のターミナルを開いて、redis-cliを起動します。フォアグラウンドのredis-serverをストップするには、Ctrl+Cを押します。

$ redis-server /usr/local/etc/redis.conf 75019:C 29 May 2020 08:52:25.243 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 75019:C 29 May 2020 08:52:25.243 # Redis version=6.0.4, bits=64, commit=00000000, modified=0, pid=75019, just started 75019:C 29 May 2020 08:52:25.243 # Configuration loaded 75019:M 29 May 2020 08:52:25.245 * Increased maximum number of open files to 10032 (it was originally set to 256). _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 6.0.4 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 75019 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 75019:M 29 May 2020 08:52:25.246 # Server initialized 75019:M 29 May 2020 08:52:25.247 * Loading RDB produced by version 6.0.4 75019:M 29 May 2020 08:52:25.247 * RDB age 8 seconds 75019:M 29 May 2020 08:52:25.247 * RDB memory usage when created 0.95 Mb 75019:M 29 May 2020 08:52:25.247 * DB loaded from disk: 0.000 seconds 75019:M 29 May 2020 08:52:25.247 * Ready to accept connections

Ubuntu 18.04

aptでインストールできます。

$ sudo apt install redis

バージョンは、4.0.9 でした。

$ redis-server --version Redis server v=4.0.9 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=9435c3c2879311f3 $ redis-cli --version redis-cli 4.0.9

サービスとしてインストールされます。インストール後、すでにサービスがスタートしていました。

$ sudo service redis stop $ sudo service redis start

試しに、サービスをストップして、redis-cliを起動してみました。redis-cliのプロンプトが「not connected>」と表示されました。

$ redis-cli Could not connect to Redis at 127.0.0.1:6379: Connection refused Could not connect to Redis at 127.0.0.1:6379: Connection refused not connected> quit

サービスがスタートしていれば、redis-cliのプロンプトは「127.0.0.1:6379>」と表示されました。

$ redis-cli 127.0.0.1:6379>

書籍内コードほか

p261 データダンプ

http://download.freebase.com/datadumps/latest/browse/book/isbn.tsv は404エラー。

http://freebase-easy.cs.uni-freiburg.de/ から、freebase-easy-14-04-14.zip をダウンロードして、"isbn-13"でgrepして、列を入れ替えて、isbn.tsv を作りました。

isbn.tsv.gz 15MB、69万件

p262のisbn.rbでインポートしたところ、28.45秒。isbn_pipelined.rbでインポートしたところ、9.4秒。パイプラインで改善できるとの説明どおり、処理時間は約1/3になりました。

p270 データの投入

http://download.freebase.com/datadumps/latest/browse/music/group_membership.tsv は404エラー

http://freebase-easy.cs.uni-freiburg.de/ から、freebase-easy-14-04-14.zip をダウンロードして、もろもろ処理をして、group_membership.tsv を作りました。

group_membership.tsv 2MB、15万件

p271 フェーズ1: データ変換

最新のnodejsで動くように、pre_populate.jsの一部を書き直しました。

/*** * Excerpted from "Seven Databases in Seven Weeks", * published by The Pragmatic Bookshelf. * Copyrights apply to this code. It may not be used to create training material, * courses, books, articles, and the like. Contact us if you are in doubt. * We make no guarantees that this code is fit for any purpose. * Visit http://www.pragmaticprogrammer.com/titles/rwdata for more book information. ***/ var // The band data file name in tab-seperated form tsvFileName = 'group_membership.tsv', // track how many file lines we've processed processedLines = 0, // standard libraries fs = require('fs'), csv = require('csv'), redis = require('redis'), // database clients redis_client = redis.createClient(6379); /** * A helper function that splits up the comma-seperated list of roles and * converts it to an array. If no valid roles exist, return an empty array. * @param string the CSV to split into a role array */ function buildRoles( string ) { var roles = string.split(','); if(roles.length === 1 && roles[0] === '') roles = []; return roles; }; /** * Utility function that increments the total number * of lines (artists) processed and outputs every 1000. */ function trackLineCount() { if( ++processedLines % 1000 === 0 ) console.log('Lines Processed: ' + processedLines); } /** * Does all heavy lifting. Loops through the CSV file * and populate Redis with the given values. */ function processData(data) { var artist = data[1]; var band = data[2]; var roles = buildRoles(data[3]); if ( band === '' || artist === '') { trackLineCount(); return true; } redis_client.sadd('band:' + band, artist); roles.forEach(function(role) { redis_client.sadd('artist:' + band + ':' + artist, role); }); trackLineCount(); } function populateRedis() { const parser = csv.parse({ delimiter: "\t", quote: false }); parser.on('error', (err) => { console.log(err.message); }); parser.on('readable', () => { var data; while (data = parser.read()) { processData(data); } }); parser.on('end', () => { console.log('Total Lines Processed: ' + processedLines); redis_client.quit(); }); fs.createReadStream(tsvFileName).pipe(parser); }; populateRedis();

populate_couch.js

/*** * Excerpted from "Seven Databases in Seven Weeks", * published by The Pragmatic Bookshelf. * Copyrights apply to this code. It may not be used to create training material, * courses, books, articles, and the like. Contact us if you are in doubt. * We make no guarantees that this code is fit for any purpose. * Visit http://www.pragmaticprogrammer.com/titles/rwdata for more book information. ***/ var // how many bands we expect to process totalBands = null, // and keep track of how many bands we have processed processedBands = 0, // The name of the couch database couchDBpath = '/bands', // standard libraries http = require('http'), redis = require('redis'), // database clients redisClient = redis.createClient(6379); /** * A helper function that builds a good CouchDB key * @param string the unicode string being keyified */ function couchKeyify(string) { // remove bad chars, and disallow starting with an underscore return string. replace(/[\t \?\#\\\-\+\.\,'"()*&!\/]+/g, '_'). replace(/^_+/, ''); }; /* * Keep track of the number of bands processed, output every 1000 loaded, * and close the Redis client when we've loaded them all. */ function trackLineCount( increment ) { processedBands += increment; // output once every 1000 lines if (processedBands % 1000 === 0) { console.log('Bands Loaded: ' + processedBands); } // close the Redis Client when complete if (totalBands <= processedBands) { console.log('Total Bands Loaded: ' + processedBands); redisClient.quit(); } }; /* * Post one or more documents into CouchDB. * @param url is where we POST to. * @param docString a stringified JSON document. * @param count the number of documents being inserted. */ function postDoc(path, docsString, count) { console.log("postDoc " + path + " " + count); const options = { host: '127.0.0.1', port: 5984, method: 'POST', path: path, headers: { 'Content-Type' : 'application/json' } }; const req = http.request(options, (res) => { res.on('data', (chunk) => { trackLineCount(count); }); res.on('error', (err) => { console.log('response.error: ' + err.message); }); }); req.on('error', (err) => { console.log('request.error: ' + err.message); }); req.write(docsString); req.end(); }; /* * Loop through all of the bands populated in Redis. We expect * the format of each key to be 'band:Band Name' having the value * as a set of artist names. The artists each have the list of roles * they play in each band, keyed by 'artist:Band Name:Artist Name'. * The band name, set of artists, and set of roles each artist plays * populates the CouchDB documents. eg: { name:"Nirvana", artists:[{ name: "Kurt Cobain", roles:["Lead Vocals", "Guitar"] },...] } */ function populateBands() { // First, create the couch database const options = { host: '127.0.0.1', port: 5984, method: 'PUT', path: couchDBpath, }; const req = http.request(options).end(); redisClient.keys('band:*', (error, bandKeys) => { totalBands = bandKeys.length; var readBands = 0, bandsBatch = []; bandKeys.forEach((bandKey) => { // substring of 'band:'.length gives us the band name var bandName = bandKey.substring(5); redisClient.smembers(bandKey, (error, artists) => { // batch the Redis calls to get all artists' information at once var roleBatch = []; artists.forEach((artistName) => { roleBatch.push([ 'smembers', 'artist:' + bandName + ':' + artistName ]); }); // batch up each band member to find the roles they play redisClient .multi(roleBatch) .exec((err, roles) => { var i = 0, artistDocs = []; // build the artists sub-documents artists.forEach( function(artistName) { artistDocs.push({ name: artistName, role : roles[i++] }); }); // add this new band document to the batch to be executed later bandsBatch.push({ _id: couchKeyify( bandName ), name: bandName, artists: artistDocs }); // keep track of the total number of bands read readBands += 1; // upload batches of 50 values to couch, or the remaining values left if (bandsBatch.length >= 50 || totalBands - readBands == 0) { postDoc( couchDBpath + '/_bulk_docs', JSON.stringify({ docs : bandsBatch }), bandsBatch.length ); // empty out the batch array to be filled again bandsBatch = []; } }); }); }); }); }; // expose couchKeyify function exports.couchKeyify = couchKeyify; // start populating bands if running as main script if(!module.parent) { populateBands(); }

bands.js

/*** * Excerpted from "Seven Databases in Seven Weeks", * published by The Pragmatic Bookshelf. * Copyrights apply to this code. It may not be used to create training material, * courses, books, articles, and the like. Contact us if you are in doubt. * We make no guarantees that this code is fit for any purpose. * Visit http://www.pragmaticprogrammer.com/titles/rwdata for more book information. ***/ var port = 8080, jsonHeader = {'Content-Type':'application/json'}, // standard libraries http = require('http'), redis = require('redis'), bricks = require('bricks'), mustache = require('mustache'), fs = require('fs'), // custom libraries couchUtil = require('./populate_couch.js'), neo4j = require('./neo4j_caching_client.js'), // database clients //couchClient = http.createClient(5984, 'localhost'), neo4jClient = neo4j.createClient(), redisClient = redis.createClient(6379); var gremlin = neo4jClient.runGremlin; /** * A convenience function for wrapping the * reading of JSON reponse data chunks. * @param response A Node HTTP response object. * @param callback the function to populate and call on completion. */ function processBuffer( response, callback ) { var buffer = ''; response.on('data', function(chunk) { buffer += chunk; }); response.on('end', function() { if(buffer === '') buffer = 'null'; callback( JSON.parse(buffer) ); }); }; /* * Post one or more documents into CouchDB. * @param url is where we POST to. * @param docString a stringified JSON document. * @param count the number of documents being inserted. */ function getCouchDoc( path, httpResponse, callback ) { var options = { hsot: "127.0.0.1", port: 5984, method: 'GET', path: path, headers: { jsonHeader } } var request = http.request(options); request.end(); request.on('response', function( response ) { if( response.statusCode != 200 ) { writeTemplate( httpResponse, '', { message: "Value not found" } ); } else { processBuffer( response, function( couchObj ) { callback( couchObj ); }); } }). on('error', function(e) { console.log('postDoc Got error: ' + e.message); }); }; /** * Wraps a block of HTML with a standard template. HTML lives in template.html. * @innerHtml populates the body of the template */ function htmlTemplate( innerHtml ) { var file_data = fs.readFileSync( 'template.html', 'utf8' ); return file_data.replace("[[YIELD]]", innerHtml); }; function writeTemplate( response, innerHtml, values ) { response.write( mustache.render(htmlTemplate(innerHtml), values)); response.end(); } // A Nodejs web app utility setup appServer = new bricks.appserver(); // attach request plugin to easily extract params appServer.addRoute("^/", appServer.plugins.request); /* * Just display a blank form if no band is given. */ appServer.addRoute("^/$", function(req, res) { writeTemplate(res, '', { message: "Find a band" }); }); /* * Accepts a band name and displays all artists in the band. * Also displays a list of suggested bands where at least * one artist has played at one time. */ appServer.addRoute("^/band$", function(req, res) { var bandName = req.param('name'), bandNodePath = '/bands/' + couchUtil.couchKeyify( bandName ), membersQuery = 'g.V[[name:"'+bandName+'"]]' + '.out("member").in("member").uniqueObject.name'; getCouchDoc( bandNodePath, res, function( couchObj ) { gremlin( membersQuery, function(graphData) { var artists = couchObj && couchObj['artists']; var values = { band: bandName, artists: artists, bands: graphData }; var body = '<h2>{{band}} Band Members</h2>'; body += '<ul>{{#artists}}'; body += '<li><a href="/artist?name={{name}}">{{name}}</a></li>'; body += '{{/artists}}</ul>'; body += '<h3>You may also like</h3>'; body += '<ul>{{#bands}}'; body += '<li><a href="/band?name={{.}}">{{.}}</a></li>'; body += '{{/bands}}</ul>'; writeTemplate( res, body, values ); }); }); }); /* * Accepts an artist name and displays band and role information */ appServer.addRoute("^/artist$", function(req, res) { var artistName = req.param('name'), rolesQuery = 'g.V[[name:"'+artistName+'"]].out("plays").role.uniqueObject', bandsQuery = 'g.V[[name:"'+artistName+'"]].in("member").name.uniqueObject'; gremlin( rolesQuery, function(roles) { gremlin( bandsQuery, function(bands) { var values = { artist: artistName, roles: roles, bands: bands }; var body = '<h3>{{artist}} Performs these Roles</h3>'; body += '<ul>{{#roles}}'; body += '<li>{{.}}</li>'; body += '{{/roles}}</ul>'; body += '<h3>Play in Bands</h3>'; body += '<ul>{{#bands}}'; body += '<li><a href="/band?name={{.}}">{{.}}</a></li>'; body += '{{/bands}}</ul>'; writeTemplate( res, body, values ); }); }); }); /* * A band name search. Used for autocompletion. */ appServer.addRoute("^/search$", function(req, res) { var query = req.param('term'); redisClient.keys("band-name:"+query+"*", function(error, keys) { var bands = []; keys.forEach(function(key){ bands.push(key.replace("band-name:", '')); }); res.write( JSON.stringify(bands) ); res.end(); }); }); // catch all unknown routes with a 404 appServer.addRoute(".+", appServer.plugins.fourohfour); appServer.addRoute(".+", appServer.plugins.loghandler, { section: "final" }); // start up the server console.log("Starting Server on port " + port); appServer.createServer().listen(port);
タイトルとURLをコピーしました