Skip to content

Hazelcast上のデータをDBに永続化

Hazelcast上にキャッシュされたデータをDB上に永続化するための設定です。

cf.

事前準備

下記SQLによって構築されるデータベースに向けてデータを永続化することを想定します:

-- hazelcast向けMapping作成SQL
CREATE MAPPING person (
    __key INT
    , name VARCHAR
    , password VARCHAR
    , age INT
)
TYPE IMap
OPTIONS (
    'keyFormat'='int'
    , 'valueFormat' = 'compact'
    , 'valueCompactTypeName' = 'person'
);
-- MySQL向けテーブル作成SQL
CREATE DATABASE eadb;
USE eadb;

CREATE TABLE IF NOT EXISTS person (
    id bigint PRIMARY KEY
    , name varchar(8)
    , password varchar(32)
    , age int
);

設定

hazelcast-docker.xml

設定ファイルに下記を追記します:

<hazelcast>
    <data-connection name="my-mysql-database">
        <type>JDBC</type>
        <properties>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/eadb</property>
            <property name="user">root</property>
            <property name="password">password</property>
        </properties>
        <shared>true</shared>
    </data-connection>

    <map name="person">
        <map-store enabled="true">
            <class-name>com.hazelcast.mapstore.GenericMapStore</class-name>
            <properties>
                <property name="data-connection-ref">my-mysql-database</property>
            </properties>
            <write-delay-seconds>5</write-delay-seconds>
            <write-batch-size>100</write-batch-size>
        </map-store>
    </map>
</hazelcast>

Goアプリケーションでデータ投入

事前にライブラリをインストールしておいてください:

go get github.com/hazelcast/hazelcast-go-client

Mappingに対応する構造体宣言

// ユーザ情報の構造体です。
type Person struct {
    Name     string // 名前
    Password string // パスワード
    Age      int32  // 年齢
}

Serializerの実装

type PersonSerializer struct{}

func (s PersonSerializer) Type() reflect.Type {
    return reflect.TypeFor[*Person]()
}

func (s PersonSerializer) TypeName() string {
    return "person"
}

func (s PersonSerializer) Write(writer serialization.CompactWriter, value any) {

    person := value.(*Person)
    writer.WriteString("name", &person.Name)
    writer.WriteString("password", &person.Password)
    writer.WriteInt32("age", person.Age)
}

func (s PersonSerializer) Read(reader serialization.CompactReader) any {
    return Person{
        Name:     *reader.ReadString("name"),
        Password: *reader.ReadString("password"),
        Age:      reader.ReadInt32("age"),
    }
}

Hazelcastクライアント作成

// hazelcastのクライアントを返します。
func HazelcastClient(ctx context.Context) *hazelcast.Client {

    cfg := hazelcast.Config{}
    cfg.Cluster.Name = "nob-hazelcast"

    // シリアライザ登録
    cfg.Serialization.Compact.SetSerializers(PersonSerializer{})

    hz, err := hazelcast.StartNewClientWithConfig(ctx, cfg)
    if err != nil {
        panic(fmt.Errorf("starting the client with config: %w", err))
    }

    return hz
}

保存処理の実装

// ユーザ情報を保存します。
func Save(key int32, value *Person) {

    ctx := context.TODO()

    hz := HazelcastClient(ctx)

    mp, err := hz.GetMap(ctx, "person")
    if err != nil {
        panic(fmt.Errorf("trying to get a map: %w", err))
    }

    err = mp.Set(ctx, key, value)
    if err != nil {
        panic(err)
    }
}

取得処理の実装

// 指定したキーのユーザ情報を取得します。
func Get(key int32) Person {

    ctx := context.TODO()

    hz := HazelcastClient(ctx)

    mp, err := hz.GetMap(ctx, "person")
    if err != nil {
        panic(fmt.Errorf("trying to get a map: %w", err))
    }

    val, err := mp.Get(ctx, key)
    if err != nil {
        panic(err)
    }

    return val.(Person)
}

SQLでデータ投入

下記SQLでHazelcastのMapping上にデータが投入され、非同期でMySQLに永続化されます:

-- hazelcast向けサンプルデータ投入SQL
INSERT INTO person (
    __key, name, password, age
) values (
    1, 'nob', 'passwd', 13
);