技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

乱数の解析行為とその対策

私がプレイしているオンラインゲームで話題になったので、乱数の解析行為とその対策について、簡単にまとめます。
 
【コンピューターにおける乱数とは】
「乱数」とは、その名の通り、ランダムな数値のことを指します。
しかし、コンピューター上で乱数を生成する場合、特定のアルゴリズムにより疑似的に生成しており、正確に言うと完全にランダムではないため、専門的には「疑似乱数」と呼ばれます。
 
例えば、平易なアルゴリズムの例として「線形合同法」が挙げられます。
このアルゴリズムでは、「初期値(シード)」と呼ばれるXn(n=0)の値を与え、Xn(n=1)の値をXn(n=0)を元に算出し、Xn(n=2)の値をXn(n=1)を元に算出し…といった形で、前回の乱数の生成値から今回の乱数の生成値を求めています。


 
他にも疑似乱数を生成するアルゴリズムの種類はいくつか存在しますが、前回の乱数の生成値から今回の乱数の生成値を求めている所は変わりありません。
 
【疑似乱数の解析行為】
疑似乱数では、アルゴリズムを知っている状態で実際に出力される生成値を観察することで、初期値を解析できる場合があります。
そして、この解析に成功した場合、初期値から順番に計算を行うことで、Xnの値を知ることができます。
 
例えば、先ほどの「線形合同法」がアルゴリズムとして適用されている場合において、Xn(n=1)から順番に
2, 5, 1, 4, 0, 3...
といった値が観察される場合、初期値は6であると解析することができます。
(M = 7, A = 1, B = 3)
 
通常、実際に使用される乱数のアルゴリズムはより複雑なものですが、同じ要領で、複数の生成値から初期値を解析することができる場合があります。
 
【解析行為への対策】
疑似乱数は、暗号鍵の生成のようなセキュリティ分野や、オンラインカジノのようなギャンブル等の分野でも用いられます。
これらの分野で疑似乱数を解析され、乱数の生成値が推測された場合、何かしらの犯罪行為が行われて不利益を受ける可能性あるため、そのような分野で疑似乱数を用いる場合は解析対策を行う必要があります。
 
対策を行う上では、「暗号論的疑似乱数」と呼ばれる、学術的に解析が困難なアルゴリズムを用いて乱数を生成することが有効です。 
(ただし、「暗号論的疑似乱数」は計算量が多いため、シミュレーションや一人向けゲームのような解析対策を行う必要がない分野においては、通常の疑似乱数の方が適しています)
 
【疑似乱数の実装方法】
各言語で、疑似乱数や暗号論的疑似乱数を生成するためのライブラリが用意されています。
例えば、Javaの場合は以下のようにライブラリを使用して実装することができます。
 
・疑似乱数
public class Main {
    public static void main(String[] args) throws Exception {
        double randomNumber = Math.random();
        System.out.println(randomNumber);
    }
}
 
・暗号論的疑似乱数
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class Main {
    public static void main(String[] args) throws Exception {
        try {
            SecureRandom randomNumber = SecureRandom.getInstance("SHA1PRNG");
            System.out.println(randomNumber.nextDouble());
        } catch (NoSuchAlgorithmException e) { 
            // 例外処理
        }
    }
}

私が所属する会社からSpring Frameworkの入門書が発行されました!

以前に、私が所属する会社からJava入門者向け書籍「絶対にJavaプログラマーになりたい人へ」を発行しましたが、その別冊をこの度発行しました!
今回も、私が執筆の多くの部分に関わっています。
 
解説の範囲をSpring Frameworkに限定することで、より容易に理解できるようにきめ細かく解説しています。
 

Kindleで販売しており、購入はこちらからできます。
ご興味がある方は、年末年始の時間がある時に手にとってみられてはいかがでしょうか?

 

結合条件で件数が膨らむと性能が悪化する

SQLでのクエリの性能改善というと、「インデックスを設定する」「本当にインデックスが使われているか実行計画を見て確認する」といった、RDBMSの物理的な機能の活用に注目しがち。
 
しかし、それ以前の問題として、「クエリの最中に件数が増加しないようにSQLを書く」という、論理的な観点での工夫により性能改善が図ることができるケースも多いです。特に、性能を考慮せずに業務的な手続きをそのままSQLに書き起こしているような場合は、論理的な観点での性能改善の余地が残っていることが多いです。
 
具体的に言えば、テーブルをJOIN句で結合する際に、結合順や処理順を工夫することで、クエリの最中に件数が増加することを防ぐことができ、性能改善を図ることができます。
そのことを、この記事では説明していきます。
 
----
 
まずは、テーブル結合により件数が膨らむ現象について説明します。
 
これは、件数が膨らまない場合です。


結合元の商品マスタは3件、結合先の商品ページ閲覧履歴は3件、共に商品コードで一意にレコードを特定できます。
以下のようなクエリを発行し、商品名ごとの閲覧回数を取得する場合も、商品マスタの件数と同じ件数の3件が取得されます。
a.商品名 AS 商品名,
b.閲覧回数 AS 閲覧回数
FROM 商品マスタ AS a
JOIN 商品ページ閲覧履歴 AS b
ON a.商品コード = b.商品コード
;
 
次に、結合先のテーブルの件数に引きずられて件数が膨らむ場合です。

結合元の商品マスタは3件、結合先の購入履歴は5件です。
商品マスタは商品コードで一意にレコードを特定できますが、購入履歴は顧客コードと商品コードの組み合わせでないと一意にレコードを特定できません。
以下のようなクエリを発行し、商品名ごとに購入日を取得する場合、結合元の件数である3件を超え、結合先の件数に引きずられ、5件が取得されます。
a.商品名 AS 商品名,
b.購入日 AS 購入日
FROM 商品マスタ AS a
JOIN 購入履歴 AS b
ON a.商品コード = b.商品コード
;
 
そして、直積結合のような形で、結合元や結合先のテーブルの件数を超えて件数が膨らむ場合です。

結合元の商品マスタは3件、結合先のメーカーマスタも3件です。
共にメーカーコードだけでは一意にレコードを特定できず、商品マスタは商品コード、メーカーマスタはメーカーコードと廃止日で一意にレコードを特定します。
以下のようなクエリを発行し、商品名ごとにメーカー名を取得する場合、結合元や結合先の件数である3件を超えて、5件のレコードが取得されます。
a.商品名 AS 商品名,
b.メーカー名 AS メーカー名,
b.廃止日 AS 廃止日
FROM 商品マスタ AS a
JOIN メーカーマスタ AS b
ON a.メーカーコード = b.メーカーコード
;
 
----
 
ここまでの例を見てお気づきかもしれませんが、テーブルのレコードを一意に特定できないカラムを結合条件として指定すると、件数が膨らんでしまいます。
 
テーブルのレコードを一意に特定するには、主キーや一意キーを結合条件として指定するのが間違いありませんが、それ以外のカラムを指定する場合も、設計上もしくは運用上、一意に特定できることもあります。
実際の設計や運用を考慮し、言い換えると実際に入っているデータに着目して、テーブルのレコードを一意に特定できているかどうか調べる方法としては、以下の2つのクエリを投げて結果が一致するかどうかを見る方法があります。一致すればレコードを一意に特定できていますし、一致しなければレコードを一意に特定できていません。乖離が大きければ大きいほど、件数が大きく膨らむ可能性が高まります。
・SELECT COUNT(*) FROM 対象テーブル;
・SELECT COUNT(*) FROM (SELECT DISTINCT 結合条件 FROM 対象テーブル);
 
----
 
このように、結合条件によっては、テーブル結合により件数が膨らむことがあります。
テーブル結合を何度も行うような長いクエリの場合、中間結果の件数が膨らむと、性能が大きく悪化することがあります。
 
それを防ぐために一番良い方法は、結合条件を見直し、件数が膨らまないようなテーブル結合を行うことです。
しかし、業務要件上、それは難しい場合が多いです。
 
結合条件の見直しが難しい場合、以下のような対策により、中間結果の件数の膨らみを最小限にすることが有効になります。
・WHERE句や集計(集合関数やDISTINCT)による件数の絞り込みはなるべく早い段階で行う
・その上で、件数が膨らむテーブル結合はなるべく遅らせる
 
なお、内部結合(JOIN・INNER JOIN)に関しては、単に件数を絞るために記述することもあるので、その場合はExistsやサブクエリに書き換えることで性能が改善する可能性があります。
ただし、これはRDBMSやテーブル定義による所が多く、逆に性能が悪化する可能性も否定できないので、一般化できるものではなく、この方法を使うとしても実行計画を調べてから使うのが望ましいです。

私が所属する会社から入門書が発行されました!スクールも同時開校です!

表題の通り、Javaプログラミング入門書「絶対にJavaプログラマーになりたい人へ」が発行されました!
Amazon kindleで読むことができます。
 
私も執筆の多くの部分に関わっています。
 
ご興味がある方は是非とも手に取ってみてください!


 
同時に、プログラミングスクール「サイゼントアカデミー」が開校されました!
サポートやフィードバックが欲しくなりましたらこちらも是非!
 
詳しい情報は、会社のブログにて書かせていただきました。

cyzennt.co.jp

ITシステムの安定稼働は神頼み?

GWということで、ITシステムのリリースを控えている会社は少なくないと思います。
リリースするシステムは是が非でも安定稼働させたいものです。
 
そこで、神様の力を借りて、システムの安定稼働を祈願するのはいかがでしょうか?
嘘のような話かもしれませんが、神社に出向いたり、祭壇を作ったり、お札を貼ったりする現場は実在しますし、筆者の経験上、その効果は無視できないものであると思っています。
 
----
 
東京都内で働く人にとって最も有名で手が出しやすいのは、神田明神だと思います。
神田明神の公式ページはこちらです。

www.kandamyoujin.or.jp


 
公式ページの「アクセス」の通りですが、御茶ノ水駅から徒歩5分、新御茶ノ水駅から徒歩5分、末広町駅から徒歩5分、秋葉原駅から徒歩7分と、都内で働いている人にとってはアクセスしやすい場所にあります。
また、「FAQ」の通り、この神社はITの安全を祈願する場所でもあります。「IT情報安全守護」というお札も販売されています。
 
----
 
神頼みには科学的な根拠はないかもしれませんが、筆者の経験上、関係者の精神面、特に「神頼み」という発想に至るまでの過程において、何かしらの効果はあると思っています。
 
どれだけ準備を怠らなかったとしても、システムが安定稼働するかどうかは運次第な所が要素が残ります。
ハードウェアが故障するかもしれませんし、連携している他社のシステムの障害に巻き込まれるかもしれません。
そして、人間がすることなので、ミスを完全にゼロにすることもできません。
 
運だと思えることまで徹底して準備を行い、それでも何かできることがないかと考えた末に辿り着くのが「神頼み」という発想です。
目の前の準備に追われ、余裕がないプロジェクトにおいては、神頼みしようと思うような精神状態ではなくなります。
「神頼み」という発想に至った時点で、品質は高い傾向にあり、実際に安定稼働もする、というのが、筆者の見解です。
 
筆者も、「神頼み」という発想に至るまで準備を怠らないようにしたいものです。

JPAでのDelete実装方法三選

Spring Framework + JPAでのDelete実装方法について、調べるのに少し苦労しましたので、実装方法をまとめます。
 
----
 
1.CrudRepositoryインターフェースのメソッドを利用する
全件削除、1つ~複数のエンティティクラスのインスタンスを指定して削除、1つ~複数の主キーの値を指定して削除するだけであれば、CrudRepositoryインターフェースのメソッドを利用することでシンプルな実装で対応できます。
CrudRepositoryインターフェースのメソッドについては、以下の公式ドキュメントで説明されています。
https://spring.pleiades.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html
 
以下は、全件削除を行うサンプルコードです。
(コードは一部を抜き出したものです)
 
【エンティティクラス】
・Message.java
package com.example.demo.domain.message.model;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;

import lombok.Data;

@Entity
@Table(name="message")
@Data
public class Message {
    
    // ID
    @Id // 主キーに対して付与
    private int id;

    // 投稿内容
    private String text;
    
    // 種別ID
    private String kindId;
}
 
【Daoクラス】
・MessageDao.java
package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.domain.message.model.Message;

public interface MessageDao extends JpaRepository<Message,String> {

}
 
【Serviceクラス】
今回の例では、ServiceクラスにCrudRepositoryインターフェースのメソッドを記述します。

・MessageService.java
package com.example.demo.domain.message.service;

import org.springframework.transaction.annotation.Transactional;

public interface MessageService {

    /** 削除(全件) */
    @Transactional // メソッドを抜ける時にcommit発行
    public void deleteAll();

}
 
・MessageServiceImpl.java
package com.example.demo.domain.message.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.domain.message.service.MessageService;
import com.example.demo.repository.MessageDao;

@Service
public class MessageServiceImpl implements MessageService {

    @Autowired
    private MessageDao dao;
    
    /** 全件削除 */
    @Override
    public void deleteAll() {
        dao.deleteAll();
        return;
    }

}
 
【Controllerクラス】
今回のサンプルでは省略しますが、Serviceクラスのメソッドを呼び出すことで、削除が実行されます。
 
----
 
2.SQL文を直接記述する
極力避けるべきではありますが、要件が複雑な場合はSQL文を直接記述せざるを得なくなる場合があります。
今回の例では、「主キー以外の項目を削除条件に指定する」「削除件数を取得する」という要件を満たすためにSQL文を直接記述します。
 
【エンティティクラス】
先ほどの例と同じです。
 
【Daoクラス】
・MessageDao.java
package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.domain.message.model.Message;

public interface MessageDao extends JpaRepository<Message,String> {

    /* JPQL定数 */
    final String JPQL_DELETE_MESSAGE_BY_KINDID = " DELETE "
                                                + " FROM Message m "
                                                + " WHERE m.kindId = :kindId ";
    
    /** 削除(種別ID指定) */
    @Modifying
    @Query(JPQL_DELETE_MESSAGE_BY_KINDID)
    public long deleteByKindId(String kindId) throws DataAccessException;

}
 
【Serviceクラス】
・MessageService.java
package com.example.demo.domain.message.service;

import org.springframework.transaction.annotation.Transactional;

public interface MessageService {

    /** 削除(全件) */
    @Transactional // メソッドを抜ける時にcommit発行
    public long deleteAll();

}
 
・MessageServiceImpl.java
package com.example.demo.domain.message.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.domain.message.service.MessageService;
import com.example.demo.repository.MessageDao;

@Service
public class MessageServiceImpl implements MessageService {

    @Autowired
    private MessageDao dao;
    
    /** 種別ID="0001"を削除 */
    @Override
    public long deleteAll() {
        return dao.deleteByKindId("0001");
    }

}
 
【Controllerクラス】
Serviceクラスのメソッドを呼び出すことで、削除が実行されます。
 
----
 
3.Derived deleteBy Methodsを使用する
「主キー以外の項目を削除条件に指定する」「削除件数を取得する」という要件であれば、Derived deleteBy Methodsを使用することで、SQL文を直接記述せずとも実現できます。
Derived deleteBy Methodsについては、以下のページで使い方が解説されています。
https://www.baeldung.com/spring-data-jpa-deleteby
 
以下は使用例です。
 
【エンティティクラス】
先ほどの例と同じです。
 
【Daoクラス】
・MessageDao.java
package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.domain.message.model.Message;

public interface MessageDao extends JpaRepository<Message,String> {

    /** 削除(種別ID指定) */
    public Long deleteByKindId(String kindId);

}
 
【Serviceクラス】
【Controllerクラス】
先ほどの例と同じです。