0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

現代のWebアプリケーション開発では、従来のリレーショナルデータベース(RDB)だけでは対応しきれない課題が数多く存在いるのは開発者の皆さんならいろいろ頭を悩ませているのではないですか?
大量のデータ、多様なデータ形式、高いスケーラビリティ、リアルタイム性など、これらの要求に応えるため生まれたのがNoSQLデータベースです。

この記事では以下の内容を執筆しました。

NoSQLの基礎概念と必要性
4つの主要なNoSQLタイプの特徴と使い分け
Docker環境でのNoSQL構築方法
Go言語による実践的なCRUD操作
各NoSQLの選定基準と適用場面
クラウドサービス(AWS/GCP)の活用方法

🛠️ 必要な環境

ここでは以下の環境が整っていることを前提としています:

  • Go 1.19以上
  • Docker & Docker Compose

📚 目次

  1. NoSQLとは何か?
  2. RDB vs NoSQL: 何が違うのか?
  3. NoSQLの4つの主要タイプ
  4. ローカル環境構築 (Docker編)
  5. ローカル環境でのGo言語NoSQL実践
  6. NoSQLデータベースへの接続・管理・操作方法
  7. NoSQLの選定方法
  8. クラウドNoSQLサービスのローカル実装例
  9. クラウドNoSQLへの接続・管理・操作方法
  10. クラウドプラットフォームのNoSQLサービス

他のチートシート

git/gh コマンド(gitコマンド以外にもgitの概念も書いてあります)

lazygit

Docker コマンド(dockerコマンド以外にもdockerの概念の記事へのリンクもあります)

ステータスコード

TypeScript

Go/Gorm

testing/gomock

C#/.NET/Unity

Ruby・Ruby on Rails

SQL

Vim

プルリクエスト・マークダウン記法チートシート

ShellScript(UNIX/Linuxコマンド)チートシート

ファイル操作コマンドチートシート

VSCode Github Copilot拡張機能

OpenAI Assistants API

GitHub API

変数・関数(メソッド)・クラス命名規則

他のシリーズ記事

チートシート
様々な言語,フレームワーク,ライブラリなど開発技術の使用方法,基本事項,応用事例を網羅し,手引書として記載したシリーズ

git/gh,lazygit,docker,vim,typescript,プルリクエスト/マークダウン,ステータスコード,ファイル操作,OpenAI AssistantsAPI,Ruby/Ruby on Rails のチートシートがあります.以下の記事に遷移した後,各種チートシートのリンクがあります.

TypeScriptで学ぶプログラミングの世界
プログラミング言語を根本的に理解するシリーズ

情報処理技術者試験合格への道 [IP・SG・FE・AP]
情報処理技術者試験に出題されるコンピュータサイエンス用語の紹介や単語集

IAM AWS User クラウドサービスをフル活用しよう!
AWSのサービスを例にしてバックエンドとインフラ開発の手法を説明するシリーズです.

AWS UserのGCP浮気日記
GCPの様子をAWSと比較して考えてみるシリーズ

Project Gopher: Unlocking Go’s Secrets
Go言語や標準ライブラリの深掘り調査レポート

1. NoSQLとは何なんだ? 🤔

NoSQL誕生の背景

現代のWebアプリケーションは、従来のデータベースでは想定されていなかった課題に直面していますね。皆さんも感じたことがありませんか?

  • データ量の爆発的増加: SNS、IoT、eコマースなどから生まれる大量のデータ
  • 多様なデータ形式: テキスト、画像、動画、JSON、ログデータなど
  • リアルタイム性の要求: ユーザーが即座にレスポンスを期待する時代
  • グローバル分散: 世界中のユーザーに低遅延でサービスを提供する必要性

これらの要求に対して、1970年代から続く伝統的なRDB(リレーショナルデータベース)では限界が見えてきました。例えば:

  • ユーザー数が数百万人を超えるSNSで友人関係を管理する
  • 毎秒数万件のセンサーデータをリアルタイムで処理する
  • 世界中に分散したサーバー間でデータを同期する
  • 頻繁に変更される商品カタログのスキーマに柔軟に対応する

こうした背景から生まれたのが「NoSQL」と呼ばれるデータベースです。

NoSQLの定義と特徴

NoSQLは「Not Only SQL」の略であり、従来のRDB(リレーショナルデータベース)以外のデータベース管理システム全般を指す言葉です。「SQLを使わない」という意味ではなく、「SQLだけではない」という意味で解釈されることが多いみたいです。

RDBが厳格なスキーマ(テーブル構造)とSQLという言語でデータを操作するのに対し、NoSQLはもっと柔軟なデータモデルとスケーラビリティ(拡張性)を特徴としています。特に、ビッグデータやリアルタイムWebアプリケーションのような、大量かつ多様なデータを高速に扱う必要がある場面でその真価を発揮するのでそんなWebアプリを作りたいという方は必見のデータベースです!

NoSQLの主な特徴:

  • 柔軟なスキーマ: 事前に厳密なテーブル定義をする必要がなく、アプリケーションの変更に追従しやすい
  • 水平スケーリング(スケールアウト): 高価なサーバー1台の性能を上げる(スケールアップ)のではなく、安価なサーバーを多数並べることで性能を向上させる
  • 高いパフォーマンス: 特定のユースケースに特化したデータモデルにより、高速な読み書きを実現する
  • 分散処理への対応: データとアクセス負荷を複数のノードに分散し、単一障害点を排除する

どんな時にNoSQLを選ぶべきか?

NoSQLは万能ではありません😭。以下のような場面で特に威力を発揮します:

NoSQLが適している場面:

  • 🚀 高速なスケーリングが必要: ユーザー数やデータ量が急激に増加する可能性がある
  • 🔄 頻繁なスキーマ変更: アジャイル開発で仕様が頻繁に変わる
  • 🌐 地理的分散: 世界中にデータセンターを持ち、低遅延を実現したい場合
  • 📊 特殊なデータモデル: グラフ構造や時系列データなど、RDBでは表現しにくいデータ

従来のRDBが適している場面:

  • 💰 厳密な整合性が必要: 金融取引など、データの不整合が許されない場合
  • 🔍 複雑なクエリが多い: JOIN や集計処理を多用する分析系システム
  • 👥 チームのSQL習熟度が高い: 既存の知識とツールを活用したい場合

2. RDB vs NoSQL: 何が違うのか?

RDBとNoSQLはどちらが良いというものではなく、適材適所で使い分けることが重要です。脳死で選定は良くないですね...

| 特徴 | RDB (Relational Database) | NoSQL (Not Only SQL) |
| : | : | : |
| データモデル | テーブル(行と列) | 多様 (キーバリュー、ドキュメント、etc.) |
| スキーマ | 固定スキーマ (事前に定義が必要) | 動的スキーマ (スキーマレス) |
| スケーリング | スケールアップ (垂直分散) | スケールアウト (水平分散) |
| 一貫性モデル | ACID特性 (強い一貫性) | BASE特性 (結果整合性) が多い |
| クエリ言語 | SQL | 専用APIや独自のクエリ言語 (CQL、Cypherなど) |
| 代表例 | MySQL、PostgreSQL、Oracle | MongoDB、Redis、Cassandra、Neo4j |

  • ACID: 原子性(Atomicity)、一貫性(Consistency)、独立性(Isolation)、永続性(Durability)の頭文字。トランザクションが確実に実行されることを保証
  • BASE: Basically Available、Soft state、Eventually consistentの頭文字。常に利用可能で、結果的に整合性が取れれば良いという考え方

3. NoSQLの4つの主要タイプ

NoSQLは単一の技術ではなく、データモデルによって大きく4つのタイプに分類されます。

a. キーバリューストア (Key-Value Store)

最もシンプルなNoSQL。文字通り、ユニークな「キー」とそれに対応する「値(バリュー)」のペアでデータを格納します。

  • 特徴: 非常に高速な読み書きが可能です。
  • 代表例: RedisAmazon DynamoDB
  • ユースケース: キャッシュ、セッション管理、リアルタイムランキングなど

データ保存形式の例:

# シンプルなキーバリューペア
user:1001:name → "山田太郎"
user:1001:email → "yamada@example.com"
user:1001:last_login → "2024-01-15T10:30:00Z"

# セッション管理
session:abc123def456 → "user_id=1001&expires=1705320600"

# キャッシュデータ
cache:user_profile:1001 → '{"name":"山田太郎","department":"開発部","role":"エンジニア"}'

# ランキングデータ (Redis Sorted Set)
game:leaderboard → {"player1": 9500, "player2": 8750, "player3": 8200}

Redisはインメモリ キーバリューストア、DynamoDBはフルマネージド NoSQLデータベースです。なお、DynamoDBはキーバリューストアとしての機能に加えて、複雑な構造を持つ項目(ドキュメント)も格納できるため、ドキュメントストアの側面も併せ持っています。

image.png

image.png

b. ドキュメントストア (Document Store)

JSONやBSON、XMLのような半構造化された「ドキュメント」単位でデータを格納します。ドキュメントは自己記述的で、柔軟な構造を持つことができます。

  • 特徴: スキーマレスで開発しやすく、ドキュメント内の特定フィールドにインデックスを貼ることも可能です。
  • 代表例: MongoDBCouchbaseAmazon DocumentDB
  • ユースケース: Webアプリケーションのバックエンド、コンテンツ管理システム、ユーザープロファイル管理

データ保存形式の例:

// ユーザープロファイル (MongoDBのBSONドキュメント)
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "田中花子",
  "email": "tanaka@example.com",
  "age": 28,
  "department": "マーケティング部",
  "skills": ["JavaScript", "Python", "データ分析"],
  "address": {
    "prefecture": "東京都",
    "city": "渋谷区",
    "postal_code": "150-0001"
  },
  "projects": [
    {
      "name": "Webサイトリニューアル",
      "status": "進行中",
      "start_date": "2024-01-01"
    }
  ],
  "created_at": ISODate("2024-01-10T09:00:00Z")
}

// 商品情報(柔軟なスキーマの例)
{
  "_id": ObjectId("507f1f77bcf86cd799439012"),
  "name": "ノートPC",
  "category": "電子機器",
  "price": 89800,
  "specifications": {
    "cpu": "Intel Core i7",
    "memory": "16GB",
    "storage": "512GB SSD",
    "display": "15.6インチ"
  },
  "reviews": [
    {"user": "user123", "rating": 5, "comment": "動作が快適です"},
    {"user": "user456", "rating": 4, "comment": "コスパが良い"}
  ],
  "in_stock": true,
  "tags": ["ビジネス", "高性能", "軽量"]
}

image.png

image.png

image.png

c. カラム指向ストア (Column-Oriented Store)

RDBが行単位でデータを格納するのとは対照的に、列(カラム)単位でデータを格納します。

  • 特徴: 特定のカラムに対する集計や分析が非常に高速で、大量の書き込み処理に強いです。
  • 代表例: Apache CassandraGoogle Cloud BigtableHBase
  • ユースケース: 大規模データ分析、IoTのセンサーデータ、時系列データ、ログデータ

データ保存形式の例:

-- Cassandraの例:センサーデータテーブル
-- 主キー: (device_id, timestamp) - デバイスIDで分散、タイムスタンプで時系列ソート

CREATE TABLE sensor_readings (
  device_id uuid,           -- パーティションキー(データ分散の基準)
  timestamp timestamp,      -- クラスタリングキー(ソート順序)
  temperature double,       -- センサー値
  humidity double,          -- センサー値
  location text,           -- 設置場所
  PRIMARY KEY (device_id, timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC);

-- 実際のデータ例
Row 1: device_id=123e4567-e89b-12d3-a456-426614174000
├── timestamp=2024-01-15T10:00:00Z → temperature=23.5, humidity=65.0, location=tokyo
├── timestamp=2024-01-15T10:01:00Z → temperature=23.7, humidity=64.8, location=tokyo
└── timestamp=2024-01-15T10:02:00Z → temperature=23.9, humidity=64.5, location=tokyo

Row 2: device_id=123e4567-e89b-12d3-a456-426614174001
├── timestamp=2024-01-15T10:00:00Z → temperature=25.1, humidity=58.2, location=osaka
├── timestamp=2024-01-15T10:01:00Z → temperature=25.3, humidity=58.0, location=osaka
└── timestamp=2024-01-15T10:02:00Z → temperature=25.5, humidity=57.8, location=osaka
# Bigtableの例:Webアクセスログ
# Row Key: website#date#user_id (例: mysite.com#2024-01-15#user123)

Row Key: mysite.com#2024-01-15#user001
├── access:timestamp → 2024-01-15T10:30:45Z
├── access:ip → 192.168.1.100
├── access:user_agent → Mozilla/5.0...
├── access:page → /products/laptop
├── access:duration → 120
└── access:status → 200

Row Key: mysite.com#2024-01-15#user002
├── access:timestamp → 2024-01-15T10:31:12Z
├── access:ip → 192.168.1.101
├── access:user_agent → Chrome/120.0...
├── access:page → /about
├── access:duration → 45
└── access:status → 200

image.png

image.png

image.png

d. グラフデータベース (Graph Database)

データ間の「関係性」に焦点を当てたデータベースです。データは「ノード(点)」として、関係性は「エッジ(線)」として表現します。

  • 特徴: データ間の複雑な関係性を高速に探索できます。
  • 代表例: Neo4jAmazon Neptune
  • ユースケース: ソーシャルネットワークサービス(SNS)の友人関係、レコメンデーションエンジン、不正検知システム

データ保存形式の例:

// Neo4jの例:ソーシャルネットワーク

// ノード(人)の作成
(alice:Person {name: "Alice", age: 30, city: "東京"})
(bob:Person {name: "Bob", age: 25, city: "大阪"})
(carol:Person {name: "Carol", age: 28, city: "東京"})

// ノード(会社)の作成
(techCorp:Company {name: "Tech Corp", industry: "IT", employees: 500})
(startupInc:Company {name: "StartupInc", industry: "AI", employees: 50})

// リレーションシップ(関係性)の作成
(alice)-[:FRIEND {since: "2020-01-15", strength: "close"}]->(bob)
(alice)-[:FRIEND {since: "2021-03-20", strength: "casual"}]->(carol)
(bob)-[:FRIEND {since: "2021-06-10", strength: "close"}]->(carol)

(alice)-[:WORKS_AT {position: "Senior Engineer", since: "2019-04-01"}]->(techCorp)
(bob)-[:WORKS_AT {position: "Data Scientist", since: "2022-01-15"}]->(startupInc)
(carol)-[:WORKS_AT {position: "Product Manager", since: "2020-06-01"}]->(techCorp)

// 商品推薦の例
(laptop:Product {name: "ノートPC", category: "電子機器", price: 89800})
(keyboard:Product {name: "ワイヤレスキーボード", category: "周辺機器", price: 8900})

(alice)-[:PURCHASED {date: "2024-01-10", rating: 5}]->(laptop)
(alice)-[:PURCHASED {date: "2024-01-12", rating: 4}]->(keyboard)
(bob)-[:VIEWED {date: "2024-01-15", duration: 120}]->(laptop)
(carol)-[:REVIEWED {date: "2024-01-20", rating: 5, comment: "快適です"}]->(laptop)

// グラフ構造の視覚的表現
    (Alice)
   /   |   \
FRIEND WORKS_AT PURCHASED
 /      |        \
(Bob)  (Tech)   (Laptop)
 |      |         |
WORKS_AT EMPLOYS REVIEWED
 |      |         |
(Startup) (Carol) (Carol)
// クエリ例:友達の友達を探す
MATCH (alice:Person {name: "Alice"})-[:FRIEND*1..2]-(friend:Person)
WHERE friend.name <> "Alice"
RETURN DISTINCT friend.name, friend.city

// 結果:
// Bob, 大阪
// Carol, 東京

// クエリ例:同じ会社で働く人を探す
MATCH (alice:Person {name: "Alice"})-[:WORKS_AT]->(company)<-[:WORKS_AT]-(colleague)
RETURN colleague.name, colleague.position

// 結果:
// Carol, Product Manager

image.png

image.png

4. ローカル環境構築 (Docker編) 🐳

ここでは主要なNoSQLデータベース4種 (MongoDBRedisCassandraNeo4j) の環境を、Dockerを使って構築する方法を紹介していきます!
この記事ではそれぞれのサービスを分けて記載するのは面倒なので、一つのファイルにまとめて記載していきます。

docker-compose.yml という名前のファイルを以下の内容で作成します。

version: '3.8'

services:
  mongodb:
    image: mongo:latest
    container_name: my-mongo
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=example

  redis:
    image: redis:latest
    container_name: my-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data

  cassandra:
    image: cassandra:latest
    container_name: my-cassandra
    ports:
      # CQLクライアント用ポート
      - "9042:9042"
    volumes:
      - cassandra-data:/var/lib/cassandra
    # コンテナが正常に起動してから他のサービスが依存できるようにヘルスチェックを追加
    healthcheck:
      test: ["CMD-SHELL", "[ $$(nodetool statusgossip) = 'running' ]"]
      interval: 5s
      timeout: 10s
      retries: 24

  neo4j:
    image: neo4j:latest
    container_name: my-neo4j
    ports:
      # Neo4j Browser用 (HTTP)
      - "7474:7474"
      # Boltプロトコル用 (Goからの接続に使う)
      - "7687:7687"
    volumes:
      - neo4j-data:/data
    environment:
      # 認証情報を設定 (初回起動時のみ有効)
      - NEO4J_AUTH=neo4j/password

volumes:
  mongo-data:
  redis-data:
  cassandra-data:
  neo4j-data:

ファイルを作成したら、ターミナルで以下のコマンドを実行するだけで、4つのデータベースが起動します。

# コンテナをバックグラウンドで起動
docker-compose up -d

# 起動中のコンテナを確認 (STATUSがrunningまたはhealthyになるまで待つ)
docker-compose ps

# コンテナを停止
docker-compose down

5. ローカル環境でのGo言語NoSQL実践 🐹

Go言語から先ほど立ち上げた4つのNoSQLデータベースを操作してみましょう。
ここではmain.goに記載することを想定しています。また、main関数を省略しているので、実行時は同じmain.goのmain関数内で下に記載する関数を実行できるように追記をお願いします。
実行は以下のコマンドで行います。

go run main.go

a. MongoDB with Go (ドキュメントストア)

MongoDBは、世界で最も人気のあるドキュメント指向NoSQLデータベース

主な特徴:

  • スキーマレス: 事前にテーブル構造を定義する必要がなく、開発効率が向上
  • JSON/BSONドキュメント: JavaScript開発者にとって自然なデータ形式
  • インデックス: ドキュメント内の任意のフィールドにインデックスを作成可能
  • レプリケーション: データの可用性とパフォーマンスを向上
  • シャーディング: 水平スケーリングによる大量データ処理

主要なユースケース:

  • Webアプリケーションのバックエンドデータベース
  • ユーザープロファイルや商品カタログ管理
  • コンテンツ管理システム(CMS)
  • リアルタイムアナリティクス

まずは、公式のMongoDB Goドライバをインストールします。

go get go.mongodb.org/mongo-driver/mongo
  • contextを適切に使う: タイムアウトやキャンセル処理のために、すべてのDB操作でcontext.Contextを渡すのがベストプラクティス
  • BSONタグ: Goの構造体をMongoDBに保存する際、bson:"fieldName" タグを使ってドキュメントのフィールド名を明示的に指定する
  • フィルタの作成: bson.Mmap[string]interface{} のエイリアスで手軽にフィルタを書けます。bson.D は順序が重要な場合に使う
package main

import (
	"context"  // コンテキスト管理:タイムアウト、キャンセル処理などに使用
	"fmt"      // フォーマット済み出力(コンソール表示用)
	"log"      // ログ出力とエラーハンドリング用
	"time"     // 時間関連の操作(タイムスタンプ、TTL設定など)

	// MongoDB関連のインポート
	"go.mongodb.org/mongo-driver/bson"    // BSON(Binary JSON)データ形式の操作
	"go.mongodb.org/mongo-driver/mongo"   // MongoDBクライアント
	"go.mongodb.org/mongo-driver/mongo/options" // MongoDB接続オプション
)

// MongoDB用のユーザー構造体
// BSONタグでMongoDBのドキュメントフィールド名を指定
type User struct {
	Name  string `bson:"name"`  // ユーザー名(MongoDB内では"name"フィールドとして保存)
	Email string `bson:"email"` // メールアドレス(MongoDB内では"email"フィールドとして保存)
}

// MongoDB接続と基本的なCRUD操作のデモンストレーション
// ドキュメントデータベースの特徴:スキーマレス、柔軟な構造、JSONライクなデータ
func mongoExample() {
	// MongoDB接続URI:ユーザー名とパスワードを含む
	// Docker Composeで設定した認証情報を使用
	uri := "mongodb://root:example@localhost:27017"

	// MongoDBクライアントを作成し接続を確立
	// context.TODO()は操作完了まで待機することを示す
	client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
	if err != nil {
		log.Fatalf("MongoDB connect error: %v", err)
	}
	// 関数終了時にクライアント接続をクローズしてリソースを解放
	defer client.Disconnect(context.TODO())

	// MongoDB接続の正常性を確認するためのPing実行
	// データベースが応答可能かをチェック
	err = client.Ping(context.TODO(), nil)
	if err != nil {
		log.Fatalf("MongoDB ping error: %v", err)
	}
	fmt.Println("Connected to MongoDB!")

	// データベース"testdb"のコレクション"users"を取得
	// MongoDBでは事前にデータベースやコレクションを作成する必要はない
	collection := client.Database("testdb").Collection("users")

	//  Create操作:新しいドキュメントの挿入
	// Go構造体からBSONドキュメントに自動変換される
	user := User{Name: "Mongo Gopher", Email: "gopher@mongodb.com"}
	_, err = collection.InsertOne(context.TODO(), user)
	if err != nil { log.Fatal(err) }
	fmt.Println("MongoDB: Inserted user 'Mongo Gopher'")

	//  Read操作:ドキュメントの検索と取得
	var result User
	// bson.Mはmap[string]interface{}のエイリアス(フィルタ条件の作成)
	filter := bson.M{"name": "Mongo Gopher"}
	// FindOneは条件に一致する最初のドキュメントを取得
	err = collection.FindOne(context.TODO(), filter).Decode(&result)
	if err != nil { log.Fatal(err) }
	fmt.Printf("MongoDB: Found user: %+v\n", result)

	//  Update操作:既存ドキュメントの更新
	// $setオペレーターを使用して特定フィールドのみを更新
	update := bson.M{"$set": bson.M{"email": "super.gopher@mongodb.com"}}
	_, err = collection.UpdateOne(context.TODO(), filter, update)
	if err != nil { log.Fatal(err) }
	fmt.Println("MongoDB: Updated user's email")

	//  Delete操作:ドキュメントの削除
	// 指定した条件に一致するドキュメントを1つ削除
	_, err = collection.DeleteOne(context.TODO(), filter)
	if err != nil { log.Fatal(err) }
	fmt.Println("MongoDB: Deleted user 'Mongo Gopher'")
}

b. Redis with Go (キーバリューストア)

Redisは、高速なインメモリキーバリューストアとして広く使用されているオープンソースデータベース

主な特徴:

  • インメモリストレージ: RAM上でデータを保持するため、極めて高速なアクセスが可能
  • 多様なデータ型: String、Hash、List、Set、Sorted Set、Bitmapなどをサポート
  • 永続化機能: RDB(スナップショット)やAOF(ログ)による永続化
  • TTL(有効期限): キーごとに自動削除タイマーを設定可能
  • レプリケーション: マスター・スレーブ構成による高可用性

主要なユースケース:

  • アプリケーションキャッシュ(DB結果、API応答のキャッシュ)
  • セッション管理(Webアプリケーションのログイン状態保持)
  • リアルタイムランキング(ゲームスコア、人気商品等)
  • 発行・購読(Pub/Sub)システム

go-redis ライブラリをインストールします。

go get github.com/go-redis/redis/v8
  • キーの設計: object-type:id (例: user:100) のような命名規則を使うと、キー空間が整理されて管理しやすくなる
  • 有効期限(TTL)の設定: キャッシュデータには必ず有効期限を設定し、メモリが溢れないようにします。Setの第4引数で指定する
  • エラーハンドリング: redis.Nil はキーが存在しないことを示す特別なエラーです。これを他のエラーと区別して処理することが重要
package main

import (
	"context"  // コンテキスト管理:タイムアウト、キャンセル処理などに使用
	"fmt"      // フォーマット済み出力(コンソール表示用)
	"log"      // ログ出力とエラーハンドリング用
	"time"     // 時間関連の操作(タイムスタンプ、TTL設定など)

	// Redis関連のインポート
	"github.com/go-redis/redis/v8" // Redisクライアントライブラリ(v8は context対応版)
)

// Redis操作で使用するグローバルコンテキスト
// 全てのRedis操作で共通して使用される
var ctx = context.Background()

// Redis接続と基本的なCRUD操作のデモンストレーション
// キーバリューストアの特徴:高速アクセス、インメモリストレージ、TTL(有効期限)サポート
func redisExample() {
	// Redisクライアントを作成して localhost:6379 に接続
	// デフォルトのRedisポート6379を使用
	rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})

	// Redis接続の正常性を確認するためのPing実行
	// _は戻り値の"PONG"レスポンスを無視
	_, err := rdb.Ping(ctx).Result()
	if err != nil {
		log.Fatalf("Redis connect error: %v", err)
	}
	fmt.Println("Connected to Redis!")

	// セッション管理に適したキー命名規則を使用
	// "object-type:identifier" の形式でキー空間を整理
	key := "session:gopher"

	//  Create/Update操作(Set):キーと値のペアを設定
	// 第4引数の5*time.Minuteで5分間のTTL(有効期限)を設定
	// TTLが切れると自動的にキーが削除される(メモリ効率化)
	err = rdb.Set(ctx, key, "active", 5*time.Minute).Err()
	if err != nil { log.Fatal(err) }
	fmt.Printf("Redis: Set key '%s' with 5 min TTL\n", key)

	//  Read操作(Get):キーに対応する値を取得
	// Redisでは全てのデータがstring型として保存される
	val, err := rdb.Get(ctx, key).Result()
	if err != nil { log.Fatal(err) }
	fmt.Printf("Redis: Get value for '%s': %s\n", key, val)

	//  TTL確認:キーの残り有効期限をチェック
	// 期限なしの場合は-1、存在しないキーの場合は-2が返される
	ttl, err := rdb.TTL(ctx, key).Result()
	if err != nil { log.Fatal(err) }
	fmt.Printf("Redis: TTL for '%s': %s\n", key, ttl)

	//  Delete操作(Del):指定したキーを削除
	// 複数のキーを同時に削除することも可能
	err = rdb.Del(ctx, key).Err()
	if err != nil { log.Fatal(err) }
	fmt.Printf("Redis: Deleted key '%s'\n", key)

	//  削除確認:キーが存在しないことを確認
	// redis.Nilは「キーが存在しない」を示す特別なエラー
	_, err = rdb.Get(ctx, key).Result()
	if err == redis.Nil {
		fmt.Printf("Redis: Key '%s' does not exist (as expected).\n", key)
	} else if err != nil { log.Fatal(err) }
}

c. Cassandra with Go (カラム指向ストア)

Apache Cassandraは、高いスケーラビリティと可用性を持つ分散NoSQLデータベースです。

主な特徴:

  • 水平スケーリング: ノードを追加するだけで線形にパフォーマンスが向上
  • 分散アーキテクチャ: 単一障害点がなく、高い可用性を実現
  • 最終的一貫性: 分散環境での結果整合性によるパフォーマンス重視
  • カラムファミリー: 関連するカラムをグループ化してパフォーマンスを最適化
  • 時系列データに最適: タイムスタンプベースのデータ格納・取得に特化

主要なユースケース:

  • IoTセンサーデータやログデータの収集・分析
  • 時系列データベース(メトリクス監視、金融データ)
  • 大規模Webアプリケーションのバックエンド
  • リアルタイムデータ処理(ストリーミング分析)

デファクトスタンダードの gocql ライブラリをインストールします。

go get github.com/gocql/gocql
  • データモデリング: Cassandraではクエリ(どうデータを取得したいか)を先に考えます。主キーの設計がパフォーマンスの鍵を握る
  • 整合性レベル: Query.Consistency() を使ってクエリごとに整合性レベル (gocql.Quorumgocql.Oneなど) を指定できます。書き込みと読み込みの要件に合わせて調整する
  • ページング: 大量データを取得する際は Query.PageSize()Iter.PageState() を使って手動でページングを制御する
  • 冪等性 (Idempotence): 分散システムでは同じ操作が複数回実行される可能性があります。INSERTUPDATE は冪等になるように設計するのが望ましい
package main

import (
	"fmt"  // フォーマット済み出力(コンソール表示用)
	"log"  // ログ出力とエラーハンドリング用
	"time" // 時間関連の操作(タイムスタンプ、TTL設定など)

	// Cassandra関連のインポート
	"github.com/gocql/gocql" // CassandraのGo言語ドライバ(CQLクエリ実行用)
)

// Cassandra用のセンサーデータ構造体
// IoTセンサーからの読み取り値を表現
type SensorReading struct {
	DeviceID  gocql.UUID // デバイスの一意識別子(UUIDv4形式)
	Timestamp time.Time  // データ取得時刻(Cassandraの時系列データに重要)
	Value     float64    // センサーの測定値(温度、湿度などの数値)
}

// Cassandraのセッションを作成し、キースペースとテーブルを準備するヘルパー関数
// Cassandraの初期設定は複雑なため、再利用可能な関数として分離
func setupCassandraSession() (*gocql.Session, error) {
	// Cassandraクラスターの設定を作成
	// 本番環境では複数のホストを設定してクラスター構成にする
	cluster := gocql.NewCluster("127.0.0.1:9042")
	cluster.Timeout = 10 * time.Second // クエリ実行のタイムアウトを10秒に設定

	// まずシステムキースペースに接続してキースペース作成権限を取得
	// Cassandraでは最初にシステムレベルで接続する必要がある
	session, err := cluster.CreateSession()
	if err != nil {
		return nil, err
	}

	// キースペース(データベース相当)を作成
	// SimpleStrategyは単一データセンター用のレプリケーション戦略
	// replication_factor: 1 はレプリカ数(本番では3以上推奨)
	// この処理は冪等(何度実行しても同じ結果)なので安全
	err = session.Query(`
		CREATE KEYSPACE IF NOT EXISTS gopher_sensors
		WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}
	`).Exec()
	if err != nil {
		return nil, err
	}
	// 一時的なセッションをクローズ
	session.Close()

	// 作成したキースペースに接続し直す
	// 以降の操作は全てこのキースペース内で実行される
	cluster.Keyspace = "gopher_sensors"
	session, err = cluster.CreateSession()
	if err != nil {
		return nil, err
	}

	// テーブルを作成(リレーショナルDBのテーブルに相当)
	// PRIMARY KEY (device_id, timestamp): 複合主キー
	// device_id: パーティションキー(データの分散先を決定)
	// timestamp: クラスタリングキー(パーティション内での並び順を決定)
	// CLUSTERING ORDER BY (timestamp DESC): 新しいデータが先頭に来るよう降順ソート
	err = session.Query(`
		CREATE TABLE IF NOT EXISTS readings (
			device_id uuid,
			timestamp timestamp,
			value double,
			PRIMARY KEY (device_id, timestamp)
		) WITH CLUSTERING ORDER BY (timestamp DESC);
	`).Exec()
	if err != nil {
		return nil, err
	}

	return session, nil
}

// Cassandra接続と基本的なCRUD操作のデモンストレーション
// カラム指向ストアの特徴:大量書き込み性能、時系列データに最適、分散アーキテクチャ
func cassandraExample() {
	// ヘルパー関数を使用してCassandraセッションを取得
	session, err := setupCassandraSession()
	if err != nil {
		log.Fatalf("Cassandra setup error: %v", err)
	}
	// 関数終了時にセッションをクローズしてリソースを解放
	defer session.Close()
	fmt.Println("Connected to Cassandra and Keyspace/Table is ready!")

	// テストデータの準備
	deviceID, _ := gocql.RandomUUID() // 一意のデバイス識別子を生成
	now := time.Now()                // 現在時刻を記録(時系列データの重要な要素)

	//  Create操作:新しいセンサーデータの挿入
	reading := SensorReading{DeviceID: deviceID, Timestamp: now, Value: 36.5}
	// プレースホルダー(?)を使用してSQLインジェクション攻撃を防ぐ
	// CQL(Cassandra Query Language)はSQLに似た構文を使用
	err = session.Query(`
		INSERT INTO readings (device_id, timestamp, value) VALUES (?, ?, ?)`,
		reading.DeviceID, reading.Timestamp, reading.Value,
	).Exec()
	if err != nil { log.Fatalf("Cassandra insert error: %v", err) }
	fmt.Printf("Cassandra: Inserted sensor reading for device %s\n", deviceID)

	//  Read操作:デバイスIDで検索してデータを取得
	var readings []SensorReading
	// WHERE句でパーティションキー(device_id)を指定
	// Cassandraは効率的なクエリのためパーティションキーでの検索を推奨
	iter := session.Query(`SELECT device_id, timestamp, value FROM readings WHERE device_id = ?`, deviceID).Iter()
	var r SensorReading
	// イテレーターを使用して結果セットを順次処理
	for iter.Scan(&r.DeviceID, &r.Timestamp, &r.Value) {
		readings = append(readings, r)
	}
	// イテレーターを適切にクローズしてエラーをチェック
	if err := iter.Close(); err != nil { log.Fatalf("Cassandra read error: %v", err) }
	fmt.Printf("Cassandra: Found %d readings for device. Latest: %+v\n", len(readings), readings[0])

	//  Update操作:Cassandraの特徴的な更新方法
	// CassandraではINSERTがUPSERT(存在しない場合は挿入、存在する場合は更新)として機能
	// 同じ主キー(device_id + timestamp)で新しい値を挿入すると既存データが更新される
	newValue := 37.0
	err = session.Query(`
		INSERT INTO readings (device_id, timestamp, value) VALUES (?, ?, ?)`,
		reading.DeviceID, reading.Timestamp, newValue,
	).Exec()
	if err != nil { log.Fatalf("Cassandra update error: %v", err) }
	fmt.Printf("Cassandra: Updated reading value to %.1f\n", newValue)

	//  Delete操作:指定した主キーのデータを削除
	// WHERE句で完全な主キー(パーティションキー + クラスタリングキー)を指定
	err = session.Query(`DELETE FROM readings WHERE device_id = ? AND timestamp = ?`, deviceID, now).Exec()
	if err != nil { log.Fatalf("Cassandra delete error: %v", err) }
	fmt.Printf("Cassandra: Deleted reading for device %s\n", deviceID)
}

d. Neo4j with Go (グラフデータベース)

Neo4jは、世界で最も人気のあるグラフデータベースの一つで、データ間の複雑な関係性を効率的に管理できる

主な特徴:

  • ネイティブグラフストレージ: ノードとリレーションシップが物理的に最適化されて格納
  • Cypherクエリ言語: 直感的なパターンマッチングでグラフトラバーサルを記述
  • ACID特性: トランザクションの原子性、一貫性、独立性、永続性を保証
  • インデックス機能: ノードとリレーションシップに対する高速検索
  • 可視化ツール: Neo4j Browserによるインタラクティブなデータ探索

主要なユースケース:

  • ソーシャルネットワーク分析(友人関係、影響力分析)
  • レコメンデーションエンジン(関連商品、類似ユーザー)
  • 不正検知システム(異常なパターンの発見)
  • ナレッジグラフ構築(組織構造、概念関係)

公式の neo4j-go-driver をインストールします。

go get github.com/neo4j/neo4j-go-driver/v5/neo4j

実装のTips:

  • Cypherクエリ: SQLとは全く異なるグラフ用のクエリ言語です。(node)-[:RELATIONSHIP]->(another_node) のようなアスキーアート風の構文が特徴
  • パラメータ化クエリ: SQLインジェクションならぬCypherインジェクションを防ぐため、クエリに直接値を埋め込まず、必ずパラメータを使う
  • トランザクション関数: session.ExecuteRead()session.ExecuteWrite() を使うと、リトライ処理を含むトランザクション管理をドライバに任せられるので推奨される
  • リソース管理: Driver はアプリケーションのライフサイクルで一つだけ作成し、Session は各リクエストやタスク単位で作成・クローズするのが基本
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)

// Neo4j接続と基本的なCRUD操作のデモンストレーション
// グラフデータベースの特徴:ノードとリレーションシップ、複雑な関係性の高速探索、Cypherクエリ言語
func neo4jExample() {
	// Neo4jサーバーへの接続設定
	uri := "neo4j://localhost:7687"  // Boltプロトコル(Neo4j専用)を使用
	user := "neo4j"                  // Docker Composeで設定したユーザー名
	password := "password"           // Docker Composeで設定したパスワード

	// Neo4jドライバーを作成し、基本認証を設定
	// WithContextを使用してコンテキストベースの操作をサポート
	driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(user, password, ""))
	if err != nil {
		log.Fatalf("Neo4j driver creation error: %v", err)
	}
	// 関数終了時にドライバーをクローズしてリソースを解放
	defer driver.Close(context.Background())
	fmt.Println("Connected to Neo4j!")

	// セッションを作成してデータベース操作を実行
	// DatabaseNameを指定(Neo4j 4.0以降は複数データベースをサポート)
	session := driver.NewSession(context.Background(), neo4j.SessionConfig{DatabaseName: "neo4j"})
	defer session.Close(context.Background())

	//  Create操作:ノードとリレーションシップの作成
	// ExecuteWriteは書き込みトランザクションを安全に実行(自動リトライ機能付き)
	_, err = session.ExecuteWrite(context.Background(),
		func(tx neo4j.ManagedTransaction) (any, error) {
			// Personノード(人)を作成
			// Cypherクエリ:CREATE (variable:Label {property: value})
			// $nameはパラメーター(SQLインジェクション防止)
			_, err := tx.Run(context.Background(),
				"CREATE (p:Person {name: $name})",
				map[string]any{"name": "Neo Gopher"},
			)
			if err != nil { return nil, err }

			// Skillノード(スキル)を作成
			// 同一トランザクション内で複数のクエリを実行
			_, err = tx.Run(context.Background(),
				"CREATE (s:Skill {name: $name})",
				map[string]any{"name": "Go"},
			)
			if err != nil { return nil, err }

			// PersonノードとSkillノード間にKNOWSリレーションシップを作成
			// MATCH文で既存ノードを検索し、CREATE文でリレーションシップを作成
			// (p)-[:KNOWS]->(s) は「pがsを知っている」という関係を表現
			_, err = tx.Run(context.Background(), `
				MATCH (p:Person {name: $p_name})
				MATCH (s:Skill {name: $s_name})
				CREATE (p)-[:KNOWS]->(s)
			`, map[string]any{
				"p_name": "Neo Gopher",
				"s_name": "Go",
			})
			return nil, err
		})
	if err != nil { log.Fatalf("Neo4j create error: %v", err) }
	fmt.Println("Neo4j: Created 'Neo Gopher' node, 'Go' node, and KNOWS relationship")

	//  Read操作:関係性を辿ってデータを取得
	// ExecuteReadは読み取り専用トランザクションを実行(パフォーマンス最適化)
	skill, err := session.ExecuteRead(context.Background(),
		func(tx neo4j.ManagedTransaction) (any, error) {
			// PersonからKNOWSリレーションシップを辿ってSkillを取得
			// RETURNでクエリ結果として返すデータを指定
			// AS skillNameでエイリアスを設定
			result, err := tx.Run(context.Background(), `
				MATCH (p:Person {name: $name})-[:KNOWS]->(s:Skill)
				RETURN s.name AS skillName
			`, map[string]any{"name": "Neo Gopher"})
			if err != nil { return nil, err }

			// 結果から単一レコードを取得
			// Singleは結果が1つだけの場合に使用
			record, err := result.Single(context.Background())
			if err != nil { return nil, err }

			// レコードから指定したフィールドの値を取得
			skillName, _ := record.Get("skillName")
			return skillName, nil
		})
	if err != nil { log.Fatalf("Neo4j read error: %v", err) }
	fmt.Printf("Neo4j: Found that 'Neo Gopher' knows '%s'\n", skill.(string))

	//  Delete操作:ノードとリレーションシップのクリーンアップ
	// 書き込みトランザクションで削除操作を実行
	_, err = session.ExecuteWrite(context.Background(),
		func(tx neo4j.ManagedTransaction) (any, error) {
			// DETACH DELETEでノードとそれに関連するリレーションシップを一括削除
			// 通常のDELETEではリレーションシップが残っているノードは削除できない
			return tx.Run(context.Background(), `
				MATCH (p:Person {name: $name})
				DETACH DELETE p
			`, map[string]any{"name": "Neo Gopher"})
		})
	if err != nil { log.Fatalf("Neo4j delete person error: %v", err) }

	// Skillノードも削除
	_, err = session.ExecuteWrite(context.Background(),
		func(tx neo4j.ManagedTransaction) (any, error) {
			return tx.Run(context.Background(), `
				MATCH (s:Skill {name: $name})
				DETACH DELETE s
			`, map[string]any{"name": "Go"})
		})
	if err != nil { log.Fatalf("Neo4j delete skill error: %v", err) }
	fmt.Println("Neo4j: Cleaned up nodes and relationships")
}

6. NoSQLデータベースへの接続・管理・操作方法 🔧📋

Go言語でのプログラミング以外にも、各データベースには専用の管理ツールやクライアントが用意されています。開発・デバッグ・運用時に非常に便利です

MongoDB への接続

1. MongoDB Compass (GUI)
MongoDB公式のGUIクライアント

ダウンロード: https://www.mongodb.com/products/compass
接続URI: mongodb://root:example@localhost:27017

2. mongo shell (CLI)

# MongoDBコンテナに直接接続
docker exec -it my-mongo mongosh -u root -p example

# または、ローカルにmongo shellがインストールされている場合
mongosh "mongodb://root:example@localhost:27017"

MongoDBでの基本操作

データベース一覧表示

show dbs

データベースの選択

use myapp

コレクション一覧表示

show collections

ドキュメントの作成

db.users.insertOne({name: "田中太郎", email: "tanaka@example.com", age: 30})

データの検索

// 全件取得
db.users.find()

// 特定条件で1件取得
db.users.findOne({name: "田中太郎"})

データの更新

db.users.updateOne(
  {name: "田中太郎"},
  {$set: {age: 31}}
)

データの削除

db.users.deleteOne({name: "田中太郎"})

Redis への接続

1. redis-cli (CLI)

# Redisコンテナに直接接続
docker exec -it my-redis redis-cli

# または、ローカルにRedisクライアントがインストールされている場合
redis-cli -h localhost -p 6379

2. RedisInsight (GUI)
Redis公式のGUIクライアント
ダウンロード: https://redis.com/redis-enterprise/redis-insight/
接続先: localhost:6379

Redisでの基本操作

データベース情報表示

現在のデータベース番号を確認します(デフォルトは0):

CLIENT LIST

データベース情報とキースペース統計を表示

INFO keyspace

利用可能なデータベース数を確認

CONFIG GET databases

データベースの選択

データベース番号で切り替えます(0-15、デフォルトは0):

SELECT 1

データベース0に戻ります:

SELECT 0

文字列データの操作

文字列データを設定

SET user:1001:name "山田花子"

文字列データを取得

GET user:1001:name

TTL付きデータの設定

TTL付きデータを設定します(60秒後に自動削除):

SETEX session:abc123 60 "logged_in"

ハッシュ型データの操作

ハッシュ型データを設定

HSET user:1001 name "山田花子" email "yamada@example.com" age 25

ハッシュの特定フィールドを取得

HGET user:1001 name

ハッシュの全フィールドを取得

HGETALL user:1001

リスト型データの操作

# リスト型データの操作
LPUSH messages "こんにちは" "お疲れ様です"
LRANGE messages 0 -1

セット型データの操作

# セット型データの操作
SADD tags "Redis" "NoSQL" "キャッシュ"
SMEMBERS tags

キーの一覧表示

# キーの一覧表示
KEYS *

Cassandra への接続

1. cqlsh (CLI)

# Cassandraコンテナに直接接続
docker exec -it my-cassandra cqlsh

# または、ローカルにCassandraがインストールされている場合
cqlsh localhost 9042

2. DataStax Studio (GUI)
DataStax社が提供するCassandra専用の開発環境です。

Cassandraでの基本操作

キースペース一覧表示

-- キースペース(データベース相当)一覧表示
DESCRIBE KEYSPACES;

キースペースの作成と使用

-- キースペースの作成と使用
CREATE KEYSPACE myapp WITH replication = {
  'class': 'SimpleStrategy',
  'replication_factor': 1
};
USE myapp;

テーブルの作成

-- テーブルの作成
CREATE TABLE users (
  id UUID PRIMARY KEY,
  name text,
  email text,
  created_at timestamp
);

データの挿入

INSERT INTO users (id, name, email, created_at)
VALUES (uuid(), '佐藤一郎', 'sato@example.com', toTimestamp(now()));

データの検索

SELECT * FROM users;
SELECT name, email FROM users WHERE id = 12345678-1234-1234-1234-123456789abc;

データの更新

UPDATE users SET email = 'newsato@example.com'
WHERE id = 12345678-1234-1234-1234-123456789abc;

データの削除

DELETE FROM users WHERE id = 12345678-1234-1234-1234-123456789abc;

テーブル一覧表示

DESCRIBE TABLES;

Neo4j への接続

1. Neo4j Browser (Web UI)

# ブラウザで以下にアクセス
http://localhost:7474

# 認証情報:
# Username: neo4j
# Password: password

2. Cypher Shell (CLI)
Neo4jコンテナに直接接続

docker exec -it my-neo4j cypher-shell -u neo4j -p password

Neo4jでの基本操作

データベース一覧表示

SHOW DATABASES

ラベル一覧表示

CALL db.labels()

リレーションシップタイプ一覧表示

CALL db.relationshipTypes()

ノードの作成

CREATE (p:Person {name: '鈴木次郎', age: 28, city: '東京'})

ラベルとプロパティでの検索

// 全Personノード
MATCH (p:Person) RETURN p

// 特定の人
MATCH (p:Person {name: '鈴木次郎'}) RETURN p

リレーションシップの作成

MATCH (a:Person {name: '鈴木次郎'})
CREATE (a)-[:WORKS_AT]->(:Company {name: 'Tech Corp'})

リレーションシップを辿る検索

MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN p.name, c.name

ノードプロパティの更新

MATCH (p:Person {name: '鈴木次郎'})
SET p.age = 29

友達関係の作成

MATCH (a:Person {name: '鈴木次郎'})
CREATE (b:Person {name: '田中三郎'}), (a)-[:FRIEND]->(b)

友達の友達を検索(2度の関係)

MATCH (p:Person {name: '鈴木次郎'})-[:FRIEND*1..2]-(friend)
RETURN DISTINCT friend.name

ノードとリレーションシップの削除

MATCH (p:Person {name: '鈴木次郎'})
DETACH DELETE p

基本CRUD操作一覧

以下は各NoSQLデータベースでの基本的なCRUD操作をまとめたコマンド一覧です。

MongoDB (mongosh)

データベース選択

データベース"testdb"に切り替えます:

use testdb

Create(作成)

単一ドキュメントを挿入

db.users.insertOne({name: "Alice", email: "alice@example.com"})

複数ドキュメントを挿入

db.users.insertMany([{name: "Bob"}, {name: "Carol"}])

Read(読み取り)

全ドキュメントを取得

db.users.find()

条件に一致する1つのドキュメントを取得

db.users.findOne({name: "Alice"})

条件に一致する全ドキュメントを取得

db.users.find({name: "Alice"})

最大5件のドキュメントを取得

db.users.find().limit(5)

Update(更新)

単一ドキュメントを更新

db.users.updateOne({name: "Alice"}, {$set: {email: "newalice@example.com"}})

複数ドキュメントを更新

db.users.updateMany({}, {$set: {status: "active"}})

ドキュメントを置き換えます:

db.users.replaceOne({name: "Alice"}, {name: "Alice", age: 25})

Delete(削除)

単一ドキュメントを削除

db.users.deleteOne({name: "Alice"})

条件に一致する複数ドキュメントを削除

db.users.deleteMany({status: "inactive"})

コレクション全体を削除

db.users.drop()

Redis (redis-cli)

Create/Update(作成・更新)

キーと値のペアを設定

SET user:1:name "Alice"

TTL付きで設定(1時間後に自動削除):

SET session:abc123 "active" EX 3600

ハッシュ(複数フィールド)を設定

HSET user:1 name "Alice" email "alice@example.com"

リストの先頭に要素を追加

LPUSH messages "Hello" "World"

セットに要素を追加

SADD tags "redis" "nosql" "database"

Read(読み取り)

単一キーの値を取得

GET user:1:name

ハッシュの特定フィールドを取得

HGET user:1 name

ハッシュの全フィールドを取得

HGETALL user:1

リストの全要素を取得

LRANGE messages 0 -1

セットの全要素を取得

SMEMBERS tags

パターンに一致するキー一覧を取得

KEYS user:*

Update(更新)

既存キーの値を上書き

SET user:1:name "Alice Updated"

ハッシュの数値フィールドをインクリメント

HINCRBY user:1 age 1

既存キーにTTL(5分)を設定

EXPIRE user:1:name 300

Delete(削除)

単一キーを削除

DEL user:1:name

複数キーを削除

DEL user:1:name user:1:email

ハッシュの特定フィールドを削除

HDEL user:1 email

現在のデータベースの全キーを削除

FLUSHDB

Cassandra (cqlsh)

セットアップ

キースペース(データベース相当)を作成

CREATE KEYSPACE demo WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};

キースペースに切り替えます:

USE demo;

テーブルを作成

CREATE TABLE users (id UUID PRIMARY KEY, name text, email text);

Create(作成)

単一行を挿入

INSERT INTO users (id, name, email) VALUES (uuid(), 'Alice', 'alice@example.com');

部分データを挿入

INSERT INTO users (id, name) VALUES (uuid(), 'Bob');

Read(読み取り)

全行を取得

SELECT * FROM users;

特定行を取得

SELECT name, email FROM users WHERE id = 12345678-1234-1234-1234-123456789abc;

最大10行を取得

SELECT * FROM users LIMIT 10;

行数をカウント

SELECT COUNT(*) FROM users;

Update(更新)

特定行を更新

UPDATE users SET email = 'newalice@example.com' WHERE id = 12345678-1234-1234-1234-123456789abc;

条件付きで更新

UPDATE users SET name = 'Alice Updated' WHERE id = 12345678-1234-1234-1234-123456789abc IF EXISTS;

Delete(削除)

特定行を削除

DELETE FROM users WHERE id = 12345678-1234-1234-1234-123456789abc;

特定カラムを削除

DELETE email FROM users WHERE id = 12345678-1234-1234-1234-123456789abc;

テーブルの全データを削除

TRUNCATE users;

テーブル自体を削除

DROP TABLE users;

Neo4j (cypher-shell)

Create(作成)

ノードを作成

CREATE (p:Person {name: 'Alice', age: 30})

ノードとリレーションシップを作成

CREATE (p:Person {name: 'Bob'})-[:KNOWS]->(q:Person {name: 'Carol'})

既存ノード間のリレーションシップを作成

MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:FRIENDS]->(b)

Read(読み取り)

全Personノードを取得

MATCH (p:Person) RETURN p

特定ノードを取得

MATCH (p:Person {name: 'Alice'}) RETURN p

リレーションシップを辿って取得

MATCH (p:Person)-[:KNOWS]->(friend) RETURN p.name, friend.name

ソートと制限付きで取得

MATCH (p:Person) RETURN p.name, p.age ORDER BY p.age DESC LIMIT 5

可変長パスを探索

MATCH (p:Person)-[:FRIENDS*1..3]-(friend)
WHERE p.name = 'Alice'
RETURN DISTINCT friend.name

Update(更新)

ノードプロパティを更新

MATCH (p:Person {name: 'Alice'}) SET p.age = 31

複数プロパティを追加・更新

MATCH (p:Person {name: 'Alice'})
SET p += {email: 'alice@example.com', city: 'Tokyo'}

リレーションシッププロパティを更新

MATCH ()-[r:KNOWS]->() SET r.since = '2023'

Delete(削除)

ノードを削除(リレーションシップがない場合のみ):

MATCH (p:Person {name: 'Alice'}) DELETE p

ノードとその全リレーションシップを削除

MATCH (p:Person {name: 'Alice'}) DETACH DELETE p

特定タイプのリレーションシップを削除

MATCH ()-[r:KNOWS]-() DELETE r

全ノードとリレーションシップを削除

MATCH (n) DETACH DELETE n

7. NoSQLの選定方法: どれを選ぶべきか? 🧭

「どのNoSQLを使えばいいのか?」これは多くの開発者が直面する問題ですよね😭

  1. データモデルを考える:

    • 扱うデータはシンプルなキーと値か? → キーバリューストア (Redis)
    • 柔軟な構造を持つJSONデータか? → ドキュメントストア (MongoDB)
    • 大量のログや時系列データを分析したいか? → カラム指向ストア (Cassandra)
    • データ間の関係性が重要か? → グラフデータベース (Neo4j)
  2. 読み書きのパターンを考える:

    • 書き込みが多い (Write-heavy): Cassandraのような分散システムは書き込み性能に優れている
    • 読み込みが多い (Read-heavy): Redisのようなインメモリデータベースは読み込みが非常に高速で、キャッシュ用途に最適
  3. 一貫性の要件を考える:

    • 金融取引のように**強い一貫性(ACID)**が必須なら、RDBを検討するか、ACIDをサポートする一部のNoSQL(例: MongoDBの最近のバージョン)を選択する
    • SNSの「いいね」のように、多少の遅延が許容されるなら**結果整合性(BASE)**で十分です。多くのNoSQLがこれに該当する
  4. エコシステムと運用コストを考える:

    • コミュニティは活発か?ライブラリやツールは豊富か?
    • 自前で運用するのか、クラウドのマネージドサービスを使うのか?

実例: Neo4jを選んだ場合
例えば、SNSアプリで「友達の友達」を検索する機能を実装する場合:

// 3度以内の関係で繋がっている人を検索(Neo4jのCypherクエリ)
MATCH (user:Person {name: "Alice"})-[:FRIENDS*1..3]-(friend:Person)
WHERE friend.name <> "Alice"
RETURN DISTINCT friend.name, LENGTH(path) AS degrees
ORDER BY degrees ASC
LIMIT 10

このような複雑な関係性クエリを、RDBで実装しようとすると複数のJOINが必要になり、パフォーマンスが大幅に低下します。しかし、Neo4jなら数ミリ秒で結果を返すことができます☺️

8. クラウドNoSQLサービスのローカル実装例 ☁️🐳

実際のプロダクション環境では、AWSやGCPなどのクラウドプロバイダーが提供するマネージドNoSQLサービスを使用することが多いです。ここでは、主要なクラウドNoSQLサービスであるAmazon DynamoDBとGoogle Cloud Bigtableを、ローカル環境(エミュレーター)で実行してみましょう

a. Amazon DynamoDB Local (キーバリュー + ドキュメント)

Amazon DynamoDBは、AWSが提供するフルマネージドNoSQLデータベースサービスです。

主な特徴:

  • サーバーレス: インフラ管理が不要で、使用量に応じて自動スケーリング
  • 高速: 一桁ミリ秒のレスポンスタイム
  • 柔軟な課金: オンデマンド課金とプロビジョンド課金を選択可能
  • ACID トランザクション: 複数項目にわたる原子性操作をサポート
  • グローバルテーブル: 複数リージョン間でのデータレプリケーション

仕様・制限:

  • 項目サイズ: 最大400KB
  • パーティションキー + ソートキー(オプション)の構成
  • 強い整合性読み取りと結果整合性読み取りを選択可能
  • GSI(Global Secondary Index)とLSI(Local Secondary Index)でクエリの柔軟性を向上
# DynamoDB用のAWS SDK v2をインストール
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/dynamodb
go get github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue

実装のTips:

  • パーティションキーの設計: データの分散を考慮して、ホットパーティションを避ける設計が重要
  • アトリビュート値の変換: attributevalue.MarshalMapUnmarshalMapでGo構造体とDynamoDBの型を自動変換
  • 条件付き操作: ConditionExpressionで楽観的ロックやデータ整合性を保証
  • オンデマンド vs プロビジョンド: トラフィックパターンに応じて課金モデルを選択
package main

import (
	"context" // コンテキスト管理:タイムアウト、キャンセル処理などに使用
	"fmt"     // フォーマット済み出力(コンソール表示用)
	"log"     // ログ出力とエラーハンドリング用

	// AWS SDK v2関連のインポート(DynamoDB Local接続用)
	"github.com/aws/aws-sdk-go-v2/aws"                             // AWS SDK の基本設定
	"github.com/aws/aws-sdk-go-v2/config"                          // AWS設定の読み込み
	"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" // DynamoDBのデータ変換ユーティリティ
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"                // DynamoDBサービスクライアント
	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"          // DynamoDBの型定義
)

// DynamoDB用のユーザープロファイル構造体
// JSONタグとDynamoDBのattributevalueタグの両方を定義
type UserProfile struct {
	UserID string `json:"user_id" dynamodbav:"user_id"` // ユーザー識別子(パーティションキー)
	Name   string `json:"name" dynamodbav:"name"`       // ユーザー名
	Email  string `json:"email" dynamodbav:"email"`     // メールアドレス
	Age    int    `json:"age" dynamodbav:"age"`         // 年齢
}

// DynamoDB Local接続と基本的なCRUD操作のデモンストレーション
// フルマネージドNoSQLの特徴:サーバーレス、自動スケーリング、ミリ秒レベルの低遅延
func dynamoDBExample() {
	// DynamoDB Localに接続するためのAWS SDK設定
	// LoadDefaultConfigで標準的なAWS設定を読み込み
	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithRegion("us-east-1"), // AWSリージョンを設定(Localでは実際は使用されない)
		// カスタムエンドポイントリゾルバーでローカルのDynamoDB Localを指定
		config.WithEndpointResolverWithOptions(aws.EndpointResolverWithOptionsFunc(
			func(service, region string, options ...interface{}) (aws.Endpoint, error) {
				return aws.Endpoint{URL: "http://localhost:8000"}, nil
			})),
	)
	if err != nil {
		log.Fatalf("unable to load SDK config, %v", err)
	}

	// 設定からDynamoDBクライアントを作成
	client := dynamodb.NewFromConfig(cfg)

	tableName := "users"

	// テーブルを作成(既に存在する場合はエラーを無視)
	// DynamoDBではテーブル作成時にキースキーマとアトリビュート定義が必要
	_, err = client.CreateTable(context.TODO(), &dynamodb.CreateTableInput{
		TableName: aws.String(tableName),
		// プライマリキーの定義
		KeySchema: []types.KeySchemaElement{
			{
				AttributeName: aws.String("user_id"), // パーティションキー名
				KeyType:       types.KeyTypeHash,     // HASHはパーティションキーを示す
			},
		},
		// キーアトリビュートのデータ型定義
		AttributeDefinitions: []types.AttributeDefinition{
			{
				AttributeName: aws.String("user_id"),
				AttributeType: types.ScalarAttributeTypeS, // S = String型
			},
		},
		// オンデマンドビリング(使用量に応じた課金)
		BillingMode: types.BillingModePayPerRequest,
	})
	if err != nil {
		// テーブルが既に存在する場合のエラーは無視
		fmt.Printf("Create table result: %v (table may already exist)\n", err)
	} else {
		fmt.Println("DynamoDB: Table created successfully")
	}

	//  Create操作:新しいアイテムの作成
	user := UserProfile{
		UserID: "dynamo-gopher-001",
		Name:   "DynamoDB Gopher",
		Email:  "dynamo@gopher.com",
		Age:    25,
	}

	// Go構造体をDynamoDBのアトリビュート値に変換
	// attributevalue.MarshalMapが自動的に型変換を行う
	item, err := attributevalue.MarshalMap(user)
	if err != nil {
		log.Fatalf("Failed to marshal user: %v", err)
	}

	// アイテムをテーブルに挿入
	// PutItemは存在しない場合は作成、存在する場合は置き換え(UPSERT)
	_, err = client.PutItem(context.TODO(), &dynamodb.PutItemInput{
		TableName: aws.String(tableName),
		Item:      item,
	})
	if err != nil {
		log.Fatalf("DynamoDB put item error: %v", err)
	}
	fmt.Printf("DynamoDB: Created user profile for %s\n", user.Name)

	//  Read操作:プライマリキーによるアイテム取得
	result, err := client.GetItem(context.TODO(), &dynamodb.GetItemInput{
		TableName: aws.String(tableName),
		// プライマリキーの値を指定(必須)
		Key: map[string]types.AttributeValue{
			"user_id": &types.AttributeValueMemberS{Value: user.UserID},
		},
	})
	if err != nil {
		log.Fatalf("DynamoDB get item error: %v", err)
	}

	// アイテムが存在するかチェック
	if result.Item == nil {
		fmt.Println("DynamoDB: User not found")
		return
	}

	// DynamoDBのアトリビュート値からGo構造体に変換
	var retrievedUser UserProfile
	err = attributevalue.UnmarshalMap(result.Item, &retrievedUser)
	if err != nil {
		log.Fatalf("Failed to unmarshal user: %v", err)
	}
	fmt.Printf("DynamoDB: Retrieved user: %+v\n", retrievedUser)

	//  Update操作:既存アイテムの部分更新
	_, err = client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
		TableName: aws.String(tableName),
		// 更新対象のプライマリキーを指定
		Key: map[string]types.AttributeValue{
			"user_id": &types.AttributeValueMemberS{Value: user.UserID},
		},
		// UPDATE式で更新する内容を指定
		UpdateExpression: aws.String("SET age = :age"),
		// 式で使用する値を定義
		ExpressionAttributeValues: map[string]types.AttributeValue{
			":age": &types.AttributeValueMemberN{Value: "26"}, // N = Number型
		},
	})
	if err != nil {
		log.Fatalf("DynamoDB update item error: %v", err)
	}
	fmt.Printf("DynamoDB: Updated user age to 26\n")

	//  Delete操作:プライマリキーによるアイテム削除
	_, err = client.DeleteItem(context.TODO(), &dynamodb.DeleteItemInput{
		TableName: aws.String(tableName),
		// 削除対象のプライマリキーを指定
		Key: map[string]types.AttributeValue{
			"user_id": &types.AttributeValueMemberS{Value: user.UserID},
		},
	})
	if err != nil {
		log.Fatalf("DynamoDB delete item error: %v", err)
	}
	fmt.Printf("DynamoDB: Deleted user %s\n", user.UserID)
}

b. Google Cloud Bigtable Emulator (カラム指向)

Google Cloud Bigtableは、Googleが開発したペタバイト級スケールの高性能NoSQLデータベースサービスです。

主な特徴:

  • ペタバイトスケール: 非常に大量のデータを高速処理
  • 低遅延: 単一桁ミリ秒の読み書きレスポンス
  • HBase互換: Apache HBase APIとの互換性
  • 自動スケーリング: ノード数の動的調整
  • 統合: BigQuery、Dataflow、Dataprocとの連携

仕様・制限:

  • 行サイズ: 最大256MB(推奨は100MB以下)
  • 行キーは最大4KB、カラム修飾子は最大16KB
  • セルのバージョニング: 同一セルに複数バージョンのデータを保存可能
  • カラムファミリー内でのデータ圧縮とガベージコレクション
# Bigtable用のGoogle Cloud Go SDKをインストール
go get cloud.google.com/go/bigtable

実装のTips:

  • 行キーの設計: データの分散と範囲検索を考慮した設計が重要
  • カラムファミリー: 関連するカラムをグループ化してアクセスパターンを最適化
  • タイムスタンプ: セルのバージョニングを活用して時系列データを効率的に管理
  • エミュレーター: BIGTABLE_EMULATOR_HOST環境変数で簡単にローカル開発が可能
package main

import (
	"context" // コンテキスト管理:タイムアウト、キャンセル処理などに使用
	"fmt"     // フォーマット済み出力(コンソール表示用)
	"log"     // ログ出力とエラーハンドリング用
	"os"      // 環境変数の設定(Bigtableエミュレーター用)
	"time"    // 時間関連の操作(タイムスタンプ、TTL設定など)

	// Google Cloud Bigtable関連
	"cloud.google.com/go/bigtable" // Bigtableクライアントライブラリ
)

func bigtableExample() {
	// Bigtableエミュレーターに接続するための環境変数を設定
	// この設定により、実際のGCP Bigtableではなくローカルエミュレーターに接続される
	os.Setenv("BIGTABLE_EMULATOR_HOST", "localhost:9000")

	// コンテキストを作成 - 全てのBigtable操作で使用される
	ctx := context.Background()

	// エミュレーター用のプロジェクトID、インスタンスID、テーブルIDを設定
	// 実際のGCPプロジェクトではないが、エミュレーターでは任意の値を使用可能
	projectID := "emulator-project"
	instanceID := "emulator-instance"
	tableID := "sensor-data"

	// Bigtableクライアントを作成
	// このクライアントを通じてデータの読み書きを行う
	client, err := bigtable.NewClient(ctx, projectID, instanceID)
	if err != nil {
		log.Fatalf("Failed to create Bigtable client: %v", err)
	}
	// 関数終了時にクライアントをクローズしてリソースを解放
	defer client.Close()

	// Admin clientを作成してテーブルやインスタンスの管理を行う
	// データ操作用のclientとは別に、管理操作専用のclientが必要
	adminClient, err := bigtable.NewAdminClient(ctx, projectID, instanceID)
	if err != nil {
		log.Fatalf("Failed to create Bigtable admin client: %v", err)
	}
	// 関数終了時にadmin clientもクローズ
	defer adminClient.Close()

	// エミュレーターではインスタンスの作成は不要(または自動で作成される)
	// 実際のGCPでは明示的にインスタンスを作成する必要がある
	fmt.Println("Bigtable: Using emulator - instance creation skipped")

	// テーブルを作成
	// 既に存在する場合はエラーが返されるが、プログラムの続行には影響しない
	err = adminClient.CreateTable(ctx, tableID)
	if err != nil {
		fmt.Printf("Table creation result: %v (table may already exist)\n", err)
	}

	// カラムファミリーを作成
	// Bigtableでは関連するカラムをカラムファミリーでグループ化する
	// "readings"ファミリーにセンサーデータ関連のカラムを格納
	err = adminClient.CreateColumnFamily(ctx, tableID, "readings")
	if err != nil {
		fmt.Printf("Column family creation result: %v (family may already exist)\n", err)
	}

	fmt.Println("Bigtable: Connected to emulator and table setup completed")

	// データ操作用のテーブルハンドルを取得
	tbl := client.Open(tableID)

	// データ準備:行キーには一意性を保つためタイムスタンプを含める
	// Bigtableでは行キーでデータがソートされるため、時系列データでは重要
	timestamp := time.Now().Unix()
	rowKey := fmt.Sprintf("sensor-001#%d", timestamp)

	//  Create/Write操作
	// Mutationオブジェクトを作成して複数のカラム更新をバッチ処理
	mut := bigtable.NewMutation()
	// カラムファミリー"readings"内の各カラムに値を設定
	// bigtable.Now()は現在のタイムスタンプをマイクロ秒単位で取得
	mut.Set("readings", "temperature", bigtable.Now(), []byte("23.5"))
	mut.Set("readings", "humidity", bigtable.Now(), []byte("65.0"))
	mut.Set("readings", "location", bigtable.Now(), []byte("tokyo"))

	// Mutationを実際のテーブルに適用
	err = tbl.Apply(ctx, rowKey, mut)
	if err != nil {
		log.Fatalf("Bigtable apply error: %v", err)
	}
	fmt.Printf("Bigtable: Wrote sensor data for row %s\n", rowKey)

	//  Read操作
	// 指定した行キーの全データを読み取り
	row, err := tbl.ReadRow(ctx, rowKey)
	if err != nil {
		log.Fatalf("Bigtable read error: %v", err)
	}

	// 読み取ったデータを表示
	// rowはmap[string][]bigtable.ReadItem形式で返される
	fmt.Printf("Bigtable: Read row %s with %d column families\n", rowKey, len(row))
	for family, columns := range row {
		fmt.Printf("  Family: %s\n", family)
		for _, column := range columns {
			// column.Columnはfamily:columnの形式、column.Valueは[]byte
			fmt.Printf("    Column: %s, Value: %s\n", column.Column, string(column.Value))
		}
	}

	//  Update操作
	// Bigtableでは同じセルに新しいタイムスタンプで書き込むことで更新
	// 古いバージョンのデータも保持される(バージョニング)
	mut = bigtable.NewMutation()
	mut.Set("readings", "temperature", bigtable.Now(), []byte("24.0"))
	err = tbl.Apply(ctx, rowKey, mut)
	if err != nil {
		log.Fatalf("Bigtable update error: %v", err)
	}
	fmt.Printf("Bigtable: Updated temperature reading for row %s\n", rowKey)

	//  Delete操作
	// 行全体を削除するMutationを作成
	mut = bigtable.NewMutation()
	mut.DeleteRow()
	err = tbl.Apply(ctx, rowKey, mut)
	if err != nil {
		log.Fatalf("Bigtable delete error: %v", err)
	}
	fmt.Printf("Bigtable: Deleted row %s\n", rowKey)

	// テーブルを削除(クリーンアップ)
	// 本番環境では慎重に行うべき操作
	err = adminClient.DeleteTable(ctx, tableID)
	if err != nil {
		fmt.Printf("Table deletion result: %v\n", err)
	} else {
		fmt.Println("Bigtable: Table deleted successfully")
	}
}

Docker Compose設定

上記のサンプルを実行するには、以下のDockerサービスをdocker-compose.ymlに追加

  dynamodb-local:
    image: amazon/dynamodb-local:latest
    container_name: my-dynamodb-local
    ports:
      - "8000:8000"
    command: ["-jar", "DynamoDBLocal.jar", "-inMemory", "-sharedDb"]
    volumes:
      - dynamodb-data:/home/dynamodblocal/data

  bigtable-emulator:
    image: gcr.io/google.com/cloudsdktool/cloud-sdk:latest
    container_name: my-bigtable-emulator
    ports:
      - "9000:9000"
    # cbtemulator: Cloud Bigtable emulator command (直接実行を試行、失敗時はgcloudコマンドにフォールバック)
    command: >
      sh -c "
        cbtemulator -port 9000 -host 0.0.0.0 ||
        gcloud beta emulators bigtable start --host-port=0.0.0.0:9000
      "
    environment:
      - BIGTABLE_EMULATOR_HOST=localhost:9000

9. クラウドNoSQLへの接続・管理・操作方法 🌐📋

ローカルエミュレーターを使用したクラウドNoSQLサービスにも、専用の管理ツールやクライアントが用意されています。

DynamoDB Local への接続

1. AWS CLI (コマンドライン)

aws dynamodb list-tables --endpoint-url http://localhost:8000

AWS CLIを使用してローカルのDynamoDB Localのテーブル一覧を取得(プロファイル設定は不要)。

aws dynamodb describe-table --table-name users --endpoint-url http://localhost:8000

指定したテーブルの詳細情報を確認。

aws dynamodb scan --table-name users --endpoint-url http://localhost:8000

テーブルの全データをスキャンして取得。

2. NoSQL Workbench (GUI)
Amazon公式のDynamoDB用GUIツールです。

# ダウンロード: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.html
# 接続設定:
# - Connection name: DynamoDB Local
# - Hostname: localhost
# - Port: 8000

3. DynamoDB Admin (Web UI)
サードパーティの軽量Webインターフェースです。

npx dynamodb-admin

Node.jsで簡単にDynamoDB Admin WebUIを起動。

ブラウザで http://localhost:8001 にアクセスすると、自動的にlocalhost:8000のDynamoDB Localを認識します。

DynamoDB Localでの基本操作

テーブル一覧表示

aws dynamodb list-tables --endpoint-url http://localhost:8000

テーブル一覧を表示。

テーブル作成

aws dynamodb create-table \
  --table-name products \
  --attribute-definitions AttributeName=product_id,AttributeType=S \
  --key-schema AttributeName=product_id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --endpoint-url http://localhost:8000

productsテーブルを作成。

アイテムの追加

aws dynamodb put-item \
  --table-name products \
  --item '{"product_id":{"S":"prod001"},"name":{"S":"ノートPC"},"price":{"N":"98000"},"category":{"S":"電子機器"}}' \
  --endpoint-url http://localhost:8000

アイテムをテーブルに追加。

アイテムの取得

aws dynamodb get-item \
  --table-name products \
  --key '{"product_id":{"S":"prod001"}}' \
  --endpoint-url http://localhost:8000

指定したキーのアイテムを取得。

全アイテムのスキャン

aws dynamodb scan --table-name products --endpoint-url http://localhost:8000

テーブル内の全アイテムをスキャンして取得。

アイテムの更新

aws dynamodb update-item \
  --table-name products \
  --key '{"product_id":{"S":"prod001"}}' \
  --update-expression "SET price = :p" \
  --expression-attribute-values '{":p":{"N":"89000"}}' \
  --endpoint-url http://localhost:8000

アイテムの価格を更新。

アイテムの削除

aws dynamodb delete-item \
  --table-name products \
  --key '{"product_id":{"S":"prod001"}}' \
  --endpoint-url http://localhost:8000

指定したキーのアイテムを削除。

Bigtable Emulator への接続

1. cbt CLI (Cloud Bigtable tool)

go install google.golang.org/api/internal/cbtool/cbt@latest

cbt toolをインストール。

export BIGTABLE_EMULATOR_HOST=localhost:9000

エミュレーター用の環境変数を設定。

cbt -project emulator-project -instance emulator-instance ls

テーブル一覧を表示。

cbt -project emulator-project -instance emulator-instance ls sensor-data

指定したテーブルの詳細を表示。

2. gcloud CLI (Google Cloud SDK)

# Google Cloud SDKのBigtableコマンド
# エミュレーター環境変数が設定されていれば自動的にエミュレーターに接続

# プロジェクトを設定(エミュレーターでは任意)
gcloud config set project emulator-project

# Bigtableインスタンス一覧(エミュレーターでは限定的)
gcloud bigtable instances list

3. HBase Shell互換インターフェース
BigtableはHBase互換なので、HBase shellも使用可能です。

# HBase shellをエミュレーターモードで起動(高度な使用方法)
# 主にスキーマ管理や高度なクエリに使用

Bigtable Emulatorでの基本操作

環境変数の設定

# 前提: 環境変数の設定
export BIGTABLE_EMULATOR_HOST=localhost:9000

テーブル一覧表示

cbt -project my-project -instance my-instance ls

テーブル一覧を表示。

テーブルとカラムファミリーの作成

cbt -project my-project -instance my-instance createtable logs

logsテーブルを作成。

cbt -project my-project -instance my-instance createfamily logs data

logsテーブルにdataカラムファミリーを作成。

データの挿入

cbt -project my-project -instance my-instance set logs "2024-01-15#user001" data:action=login data:timestamp=1642248000 data:ip=192.168.1.100

ログデータを行に挿入。

データの読み取り

cbt -project my-project -instance my-instance lookup logs "2024-01-15#user001"

指定した行のデータを読み取る。

範囲読み取り

cbt -project my-project -instance my-instance read logs start="2024-01-15" end="2024-01-16"

指定した範囲内の行データを読み取る。

特定のカラムのみ読み取り

cbt -project my-project -instance my-instance read logs columns=data:action

特定のカラム(data:action)のみを読み取る。

データの更新

cbt -project my-project -instance my-instance set logs "2024-01-15#user001" data:action=logout

新しいタイムスタンプでデータを上書き更新。

行の削除

cbt -project my-project -instance my-instance deleterow logs "2024-01-15#user001"

指定した行を削除。

テーブル一覧表示

cbt -project my-project -instance my-instance ls

テーブル一覧を表示。


基本CRUD操作一覧

以下は各クラウドNoSQLサービスでの基本的なCRUD操作をまとめたコマンド一覧です。

DynamoDB Local (AWS CLI)

セットアップ

aws dynamodb create-table \
  --table-name users \
  --attribute-definitions AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --endpoint-url http://localhost:8000

usersテーブルを作成。

aws dynamodb list-tables --endpoint-url http://localhost:8000

作成済みテーブルの一覧を表示。

Create(作成)

aws dynamodb put-item \
  --table-name users \
  --item '{"id":{"S":"user1"},"name":{"S":"Alice"},"email":{"S":"alice@example.com"}}' \
  --endpoint-url http://localhost:8000

単一項目をテーブルに挿入。

aws dynamodb batch-write-item \
  --request-items '{"users":[{"PutRequest":{"Item":{"id":{"S":"user2"},"name":{"S":"Bob"}}}},{"PutRequest":{"Item":{"id":{"S":"user3"},"name":{"S":"Carol"}}}}]}' \
  --endpoint-url http://localhost:8000

複数項目を一括でテーブルに挿入。

Read(読み取り)

aws dynamodb get-item \
  --table-name users \
  --key '{"id":{"S":"user1"}}' \
  --endpoint-url http://localhost:8000

単一項目を主キーで取得。

aws dynamodb scan \
  --table-name users \
  --endpoint-url http://localhost:8000

テーブル内の全項目をスキャンして取得。

aws dynamodb scan \
  --table-name users \
  --filter-expression "attribute_exists(email)" \
  --endpoint-url http://localhost:8000

emailフィールドが存在する項目のみを条件付きでスキャン。

aws dynamodb scan \
  --table-name users \
  --select COUNT \
  --endpoint-url http://localhost:8000

テーブル内の項目数をカウント。

Update(更新)

aws dynamodb update-item \
  --table-name users \
  --key '{"id":{"S":"user1"}}' \
  --update-expression "SET email = :e" \
  --expression-attribute-values '{":e":{"S":"newalice@example.com"}}' \
  --endpoint-url http://localhost:8000

項目のemailフィールドを部分更新。

aws dynamodb update-item \
  --table-name users \
  --key '{"id":{"S":"user1"}}' \
  --update-expression "SET #n = :name, age = :age" \
  --expression-attribute-names '{"#n":"name"}' \
  --expression-attribute-values '{":name":{"S":"Alice Updated"},":age":{"N":"30"}}' \
  --endpoint-url http://localhost:8000

複数の属性(nameとage)を同時に更新。

Delete(削除)

aws dynamodb delete-item \
  --table-name users \
  --key '{"id":{"S":"user1"}}' \
  --endpoint-url http://localhost:8000

指定したキーの単一項目を削除。

aws dynamodb batch-write-item \
  --request-items '{"users":[{"DeleteRequest":{"Key":{"id":{"S":"user2"}}}},{"DeleteRequest":{"Key":{"id":{"S":"user3"}}}}]}' \
  --endpoint-url http://localhost:8000

複数項目を一括で削除。

aws dynamodb delete-table \
  --table-name users \
  --endpoint-url http://localhost:8000

テーブル全体を削除。

Bigtable Emulator (cbt CLI)

前提条件

# 環境変数を設定
export BIGTABLE_EMULATOR_HOST=localhost:9000

セットアップ

cbt -project emulator-project -instance emulator-instance createtable users

usersテーブルを作成。

cbt -project emulator-project -instance emulator-instance createfamily users cf1

usersテーブルにcf1カラムファミリーを作成。

cbt -project emulator-project -instance emulator-instance ls

作成済みテーブルの一覧を表示。

Create(作成)

cbt -project emulator-project -instance emulator-instance set users row1 cf1:name=Alice cf1:email=alice@example.com

単一行に複数のカラムを設定。

cbt -project emulator-project -instance emulator-instance set users row2 cf1:name=Bob

単一行に単一のカラムを設定。

cbt -project emulator-project -instance emulator-instance set users row3 cf1:name=Carol cf1:age=25

行に混合データ(nameとage)を設定。

Read(読み取り)

cbt -project emulator-project -instance emulator-instance read users

テーブル内の全行を読み取ります。

cbt -project emulator-project -instance emulator-instance lookup users row1

特定の行(row1)を読み取ります。

cbt -project emulator-project -instance emulator-instance read users prefix=row

指定したプレフィックス(row)で始まる行を読み取ります。

cbt -project emulator-project -instance emulator-instance read users start=row1 end=row3

指定した範囲(row1からrow3)の行を読み取ります。

cbt -project emulator-project -instance emulator-instance read users columns=cf1:name

特定のカラム(cf1:name)のみを読み取ります。

cbt -project emulator-project -instance emulator-instance count users

テーブル内の行数をカウント。

Update(更新)

cbt -project emulator-project -instance emulator-instance set users row1 cf1:email=newalice@example.com

特定のセル(cf1:email)を更新。

cbt -project emulator-project -instance emulator-instance set users row1 cf1:name="Alice Updated" cf1:status=active

複数のセル(nameとstatus)を同時に更新。

Delete(削除)

cbt -project emulator-project -instance emulator-instance deleterow users row1

特定の行(row1)を削除。

cbt -project emulator-project -instance emulator-instance deletecells users row2 cf1:email

特定のセル(row2のcf1:email)を削除。

cbt -project emulator-project -instance emulator-instance deleteallrows users

テーブル内の全行を削除。

cbt -project emulator-project -instance emulator-instance deletetable users

テーブル全体を削除。

実践的な使用例

MongoDBでのユーザー管理例:

Docker経由でMongoDBに接続

docker exec -it my-mongo mongosh -u root -p example

実際の操作:

userdbデータベースを使用

use userdb

ユーザーを挿入

db.users.insertOne({name: "山田太郎", email: "yamada@example.com", age: 30})

年齢25歳以上のユーザーを検索

db.users.find({age: {$gte: 25}})

山田太郎の年齢を更新

db.users.updateOne({name: "山田太郎"}, {$set: {age: 31}})

DynamoDBでのセッション管理例:

セッションテーブルにセッション情報を作成・操作

aws dynamodb put-item \
  --table-name sessions \
  --item '{"session_id":{"S":"sess_123"},"user_id":{"S":"user_456"},"expires":{"N":"1640995200"}}' \
  --endpoint-url http://localhost:8000

TTL(有効期限)付きでセッション情報を取得

aws dynamodb get-item \
  --table-name sessions \
  --key '{"session_id":{"S":"sess_123"}}' \
  --endpoint-url http://localhost:8000

10. クラウドプラットフォームのNoSQLサービス (AWS & GCP) ☁️

自前でサーバーを管理するのは大変です。そこで、AWSやGCPが提供するマネージドNoSQLサービスが非常に便利です。

a. AWS (Amazon Web Services)

  • Amazon DynamoDB: AWSを代表するNoSQLです。キーバリューとドキュメントの両方のモデルをサポートするフルマネージドサービスです。
  • Amazon ElastiCache: マネージドなRedisまたはMemcachedサービスです。
  • Amazon Keyspaces (for Apache Cassandra): サーバーレスのCassandra互換データベースです。
  • Amazon DocumentDB: MongoDB互換のドキュメントデータベースです。
  • Amazon Neptune: フルマネージドのグラフデータベースです。

b. GCP (Google Cloud Platform)

  • Cloud Firestore / Cloud Datastore: サーバーレスのドキュメントデータベースです。
  • Cloud Bigtable: カラム指向のペタバイト級NoSQLデータベースで、HBase互換です。
  • Memorystore: フルマネージドなRedisとMemcachedサービスです。
  • GCPには直接的なNeo4j互換のマネージドサービスはありませんが、MarketplaceからNeo4jインスタンスを簡単にデプロイできます。

NoSQLは、現代のアプリケーションが直面する多様な課題を解決するための強力なツールです。RDBとの違いを理解し、4つの主要なタイプの特徴を把握することで、プロジェクトに最適なデータベースを選んでみましょう!

まずはDockerで気軽に触れてみて、Go言語でコードを書き、クラウドのマネージドサービスも視野に入れながら、NoSQLの世界を存分に楽しんでみましょう!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?