Dockerを使ってTsurugi環境構築を行う際の注意点

はじめに

このエントリは Tsurugi Advent Calendar 2023 の17日目のエントリです。前日は hishidama さんによる「 Tsurugiのdrop table 」でした。

Tsurugiに限らず、最近は手元で開発用のサーバー類を立ち上げたい場合、Dockerコンテナとして立ち上げるのが楽ですよね。簡単に環境を構築できて簡単に捨てられるので。

TsurugiももちろんDockerイメージが用意されているので簡単にサーバーを立ち上げて開発を始めることができます (公式のDockerユーザーガイドのリンク) 。 ただ自分がTsurugiのDockerコンテナを立ち上げた際にいくつか引っ掛かったことがあったので、このエントリではその内容について触れていきます。

Dockerイメージの入手と起動

TsurugiのDockerイメージは Github Container Registry 上で公開 しています。 次のように docker pull する際に ghcr.io を頭に付けると引っ張ってこれます。

docker pull ghcr.io/project-tsurugi/tsurugidb:latest

本エントリ執筆時点では latest タグで 1.0.0-BETA2 が落ちてきます。

バインドするポートを指定して起動すれば、そのまま立ち上がります。

docker container run -d -p 12345:12345 --name tsurugi ghcr.io/project-tsurugi/tsurugidb:latest

TCP12345ポートで接続を受け付けるので、SQLコンソールである TanzawaJava APIIceaxe で12345ポートに接続して利用します。

設定ファイルをホスト管理する方法とその際の注意点

Dockerコンテナとしてサーバーを管理していても、設定ファイル類などはホスト側で管理したいことが多いと思います。TsurugiのDockerイメージでは設定ファイル格納ディレクトリは /usr/lib/tsurugi/var/etc になっているので、このディレクトリをホスト側の任意のディレクトリとバインドマウントします。

docker container run -d -p 12345:12345 -v <ホスト側のパス>:/usr/lib/tsurugi/var/etc  --name tsurugi ghcr.io/project-tsurugi/tsurugidb:latest

ここでまず注意点があります。 /usr/lib/tsurugi/var/etc とバインドしたホスト側のディレクトリに設定ファイル tsurugi.ini が配置されていないと次のようなログを残してTsurugiが起動に失敗します。

could not launch tsurugidb, as cannot find any valid configuration file

設定ファイルが存在しない状態では起動できません。そのため必ずこのディレクトリに tsurugi.ini を配置して起動してください。

tsurugi.ini に記載する設定項目には初期値が設定されているため、ほとんどの項目は記載しなくても動きます。ですが datastore セクションの log_location だけは例外で、この設定が記述されていない場合 データが永続化されません 。 ただのオンメモリデータベースになってしまい、コンテナを停止して起動すると登録したデータが失われてしまいます。

TsurugiのDockerイメージでは $TSURUGI_HOME/var/data/logトランザクションログの保存ディレクトリとして作られているので、次のように設定を記入してください。

[datastore]
log_location=var/data/log

コンテナを削除してもデータを残す方法

コンテナとしてデータベースを利用している場合、データはコンテナ外に保存してコンテナを作り直しても継続して利用できるようにすることが多いと思います。

先にも書いたように $TSURUGI_HOME/var/data/logトランザクションログの保存ディレクトリであるため、このディレクトリをバインドすればいいです。次にボリューム (ここではボリュームの名前を tsurugivol としています) にマウントする例を示します。

docker container run -d -p 12345:12345 -v tsurugivol:/usr/lib/tsurugi/var/data --name tsurugi ghcr.io/project-tsurugi/tsurugidb:latest

以上、自分が引っ掛かった経験を元に注意点をまとめました。コンテナ技術を使えばお手軽に試すことができるので、みんなどんどんTsurugiで遊んでみてください。

DiigoからRaindrop.ioへPandasを使って移行した話

ブックマークサービスの移行について

自分は基本的にWebサイトのブックマークはWebサービスを利用するようにしているのですが、長らくその目的にDiigoを利用していました。

www.diigo.com

日本ではオンラインブックマークサービスとしては圧倒的にはてなブックマークが有名ですが、はてなに無い機能として「ブックマークしたWebページにハイライトやメモの追加を行うことができる」というものがあり、これが便利だったのではてなから乗り換えました。

課金も行って便利に使っていたDiigoですが、近年は開発が停滞しているように見受けられ(ブログも5年ほど更新されてしません)、将来性が不安になってきていました。 そんなところに次のRaindrop.ioというオンラインブックマークサービスがあることを知りました。

raindrop.io Raindrop.ioは次のような特徴を持つオンラインブックマークサービスです。

  • Diigoと同様、ブックマークしたWebサイトに対して文章にハイライトを引いたり、メモを記入することができる
  • ブックマークはコレクション(フォルダと同義)とタグという2種類の整理方法がある
    • Diigoはタグのみで、エントリ数が多くなると散らかってしまう傾向があったが、こちらはコレクションで大まかに整理した上でタグ付けして詳細を探すような使い方ができる *1
  • ブックマークの内容を分析して自動的にカテゴリ分けしてくれる機能もある
  • Webブラウザ向け拡張機能やモバイルアプリケーションももちろん提供している
    • Diigoのモバイルアプリはブラウザが前面に出てくるインターフェースだったのが使い辛かった

総じてDiigoの機能は全て保持しており、開発も盛んに行われている印象だったので乗り換えてみることにしました。

どのようにしてデータを移行するか

乗り換えるためにはデータの移行が必要です。可能ならば次の情報を持っていきたいと考えていました。

  • URL
  • ブックマークタイトル
  • エントリに対して自分が記入した説明
  • ブックマークに付けたタグ
  • ブックマークした日時
    • Webサイトの情報は陳腐化が激しいのでいつブックマークしたかの情報は重要

さすがにWebページのハイライトやノートの情報までは持っていくことは考えませんでした。

Diigo側では次のような形式でデータのエクスポートが可能でした (Diigoのエクスポートページ) 。

エクスポート形式 出力する内容
IEブックマーク URL、タイトル
Firefoxブックマーク URL、タイトル、タグ
RSS URL、タイトル、説明、タグ、Webページのハイライトやメモの情報
CSV URL、タイトル、説明、タグ、Webページのハイライトやメモの情報
Chromeブックマーク形式 URL、タイトル (今は亡きdel.icio.us互換らしい)

一番情報量が多いのはRSSCSVのようです。

一方Raindrop.io側では次のような形式でのインポートが可能です (Raindropのインポート形式についての説明) 。

というわけで多くの情報を持っていくにはCSVで移行するのが一番良さそうだと分かりました。

Pandasを使ってCSVのフォーマットを合わせる

DiigoからエクスポートしたCSVは次のようなカラム構成でした。

カラム名 内容
title ブックマークタイトル
url ブックマークのURL
tags ブックマークに付けられたタグ
description ブックマークに付けた説明文
comments Webページに添付したノート
annotations WebページのハイライトをHTMLとして出力している
createt_at ブックマーク作成日時をUTCで出力 (%Y-%m-%d %H:%M:%S フォーマット)

一方Raindrop.io側はインポートするCSVのカラム構成を次のように定めています。 カラム順は問わず、必須なのは url カラムのみです。

カラム名 内容
url ブックマークのURL
folder ブックマークが保存されているフォルダー
url ブックマークのURL
note ブックマークに付けられた説明文
tags ブックマークに付けられたタグ
created ブックマーク作成日時をUNIXタイムスタンプもしくはISO8601形式で

Diigoから出力されたCSVから次のような変換を行う必要がありますね。

  • comments 及び annotations カラムを削除
  • カラム名 descriptionnote にリネーム
  • カラム created_at の日時文字列をISO8601形式に変換する
  • カラム名 created_atcreated にリネーム

Pandasを使ってこの変換処理を行いました。Jupyter Notebookを使って実行しています。Jupyterは動かした結果をその場で見ながら試行錯誤できて便利ですね。

まずCSVをPandasを使って読み込みます。UTF-8CSVなのでそのまま読み込めます。

df = pd.read_csv("<path to input CSV>")

日時のフォーマットを変換します。まずは created_at カラムを datetime 型に変換します。

df["converted_dt"] = pd.to_datetime(df["created_at"], errors="coerce", utc=True)

Pandasは日付っぽい文字列に対してフォーマットを指定せずともある程度よろしく解釈してくれます。時刻はUTCなので utc=True を指定しています。

今度はISO8601形式の文字列に変換します。Raindropが要求するカラム created に出力します。

df["created"] = df["converted_dt"].dt.strftime("%Y-%m-%dT%H:%M:%SZ")

次にカラム名のリネームをします。

df = df.rename(columns={"description": "note"})

最後に必要なカラムだけを選択して、出力用DataFrameを作成し、CSVファイルとして出力します。

output_df = df[["url", "title", "note", "tags", "created"]]
output_df.to_csv("<path to output CSV>", index=False, quoting=csv.QUOTE_NONNUMERIC)

文字列には改行も入っているのでCSVのクォートは csv.QUOTE_NONNUMERIC を指定しました。

このファイルをRaindrop.ioに読み込ませると無事インポートに成功しました!

Diigoではこうだったのが...

Raindrop.io側にこのようにちゃんと引き継がれました。タグや登録日時の情報も引き継がれています。

*1:Diigoアウトライナー機能を提供しており、アウトライナーで整理してもらうという方針でしたが、このやり方は手軽ではなかったですね

復活していたQtJambi

QtJambiの開発は引き継がれていた

OracleからJavaのクライアントテクノロジーに関するロードマップが発表され、それについて色々思うことを垂れ流した次のブログを投稿したのですが、あれからもう5年になるのですね。

aoe-tk.hatenablog.com aoe-tk.hatenablog.com

このエントリにて、次のようなことを書いていました。

汎用的なクロスプラットフォーム GUI で一番成功しているのはやはり Qt でしょうか。モバイルへの進出にも成功していますし。開発言語は C++ ですが、他のプログラミング言語へのバインディング も多いです。ですが、Java バインディングである Jambi が死んでしまったんですよね...。

QtのJavaバインディングであるJambiについて、このエントリを書いたときは死んだような状態になっていたのですね。ところが最近QtJambiという名称として (以前は "Qt Jambi" とQtとJambiの間にスペースが入っていました) 復活していたことに気付きました。

github.com

これは元々NokiaがQt JambiのOSS化にあたって作った 旧Qt Jambiのリポジトリ とは別のリポジトリです。 どうもOmix Visualizationという会社が最新のQtに対応したフォークを作成して開発を引き継いだようです。旧リポジトリでは対応していなかったQt5系はもちろん、6系も最新の6.5にまで対応したリリースがあります。

コミット履歴を調べてみると *1 一番最初のコミットは2015年9月で、旧リポジトリの最後のコミットが2015年9月であるのを見るに、旧プロジェクトの開発終了後すぐに新しいリポジトリを作ったようです。旧プロジェクトにてコミッタ間で話し合いがあってフォークが決まったとかですかねえ。

リポジトリ作成は2015年9月ですがリリースタグの付いた最初のリリースは2020年8月で、ここからプロダクトとしてリリースしていくステップに入ったようです。 Qt本家Wikiの言語バインディングのページ に追加されたのもその時からでした。旧プロジェクトの終了後、5年のブランクを経て復活した形ですね。

触ってみる

見つけたからには触ってみることにしましょう。なお自分はQtによる開発の経験は過去に一切ありません🙂

QtJambiはJPMSに従ったモジュール化がされています。次のAPIドキュメントのトップメージにモジュール一覧が列挙されています。Qtが提供する膨大なライブラリを概ねカバーしているようです。3DやWebViewなどはもちろん、BluetoothNFC、センサーなど実に膨大な範囲をカバーしていることが分かりますね。

https://doc.qtjambi.io/latest/

基本的なものは全て qtjambi モジュールに入っています。モジュール別にMaven Centralに登録されているので、Mavenを使っている場合は pom.xml に次のように依存設定を追加します。実行にあたってはプラットフォームに応じたネイティブライブラリも必要なので qtjambi-native-<os>-<architecture> も追加する必要があります。

    <dependencies>
        <dependency>
            <groupId>io.qtjambi</groupId>
            <artifactId>qtjambi</artifactId>
            <version>6.5.0</version>
        </dependency>
        <dependency>
            <groupId>io.qtjambi</groupId>
            <artifactId>qtjambi-native-windows-x64</artifactId>
            <version>6.5.0</version>
        </dependency>
    </dependencies>

これでプログラムを作成できます。ごく簡単なウィンドウとメニューを持つアプリケーションを作ってみます。

package aoetk.qtsamle;

import io.qt.gui.QAction;
import io.qt.widgets.QApplication;
import io.qt.widgets.QLabel;
import io.qt.widgets.QMainWindow;
import io.qt.widgets.QMenu;

import static io.qt.core.QObject.tr;
import static java.util.Objects.requireNonNull;

public class WindowSample {

    public static void main(String[] args) {
        QApplication.initialize(args);
        QMainWindow mainWindow = new QMainWindow();
        QMenu menu = requireNonNull(mainWindow.menuBar()).addMenu(tr("&File"));
        QAction quitAction = requireNonNull(menu).addAction(tr("&Quit"));
        requireNonNull(quitAction).triggered.connect(QApplication::quit);
        mainWindow.setCentralWidget(new QLabel(tr("Hello Qt!"), mainWindow));
        mainWindow.show();
        QApplication.exec();
        QApplication.shutdown();
    }

}

実行するにあたっては予めQtがインストールされている必要があります。 インストーラのダウンロードページ からQtのインストールを行います。予めユーザー登録を行う必要がある点に注意してください。非常に巨大なライブラリなのでインストールには結構時間が掛かります。

作ったJavaアプリケーションを実行する際、JVMシステムプロパティ java.library.path にQtのライブラリパスを指定します。OS別に次のようなパスになります。

  • Windows
    • <path to>\Qt\<version>\msvc2019_64\bin
  • Linux
    • <path to>/Qt/<version>/gcc_64/lib
  • macOS
    • <path to>/Qt/<version>/macos/lib
    • macOSの場合はさらに起動引数として -XstartOnFirstThread を指定する必要あり

Windows環境のIntelliJでの実行設定の例を示しておきます。

プログラムを実行すると次のようにメニューとラベルを持つウィンドウが起動しました。

使い方を学ぶには?

汎用クロスプラットフォームGUIツールキットとしては最強と思われるQtへのバインディングが提供されたことで、再びJavaにもスタンドアロンGUIアプリケーションを作るための強力な手段が加わりました。

現時点での問題点は余りにも情報が少ないという点ですね。GoogleTwitter、Stack Overflowなどを検索してもこの新しいQtJambiへの言及がほとんど見られていません。本当に一握りの人間しかこれの存在に気付いていないような気がしますw

基本的にドキュメントは GitHubプロジェクト上のWiki からたどれる範囲のみで、Qtについての基本概念は知っていることを前提とした記述になっています。APIドキュメントについても本家C++版のページへのリンクを示すだけのクラスが多いです。

自分はC++の開発経験がゼロに等しいので、C++版のドキュメントを読むのはちょっと辛いんですよねえ。幸いQtは有名どころのプログラミング言語に対してはほぼバインディングを提供しており、例えばPythonバインディングであるQt for Python (PySide) については公式からも豊富なドキュメントが提供されているので、これでQtを勉強しますかねえ。

www.qt.io

というわけでいつの間にか復活していたQtJambiについての紹介でした。

*1:コミット数はほぼリリース単位になっていて少なく、クローズドのリポジトリで開発してリリースのタイミングで一気にパブリックリポジトリにpushしているスタイルのようです。

PySparkでコントロールブレイク処理

お題は次のエントリです。

gonsuke777.hatenablog.com

上記エントリではいわゆるコントロールブレイク処理(ソート済みのレコードを読み込み、キー項目ごとにグループ分けして行う処理のことでキーブレイク処理と呼ぶことも)を 1 本の SQL でスマートに行っています。これと同じことを PySpark でやってみるという話です。

次のような CSV ファイルを用意しておきます。

sales_date,jan_code,sales_cnt
2014/10/06,AAA,100
2014/10/07,AAA,200
2014/10/08,BBB,100
2014/10/09,BBB,150
2014/10/10,BBB,189
2014/10/11,CCC,120
2014/10/12,CCC,111
2014/10/13,AAA,210
2014/10/14,AAA,545
2014/10/15,AAA,90
2014/10/16,CCC,90

これを Spark DataFrame に読み込みます。

from pyspark.sql.types import StructType, StructField, StringType, IntegerType, DateType

schema = StructType([
  StructField('sales_date', DateType()),
  StructField('jan_code', StringType()),
  StructField('sales_cnt', IntegerType())
])

df = spark.read.csv('<path-to-csv>', schema=schema, header=True, dateFormat='yyyy/MM/dd')
df.show()
# +----------+--------+---------+
# |sales_date|jan_code|sales_cnt|
# +----------+--------+---------+
# |2014-10-06|     AAA|      100|
# |2014-10-07|     AAA|      200|
# |2014-10-08|     BBB|      100|
# |2014-10-09|     BBB|      150|
# |2014-10-10|     BBB|      189|
# |2014-10-11|     CCC|      120|
# |2014-10-12|     CCC|      111|
# |2014-10-13|     AAA|      210|
# |2014-10-14|     AAA|      545|
# |2014-10-15|     AAA|       90|
# |2014-10-16|     CCC|       90|
# +----------+--------+---------+

元の SQL では ROW_NUMBER ウィンドウ関数を使って単純ソートした場合の連続値と jan_code で区切りつつソートした場合の連続値を割り振っていますが、PySpark (Spark SQL) でも pyspark.sql.functions.row_number という同じ関数があります。

from pyspark.sql import Window
from pyspark.sql.functions import row_number

# SQLでの ROW_NUMBER() OVER(ORDER BY SALES_DATE) に相当
df = df.withColumn('simple_sq', row_number().over(Window.orderBy('sales_date')))

# SQLでの ROW_NUMBER() OVER(PARTITION BY JAN_CODE ORDER BY SALES_DATE) に相当
df = df.withColumn('part_jan_sq', row_number().over(Window.partitionBy('jan_code').orderBy('sales_date')))

パーティションとソート順は pyspark.sql.Window クラスのファクトリメソッドを使って生成する pyspark.sql.WindowSpec オブジェクトとして渡します。

あとは distance を計算すれば集約カラムが作られますね。

from pyspark.sql.functions import col

df = df.withColumn('distance', col('simple_sq') - col('part_jan_sq'))
df.orderBy('sales_date').show()

# +----------+--------+---------+---------+-----------+--------+
# |sales_date|jan_code|sales_cnt|simple_sq|part_jan_sq|distance|
# +----------+--------+---------+---------+-----------+--------+
# |2014-10-06|     AAA|      100|        1|          1|       0|
# |2014-10-07|     AAA|      200|        2|          2|       0|
# |2014-10-08|     BBB|      100|        3|          1|       2|
# |2014-10-09|     BBB|      150|        4|          2|       2|
# |2014-10-10|     BBB|      189|        5|          3|       2|
# |2014-10-11|     CCC|      120|        6|          1|       5|
# |2014-10-12|     CCC|      111|        7|          2|       5|
# |2014-10-13|     AAA|      210|        8|          3|       5|
# |2014-10-14|     AAA|      545|        9|          4|       5|
# |2014-10-15|     AAA|       90|       10|          5|       5|
# |2014-10-16|     CCC|       90|       11|          3|       8|
# +----------+--------+---------+---------+-----------+--------+

集約のためのキーができたので、集約を行っておしまい。

grouped_df = df.groupBy(['jan_code', 'distance']) \
  .agg(min('sales_date').alias('sales_date_first'), \
       max('sales_date').alias('sales_date_last'), \
       sum('sales_cnt').alias('cnt_sum'))
grouped_df.orderBy('sales_date_first').show()

# +--------+--------+----------------+---------------+-------+
# |jan_code|distance|sales_date_first|sales_date_last|cnt_sum|
# +--------+--------+----------------+---------------+-------+
# |     AAA|       0|      2014-10-06|     2014-10-07|    300|
# |     BBB|       2|      2014-10-08|     2014-10-10|    439|
# |     CCC|       5|      2014-10-11|     2014-10-12|    231|
# |     AAA|       5|      2014-10-13|     2014-10-15|    845|
# |     CCC|       8|      2014-10-16|     2014-10-16|     90|
# +--------+--------+----------------+---------------+-------+

PySparkでの時刻変換色々

最近はデータエンジニアリングのお仕事がメインで、もっぱら PySpark を触っています。 自分向けの備忘録的も兼ねてちょいちょい blog に tips を書いていきたいと思います。

今回は時刻変換に関するもの。

タイムゾーン付き日付文字列をパースしてtimestamp型に変換

基本は to_timestamp 関数を使います。

from pyspark.sql.functions import col, to_timestamp

df = spark.createDataFrame([('2021-05-16T23:03:49.220Z',)], ['str_datetime'])
df = df.withColumn('datetime', to_timestamp(col('str_datetime'), "yyyy-MM-dd'T'HH:mm:ss.SSSX"))

日時フォーマットのパターン文字列は Java方式 です。Spark は Scala で作られているので、Python でコードを書いていてもこういうところで Java が顔を出してきます。

似たものとしてUNIX時間に変換する unit_timestamp という関数がありますが、こちらはミリ秒以下が切り捨てられることに注意してください。

from pyspark.sql.functions import unix_timestamp
from pyspark.sql.types import TimestampType

df = df.withColumn('time', unix_timestamp(col('str_datetime'), "yyyy-MM-dd'T'HH:mm:ss.SSSX").cast(TimestampType()))

日付文字列をdate型に変換

to_date 関数を使います。

from pyspark.sql.functions import to_date

df = df.withColumn('date_col', to_date(col('str_datetime'), "yyyy-MM-dd'T'HH:mm:ss.SSSX"))

ゾーン情報を使ってローカル時刻にタイムスタンプをずらす

from_utc_timestamp 関数を使います。 現地時刻に変換した文字列を取得したい時やタイムゾーン情報のないデータベースに登録するときとかに使うかも。

from pyspark.sql.functions import from_utc_timestamp

df = df.withColumn('local_time', from_utc_timestamp(col('time'), 'Asia/Tokyo'))

date型、timestamp型のカラムを手動作成する

Pythondatetime.date 型で値を投入すると Spark SQLDateType になります。
同様に datetime.datetime 型で値を投入すると Spark SQLTimestampType になります。

import datetime

a_date = datetime.date(2022, 1, 1)
a_datetime = datetime.datetime(2022, 1, 1, hour=1, minute=10, second=10, microsecond=100000)
df = spark.createDataFrame([(a_date, a_datetime)], ('date_col', 'time_col'))

SoftBank回線でSIMフリースマートフォンに乗り換えたら大変だった話

前置き

これまでスマートフォンとして 3 年前に購入した Pixel 3 XL を使い続けていましたが、さすがにバッテリーがへたってきており、もうすぐサポート期間の終了を迎えることもあって別の機種に乗り換えることになりました。

もうすぐ登場する Pixel 6 を当初は考えていたのですが、非常に高価になるという話で、また端末サイズも今使っている Pixel 3 XL よりさらに大きくなるという話なので、別のものも探してみることにしました。

色々探してみたところ、ASUS からリリースされたばかりの Zenfone8 が良さそうということでこれを購入しました。

www.asus.com

購入判断の根拠は次のようなところです。

  • Qualcomm の最新でハイエンドチップである Snapdragon 888 を搭載していながら税込みで8万円を切るという安さ
    • 今使っている Pixel 3 が Snapdragon800番台だったので、800系以外はあり得ませんでした
    • iPhone 並のパフォーマンスが欲しかったら700番台、800番台は必須だと思います(特にゲームで大きな差が出る)
  • 端末サイズがコンパクトでありながらパンチホールカメラ+ナローベゼルで 5.9 インチの画面サイズを確保している
  • SIM フリーなのでキャリアの余計なアプリとかが入っていない
  • SIM フリー端末にしては珍しく FeliCa を搭載している
    • Suica に全面的に依存していたのでこれは必須

SoftBank回線でSIMフリースマートフォンを使う時の注意点

前置きが長くなりました。ということで家電量販店で上記端末を購入、そのまま家に持ち帰って家で Pixel に指していた SIM を指してセットアップ、その日は問題なく使え、新端末の快適なパフォーマンスを楽しんでいました。

が、次の日に異変に気付きます。外出時に使ってみるとネットワークに一切つながらないのです。幸い電話は使うことができたのですが、外出中は久しぶりに Wi-Fi 乞食になってしまいました。 *1

もしかしてやっぱり契約変更とかが必要なのかな?と思って家から近い SoftBank ショップで見てもらったのですが、「SIM はそのまま指して使えるはず」「SIM カードや設定には特に問題がない」「端末に問題がありそうだから購入したお店で端末を見てもらって」との回答で解決に至りませんでした…。

仕方がないので翌日端末を購入した家電量販店で見てもらうことにしたのですが、そこで実は SoftBank の SIM カードは大きく分けて次の 3 種類があることを教えてもらいました。 *2

  1. iPhone 用の SIM
  2. SoftBank から販売している Android 端末用の SIM
  3. SIM フリースマートフォン向けの SIM

自分が今まで使っていたのは 2 番の SIM カードだったので、3 番への交換が必要だったのでした。家電量販店にはキャリアショップもあったので、そちらに案内してもらい、様々な契約変更を行って、無事にネットワークにつながる SIM が手に入りました(応対した人は別店舗での不手際について申し訳ないとかなり恐縮してました)。

こんな感じでちゃんと使えるようになるまでに色々バタバタしてしまいました。この辺りのガイドラインは Web サイト等できちんと案内して欲しいところです。まあできればキャリアから販売している端末を使ってもらいたいからなんでしょうけど。同じようなことをしようとする人がもしいたら参考になるかと思い、今回の顛末をまとめることにしました。

お陰様で新端末ではこれまで使えなかった5G回線やVoLTEも使えるようになりました。

Zenfone 8について

最後におまけで Zenfone 8 の感想を。

  • さすが Snapdragon 888 だけあってパフォーマンスは速い、アズレンも爆速で動く
    • 最近のアズレン弾幕の描画が派手になったり、戦闘開始時のスキル発動が多く重なるようになったのでかなり負荷が大きく、Pixel 3だと力不足になる場面が時々ありました
    • ゲームやベンチマークツール等高負荷を要求するアプリが起動すると自動的にクロック数を上げる仕組みがあるようです
      • そのためか高負荷処理を行っているとかなり発熱します
      • ベンチマークツールでブースト機能を起動するのはちょっとずるくないかという気もしますがw
  • 端末スピーカーが非常に良い
    • Pixelもステレオスピーカーでしたが、音の広がりが全然違いました
  • スクリーンでの指紋認証は便利
  • 端末サイズがコンパクトなので片手操作が容易、それでいてスクリーンサイズが犠牲になっていない
    • 画面についてはリフレッシュレートが 120MHz で滑らかに動くことも特長ですが、自分の目ではあんまり分かりませんでしたw
  • Qi 充電が無いのはちょっと残念
  • デュアル SIM なのでもし海外に行く機会があったらそのまま現地の SIM を追加で刺して使えるので便利そう

*1:この時に気付いたのですが、オリンピックのお陰でか東京の地下鉄は車内 Wi-Fi が通るようになっていたのですね

*2:こんな面倒なことしているのSoftBankくらいらしいですが…

IntelliJ IDEA 2020.2でJavaFXのランタイムが同梱されなくなりました

IntelliJ IDEA の最新版 2020.2 がリリースされましたね。日本語でも新機能について案内する記事がアップされています。

www.jetbrains.com

この記事の最後のように次のような個人的に気になる記述がありました。

  • パフォーマンスと表示の問題を回避するため、新たに JCEF との統合機能を提供して IntelliJ Platform のプラグインを実行できるようにしました。

まず多くの人は「JCEFって何?」ってなると思います。これはアプリケーションに Chromium を組み込めるようにするためのフレームワーク CEF (Chromium Embedded Framework) の Java ラッパーのことです。

IntelliJIDE 内部で使う Web ブラウザコンポーネント (Markdown エディタの HTML プレビューなどで使っています) として今後は JCEF を使うようになったということです。これまでは IDE 内部で使う Web ブラウザコンポーネントとしては JavaFX の WebView を使っていましたが、2020.2 でこれをやめることになります。本件について JetBrains の Blog 記事で説明がありました。

blog.jetbrains.com

ご存じの通り IntelliJ は Swing をベースに作られています。従って JavaFX の WebView は JFXPanel を通して使うことになりますが、そのために性能やレンダリングの問題を解決できなかったとのことです。

実際、JavaFX と Swing はレンダリングスレッドもタイミングも異なり、Swing アプリの上で JavaFX ノードを描画するときは JavaFXレンダリングエンジンである Prism エンジンではなく Java2D を使って描画するため、JavaFX のパフォーマンスを 100% 発揮することができないのも確かです。この不整合を解決しようとする計画も過去にはありましたが、 Oracle の JavaFX へのやる気が無くなった ので...。

というわけで上記の記事では IntelliJプラグイン開発者に対して今後は JavaFX への依存をやめ、Web ブラウザコンポーネントを使いたいときは JCEF を使うことを促しています。

ですが、JCEF は現在絶賛開発中のステータスで、ユーザーとして利用できるような段階にありません。ビルド方法のガイドはありますが利用方法のガイドはなく、API ドキュメントもありません。ラップ対象の CEF については既に利用可能なステータスにあり (C++ のライブラリです) 、ある程度ドキュメントもあるので、こちらを参照して利用方法を推測することになります。

さすがにこの状態で使うのは辛いので、JetBrains 側でラップした APIプラグイン開発者向けに用意したようです。以下のドキュメントで解説されています。

jetbrains.org

というわけで IntelliJ IDEA 2020.2 からは JavaFX のランタイムは同梱されなくなります。この点について個人的に1点気になることがありました。

実は IntelliJJava IDE の中で唯一 JavaFX Scene Builder を内部に組み込んでいた IDE でした。JavaFX のランタイムが同梱されなくなる 2020.2 ではどうなってしまうのでしょうか?

というわけで早速 FXML ファイルを開いてみました。すると...

f:id:aoe-tk:20200729200101p:plain

「Scene Builder Kit をダウンロードしてくれ」というリンクが表示されました。このリンクをクリックすると次に JavaFX Runtime のダウンロードが求められ、それも行うと次のように Scene Builder が表示されました。

f:id:aoe-tk:20200729200142p:plain

というわけで別途ランタイムをダウンロードする必要があるものの、Scene Builder が使えなくなったわけではないのでご安心ください。

以上、誰も気付かないであろう IDEA 2020.2 の変更点についてのお話でした。