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

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

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

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

Ubuntu 18.04

ソースビルド

結果から言うと、断念したって。

とにかく動かしたい人は、Dockerコンテナへ

apt

結果から言うと、1つのUbuntu上で複数のRiakサーバを動かすことができず、断念したって。

とにかく動かしたい人は、Dockerコンテナへ

Installing on Debian and Ubuntu

(1)署名キーを追加しました。

$ curl https://packagecloud.io/gpg.key | sudo apt-key add -

(2)apt-transport-httpsをインストールしました。すでにインストール済みでした。

$ sudo apt install -y apt-transport-https パッケージリストを読み込んでいます... 完了 依存関係ツリーを作成しています 状態情報を読み取っています... 完了 apt-transport-https はすでに最新バージョン (1.6.12) です。

(3)/etc/apt/sources.list.d/basho.listを作成保存しました。

# this file was generated by packagecloud.io for # the repository at https://packagecloud.io/basho/riak deb https://packagecloud.io/basho/riak/ubuntu/ precise main deb-src https://packagecloud.io/basho/riak/ubuntu/ precise main

(4)apt updateしたところ、さきほど追加したbasho.listでエラー。

$ sudo apt update (省略) エラー:6 https://packagecloud.io/basho/riak/ubuntu precise InRelease 公開鍵を利用できないため、以下の署名は検証できませんでした: NO_PUBKEY 960B2B2623A0BD5D (省略)
【Ubuntu】 apt update時のGPGエラー対処
Ubuntuでapt update時にGPGエラーが発生していて、なんとなく今まで放置していたのですが、この度、対処することにしましたので備忘録として残しておきます。

を参考に、公開鍵をインポートしました。

$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 960B2B2623A0BD5D Executing: /tmp/apt-key-gpghome.VpBcQ7aSFb/gpg.1.sh --keyserver keyserver.ubuntu.com --recv-keys 960B2B2623A0BD5D gpg: 鍵E3D48452E86B579B: 公開鍵"https://packagecloud.io/basho/riak (https://packagecloud.io/docs#gpg_signing) <support@packagecloud.io>"をインポートしました gpg: 処理数の合計: 1 gpg: インポート: 1

あらためて、apt updateしました。

$ sudo apt update

(5)riakをインストールしました。

$ sudo apt install riak

(5)riakを起動しました。ulimitを65536にしたほうがいいよ、と警告が表示されました。

$ riak version 2.2.3 $ sudo riak start !!!! !!!! WARNING: ulimit -n is 1024; 65536 is the recommended minimum. !!!! $ sudo riak stop
Riak インストール手順(インストールと動作確認) - Qiita
riak 2.0のインストール あまり世間で騒がれていないのですが、このriak かなり使いやすいです インストール簡単 クラスタリング簡単 障害対応簡単 の三拍子揃っています ここでは、ひとまずインストールの手順だけ...

の「前準備」を参考に、/etc/security/limits.d/riak.conf を新規保存し、OSを再起動しました。

root soft nofile 65536 root hard nofile 65536 riak soft nofile 65536 riak hard nofile 65536

(6)riakを起動しました。

$ sudo riak start

http://localhost:8098/ にアクセスすると、それらしいものが表示されたので、動いているようです。単独の1台では動きました。

書籍では、Riak 1.0.2をソースビルドして3台のRiakサーバを動かしているよ。

3台構成で試したいわ

ApacheのVirtualHostのように、ポート番号を変えて複数サーバを動かしたいのですが、方法がわかりません。

Dockerコンテナを3台動かしてみたら?

はい、DockerコンテナにRiakサーバをインストールして、Dockerコンテナを3台動かしたほうがいいかもしれません。

探してみると、Riak開発元のbashoがdockerイメージを公開していました!

Dockerイメージ

Docker Hub

Riak開発元のbashoがdockerイメージを公開していました。

docker-compose.yml

作業するディレクトリは ~/projects/personals/study_7db/riak/ です。

Docker Hubページでdocker-compose.ymlの例が説明されています。このとおりに新規保存します。

version: "2" services: coordinator: image: basho/riak-kv ports: - "8087:8087" - "8098:8098" environment: - CLUSTER_NAME=riakkv labels: - "com.basho.riak.cluster.name=riakkv" volumes: - schemas:/etc/riak/schemas member: image: basho/riak-kv ports: - "8087" - "8098" labels: - "com.basho.riak.cluster.name=riakkv" links: - coordinator depends_on: - coordinator environment: - CLUSTER_NAME=riakkv - COORDINATOR_NODE=coordinator volumes: schemas: external: false

coordinatorとmemberの2つのサービスがあるのね。

coordinatorノードの起動

まず、coordinatorノードを起動します。書籍中のdev1に相当します。

$ sudo docker-compose up -d coordinator Creating network "riak_default" with the default driver Creating riak_coordinator_1 ... Creating riak_coordinator_1 ... done

docker-compose psを調べます。

$ docker-compose ps Name Command State Ports ----------------------------------------------------------------------------------------------------------- riak_coordinator_1 /usr/lib/riak/riak-cluster.sh Up 0.0.0.0:8087->8087/tcp, 0.0.0.0:8098->8098/tcp

http://localhost:8098/admin/#/ がコンソール画面です。

すげー

memberノードの2台起動

書籍中では、Riakの開発用サーバを3台起動しています。すでにdev1相当のcoordinatorノード1台を起動しているので、残りのdev2、dev3として、memberノードを2台起動します。

$ sudo docker-compose up -d --scale member=2 riak_coordinator_1 is up-to-date Creating riak_member_1 ... Creating riak_member_2 ... Creating riak_member_1 ... done Creating riak_member_2 ... done

docker-compose psを調べます。

$ docker-compose ps Name Command State Ports ------------------------------------------------------------------------------------------------------------- riak_coordinator_1 /usr/lib/riak/riak-cluster.sh Up 0.0.0.0:8087->8087/tcp, 0.0.0.0:8098->8098/tcp riak_member_1 /usr/lib/riak/riak-cluster.sh Up 0.0.0.0:32779->8087/tcp, 0.0.0.0:32777->8098/tcp riak_member_2 /usr/lib/riak/riak-cluster.sh Up 0.0.0.0:32778->8087/tcp, 0.0.0.0:32776->8098/tcp

3台が起動したよ!

riak_member_1 って、どうやって名前をつけたのかしら?

「riak」は、docker-compose.ymlのある作業ディレクトリ riak/ から、
「member」は、docker-compose.ymlのservicesの memberから、名前をつけているようです。

memberノードのport確認

書籍中では、Riakの開発用サーバを3台起動しています。それぞれのポート番号は次のとおりです。

  • dev/dev1/bin/riakを起動して、localhost:8091
  • dev/dev2/bin/riakを起動して、localhost:8092
  • dev/dev3/bin/riakを起動して、localhost:8093

書籍のRiakのバージョンは?

書籍のRiakは1.0.2、aptインストールしたRiakは2.2.3、dockerイメージのRiakも2.2.3でした。1.0.2をソースビルドすると、devディレクトリができるようです。

今回のdocker環境では、dev1のポートは、8098です。

dev2、dev3のポート番号を調べるために、さきほどのdocker-compose ps結果を見てましょう。「riak_member_1」のPorts列で「8098/tcp」を探してください。

「0.0.0.0:32777->8098/tcp」となっています。この「0.0.0.0:32777」の「32777」がdockerをホストしている側のポート番号です。つまり、riak_member_1は、localhost:32777でアクセスできます。

memberノードのコンソール画面はあるのかな?

http://localhost:32777/admin/#/にアクセスすると、memberノードのコンソール画面が表示されます。

32777や32776は固定なのかしら?

dev1の8098は固定ですが、dev2の32777やdev3の32776は、docker-compose up するたびに変わります。docker-compose upしたら確認してください。

書籍中では、dev2のポート8092は、32777で読み替えます。dev3のポート8093は、32776で読み替えます。読み替えと入力が面倒そうなので、変数に設定しておきます。

$ dev1=http://localhost:8098 $ dev2=http://localhost:32777 $ dev3=http://localhost:32776

毎回、ポートを調べて、変数を設定するのは面倒だな

dockerhubページの
HOST:PORT Discoveryの項目に
それらしいシェルスクリプトがあるわよ

はい、そのシェルスクリプトを参考にして、「dev1、dev2、dev3の設定」をするシェルスクリプトを作りました。jqコマンドが必要です。

7db_discover_dev.sh

#!/bin/bash -uex CLUSTER_NAME=riakkv function riak_port () { docker_name=$1 container_id=$(docker ps -q -f label=com.basho.riak.cluster.name=$CLUSTER_NAME -f name=$docker_name) arr=($(docker inspect $container_id | jq -r '.[] | .NetworkSettings.Ports."8098/tcp"[0].HostPort')) port=${arr[0]} echo $port } docker_name_arr=("coordinator_1" "member_1" "member_2") i=1 for docker_name in ${docker_name_arr[@]} do port=$(riak_port $docker_name) name=dev$i value="http://localhost:$port" eval $name=$value echo $name=$value i=$(($i + 1)) done

sourceコマンドで、現在のシェル環境に適用します。

$ source ./7db_discover_dev.sh dev1=localhost:8098 dev2=localhost:32777 dev3=localhost:32776 $ echo $dev1 localhost:8098 $ echo $dev2 localhost:32777 $ echo $dev3 localhost:32776

WindowsやMac OS XでもRiak3台構成を試せるわね

書籍内コードを試してみる

書籍中コードの
http://localhost:8091 は、$dev1 で読み替えます。
http://localhost:8092 は、$dev2 で読み替えます。
http://localhost:8093 は、$dev3 で読み替えます。

書籍のRiakサーバ書籍のURL読み替える変数
1台目 dev1localhost:8091$dev1
2台目 dev2localhost:8092$dev2
3台目 dev3localhost:8093$dev3

p52、ノードにPingを打ってみよう

書籍では、

$ curl http://localhost:8091/ping OK $ curl http://localhost:8092/ping OK $ curl http://localhost:8093/ping OK

となっているところを、次のように読み替えて、実行します。

$ curl $dev1/ping OK $ curl $dev2/ping OK $ curl $dev3/ping OK

p53、何かをPUTしてみよう。

$ curl -v -X PUT $dev1/riak/favs/db -H "Content-type: text/html" -d "<html><body><h1>My new favorite DB is RIAK</h1></body></html>" * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 8098 (#0) > PUT /riak/favs/db HTTP/1.1 > Host: localhost:8098 > User-Agent: curl/7.58.0 > Accept: */* > Content-type: text/html > Content-Length: 61 > * upload completely sent off: 61 out of 61 bytes < HTTP/1.1 204 No Content < Vary: Accept-Encoding < Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) < Date: Sat, 18 Apr 2020 15:58:22 GMT < Content-Type: text/html < Content-Length: 0 < * Connection #0 to host localhost left intact

http://localhost:8098/riak/favs/db を開くと、「My new favorite DB is RIAK」と表示されました。

p60 hotel.rb

Riakクライアントライブラリの仕様が変更されていて、p60のhotel.rbのままでは動きませんでした。

$ ruby hotel.rb Traceback (most recent call last): 2: from hotel_org.rb:14:in `<main>' 1: from hotel_org.rb:14:in `new' /home/aoki/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/riak-client-2.6.0/lib/riak/client.rb:118:in `initialize': [:http_port] are not valid options for Client.new (ArgumentError)

14行目のRiak::Client.newを修正します。

client = Riak::Client.new(:nodes => [ {:host => 'localhost', :pb_port => 8087} ])

8098じゃないの?

8098を試しましたが、Riak::Client.newで止まったままでした。8087で動きました。8098はhttpアクセスするポート、8087はもう一つのポート(protocol buffer用)です。

実行してみると、別のエラーが。

$ ruby hotel.rb Making rooms 100 - 200 Traceback (most recent call last): 5: from hotel_org.rb:19:in `<main>' 4: from hotel_org.rb:19:in `each' 3: from hotel_org.rb:23:in `block in <main>' 2: from hotel_org.rb:23:in `each' 1: from hotel_org.rb:32:in `block (2 levels) in <main>' /home/aoki/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/riak-client-2.6.0/lib/riak/robject.rb:150:in `store': invalid_argument 101 is not a String (ArgumentError)

"101"は、部屋番号で、キー名として使っています。Riak::RObject.new()の2つ目の引数です。

25行目の整数を文字列に変換します。

ro = Riak::RObject.new(bucket, (current_rooms_block + room).to_s(10))

以上をまとめたのが、次のhotel.rb です。

#--- # 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. #--- # generate loads and loads of rooms with random styles and capacities require 'rubygems' require 'riak' STYLES = %w{single double queen king suite} client = Riak::Client.new(:nodes => [ {:host => 'localhost', :pb_port => 8087} ]) bucket = client.bucket('rooms') for floor in 1..100 current_room_block = floor * 100 puts "Making rooms #{current_room_block} - #{current_room_block + 100}" for room in 1...100 room_id = (current_room_block + room) style = STYLES[rand(STYLES.length)] capacity = rand(8) + 1 puts "room_id #{room_id} #{style} #{capacity}" ro = Riak::RObject.new(bucket, room_id.to_s(10)) ro.content_type = "application/json" ro.data = {'style' => style, 'capacity' => capacity} ro.store end end

rubyに慣れていないから、泣きそうだったわね

p80 事前/事後コミットフック

post commit hookは、書籍のRiak 1.0.2の時点でも、現在の 2.2.3でも、Erlangでしか書けません。

pre commit hookは、書籍のRiak 1.0.2では、JavaScriptで書いています。2.2.3でもJavaScriptでできるかもしれないと思い、いろいろ試しましたが、できませんでした。

試したこと1

1.0.2のapp.configを /etc/riak/app.config にコピーして、riak stop; riak start

タイムアウトで起動しませんでした。

試したこと2
1.0.2のapp.configを /etc/app.config にコピーして、riak stop; riak start

起動しましたが、/etc/app.configを読んでいないようです。

試したこと3

p80の中ほどの「js_source_dir」は、1.0.2のapp.configにあります。

{riak_kv, [ {js_source_dir, "/tmp/js_source"} ]}

この部分だけを /etc/riak/advanced.confif に挿入して、riak stop; riak start

起動しました。http://localhost:8098/admin/#/cluster/default/ops → Individual Node Details → riak@192.168.80.2 → Config Files → advanced.config を表示すると、js_source_dirも表示されていました。advanced.configをそのまま表示しているだけかもしれません。

docker-compose.ymlのvolumesで、./js_source:/tmp/js_source を設定し、./js_source/の下に、p80のmy_validators.js を保存しました。dockerでbashログインして、/tmp/js_source/my_validators.js が見えることを確認しました。

animalsバケットにprecommitを設定しました。

$ curl -X PUT $dev1/riak/animals \ -H "Content-type:application/json" \ -d '{"props":{"precommit":[{"name": "good_score}]}}'

scoreの値に関係なく、常に403でした。precommitに good_sourceを設定したものの、good_sourceを読み込めていないような気がします。

$ curl -X PUT $dev1/riak/animals/bruiser -H "Content-type:application/json" -d '{"score": 1}' HTTP/1.1 403 Forbidden Vary: Accept-Encoding Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) Date: Sat, 25 Apr 2020 05:33:41 GMT Content-Type: text/plain Content-Length: 31

試したこと4

map関数やreduce関数には、languageとsourceでJavaScript関数を定義できました。それを真似て、precommit関数に、languageやsourceを設定しました。

$ curl -X PUT $dev1/riak/animals \ -H "Content-type:application/json" \ -d '{"props":{"precommit":[{"language":"javascript", "source":"function(o){return o;}"}]}}'

scoreの値に関係なく、500エラーでした。エラー内容は「invalid_hook_def」、フックの定義が間違っているとのこと。precommit関数では、languageやsourceを設定できないようです。

$ curl -i -X PUT $dev1/riak/animals/bruiser -H "Content-type: application/json" -d '{"score": 2}' HTTP/1.1 500 Internal Server Error Vary: Accept-Encoding Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) Date: Sat, 25 Apr 2020 05:10:50 GMT Content-Type: text/html Content-Length: 1277 <html><head><title>500 Internal Server Error</title></head><body><h1>Internal Server Error</h1>The server encountered an error while processing this request:<br><pre>{error, {error,badarg, [{erlang,iolist_to_binary, [{invalid_hook_def, {struct, [{<<"language">>,<<"javascript">>}, {<<"source">>,<<"function(o){return o;}">>}]}}], []},

試したこと5

mapreduceで使った、ビルトイン関数のRiak.mapValuesJsonは読み込めるかもと思い、precommit関数に設定しました。

$ curl -X PUT $dev1/riak/animals \ -H "Content-type:application/json" \ -d '{"props":{"precommit":[{"name":"Riak.mapValuesJson"}]}}'

500エラーですが、エラー内容は「invalid_return」にかわりました。

$ curl -i -X PUT $dev1/riak/animals/bruiser -H "Content-type: application/json" -d '{"score": 4}' HTTP/1.1 500 Internal Server Error Vary: Accept-Encoding Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) Date: Sat, 25 Apr 2020 05:30:30 GMT Content-Type: text/html Content-Length: 1195 <html><head><title>500 Internal Server Error</title></head><body><h1>Internal Server Error</h1>The server encountered an error while processing this request:<br><pre>{error, {error,badarg, [{erlang,iolist_to_binary, [{invalid_return, {<<"Riak.mapValuesJson">>,[{struct,[{<<"score">>,4}]}]}}], []},

試したこと6

ビルトイン関数のRiak.mapValuesJsonを、単に return {"obj"} にしてみました。
ファイルは、/usr/lib/riak/lib/riak_kv-2.1.7-0-gbd8e312/priv/mapred_builtins.js です。

mapValuesJson: function(obj) { return {"obj"}; },

残念ながら403エラー。mapred_builtins.jsを編集したことが原因のようです。

試したこと7

mapred_builtins.jsに、goodScore関数を追加してみました。
ファイルは、/usr/lib/riak/lib/riak_kv-2.1.7-0-gbd8e312/priv/mapred_builtins.js です。

var Riak = function() { return { goodScore: fucntion(obj) { return obj; }, getClassName: function(obj) {

precommit関数をRiak.goodScoreにしても、はさきほどのRiak.mapValuesJsonにしても、animals/bruiserに書き込むと、403エラー。mapred_builtins.jsを編集したことが原因のようです。

うーん、お手上げです。

p82 Riakで全文検索

Riakサーチを有効化するには、app.configのRiak Search Configを enabled, trueに設定する。

Riak 2.2.3では、/etc/riak/riak.confの「search = off」を「search = on」にすることのようです。

p83のクエリに該当するものがわからず、あきらめました。

公式ドキュメントにしたがって、インデックスを作成しました。

まず、操作を簡単にするために、dockerコンテナを1台にします。

$ sudo docker-compose down $ sudo docker-compose up -d coordinator

riak.confの「search = off」を「search = on」に置換して、riakを再起動します。

$ sudo docker exec -it riak_coordinator_1 sed -e 's/search = off/search = on/' -i.back /etc/riak/riak.conf $ sudo docker exec -it riak_coordinator_1 riak stop ok $ sudo docker exec -it riak_coordinator_1 riak start

famousインデックスを作成します。

$ curl -X PUT $dev1/search/index/famous

animasバケットタイプを新規作成します。

$ sudo docker exec -it riak_coordinator_1 riak-admin bucket-type create animals '{"props":{}}' animals created WARNING: After activating animals, nodes in this cluster can no longer be downgraded to a version of Riak prior to 2.0 $ sudo docker exec -it riak_coordinator_1 riak-admin bucket-type activate animals animals has been activated WARNING: Nodes in this cluster can no longer be downgraded to a version of Riak prior to 2.0 $ sudo docker exec -it riak_coordinator_1 riak-admin bucket-type list default (active) animals (active)

catsバケットに、famousインデックスを設定します。

$ curl -X PUT $dev1/types/animals/buckets/cats/props \ -H 'Content-Type: application/json' \ -d '{"props":{"search_index":"famous"}}'

データを登録します。

curl -XPUT $RIAK_HOST/types/animals/buckets/cats/keys/liono \ -H 'Content-Type: application/json' \ -d '{"name_s":"Lion-o", "age_i":30, "leader_b":true}' curl -XPUT $RIAK_HOST/types/animals/buckets/cats/keys/cheetara \ -H 'Content-Type: application/json' \ -d '{"name_s":"Cheetara", "age_i":28, "leader_b":false}' curl -XPUT $RIAK_HOST/types/animals/buckets/cats/keys/snarf \ -H 'Content-Type: application/json' \ -d '{"name_s":"Snarf", "age_i":43}' curl -XPUT $RIAK_HOST/types/animals/buckets/cats/keys/panthro \ -H 'Content-Type: application/json' \ -d '{"name_s":"Panthro", "age_i":36}'

name_sがLion*で検索します。

curl "$RIAK_HOST/search/query/famous?wt=json&q=name_s:Lion*" | jq { "responseHeader": { "status": 0, "QTime": 88, "params": { "q": "name_s:Lion*", "shards": "192.168.128.2:8093/internal_solr/famous", "wt": "json", "192.168.128.2:8093": "(_yz_pn:62 AND (_yz_fpn:62)) OR _yz_pn:61 OR _yz_pn:58 OR _yz_pn:55 OR _yz_pn:52 OR _yz_pn:49 OR _yz_pn:46 OR _yz_pn:43 OR _yz_pn:40 OR _yz_pn:37 OR _yz_pn:34 OR _yz_pn:31 OR _yz_pn:28 OR _yz_pn:25 OR _yz_pn:22 OR _yz_pn:19 OR _yz_pn:16 OR _yz_pn:13 OR _yz_pn:10 OR _yz_pn:7 OR _yz_pn:4 OR _yz_pn:1" } }, "response": { "numFound": 1, "start": 0, "maxScore": 1, "docs": [ { "leader_b": true, "age_i": 30, "name_s": "Lion-o", "_yz_id": "1*animals*cats*liono*10", "_yz_rk": "liono", "_yz_rt": "animals", "_yz_rb": "cats" } ] } }

age_iが 30〜で検索します。Line-o、Snarf、Panthroの3個がヒットするはずです。

curl "$RIAK_HOST/search/query/famous?wt=json&q=age_i:%5B30%20TO%20*%5D" | jq { "responseHeader": { "status": 0, "QTime": 21, "params": { "q": "age_i:[30 TO *]", "shards": "192.168.128.2:8093/internal_solr/famous", "wt": "json", "192.168.128.2:8093": "(_yz_pn:62 AND (_yz_fpn:62)) OR _yz_pn:61 OR _yz_pn:58 OR _yz_pn:55 OR _yz_pn:52 OR _yz_pn:49 OR _yz_pn:46 OR _yz_pn:43 OR _yz_pn:40 OR _yz_pn:37 OR _yz_pn:34 OR _yz_pn:31 OR _yz_pn:28 OR _yz_pn:25 OR _yz_pn:22 OR _yz_pn:19 OR _yz_pn:16 OR _yz_pn:13 OR _yz_pn:10 OR _yz_pn:7 OR _yz_pn:4 OR _yz_pn:1" } }, "response": { "numFound": 3, "start": 0, "maxScore": 1, "docs": [ { "leader_b": true, "age_i": 30, "name_s": "Lion-o", "_yz_id": "1*animals*cats*liono*10", "_yz_rk": "liono", "_yz_rt": "animals", "_yz_rb": "cats" }, { "age_i": 43, "name_s": "Snarf", "_yz_id": "1*animals*cats*snarf*4", "_yz_rk": "snarf", "_yz_rt": "animals", "_yz_rb": "cats" }, { "age_i": 36, "name_s": "Panthro", "_yz_id": "1*animals*cats*panthro*61", "_yz_rk": "panthro", "_yz_rt": "animals", "_yz_rb": "cats" } ] } }
ビッグデータを活かすデータベース技術
タイトルとURLをコピーしました