カテゴリー: テクノロジー

今更ながらLaravelのバージョンを5.1から5.5にアップグレード

最初は5.1から5.5に一気にあげようかと思いましたが、順次あげていったほうが最終的には早く終わりそうだったので、Laravelのアップグレード手順と他の方の記事を参考にしながらアップグレードを行いました。
せっかくなのと覚書を兼用してつまづいた部分をいくつか紹介します。

5.1 > 5.2

composer updateでエラー

Illuminate\Foundation\ComposerScripts::postUpdate
php artisan optimize
PHP Fatal error: Uncaught ReflectionException: Class log does not exist in vendor/laravel/framework/src/Illuminate/Container/Container.php:734
Stack trace:

原因は.envに定義している配列の書き方がまずかった模様

ALLOW_IPS=[“192.168.111.111”, “192.168.111.123”]

こちらスペースを削除したら問題なく動作しました。

5.2 > 5.3

bladeを拡張していた部分でエラー

(‘aaa’, $bbb->ccc)

これまでblade拡張のdirectiveにて上記のようにパラメータが来ていたが、5.3になってからは下記のように外側の括弧がなくなりマッチせずエラーに

‘aaa’, $bbb->ccc

こちらは括弧がない文字列でマッチ条件を変更

5.3 > 5.4

・使用していたSlackのライブラリが使えなくなった > 別のライブラリに変更
・関数名がリネームされていた > forceSchemaをforceSchemeに書き換え

5.4 > 5.5

・Filesystemのfilesメソッドの挙動がSplFileInfoを返すようになったので、実行前にis_dirで確認を追加しました。
・Requestのhas,onlyメソッドの挙動が少し変わっていたので適宜書き換え

動作確認やアップグレード中に不具合修正が入ったり等で少し時間がかかりましたが、基本的にはアップグレード手順書通り行えばそこまで問題はなさそうです。


Solrで入れ子構造の文書をインデックスする

入れ子構造の文書

入れ子になった文書をそのままインデックスできると便利なことがあります。たとえば

  • 親文書:ブログ本文、子文書:コメント
  • 親文書:製品の基本情報、子文書:サイズ違い、色違いなどのバリエーション
  • 親文書:音楽プレイリスト、子文書:曲

などです。

Solrで入れ子構造を扱う

Solrは入れ子になった文書を扱うことができますが、そのためにはいくつかの条件と制限があります。

  • 親-子の2階層まで
  • indexされるがstoreされない root フィールドを持つ。同一の文書に含まれるすべての親要素、子要素は自動的に root フィールドに同じ値を与えられる
  • 親階層の文書であることを示すフィールドを持つ。検索時の条件として使う。
  • いわゆるスキーマレスの設定が必要。構造の異なる(場合が多い)親と子を同じコア(コレクション)内で扱う必要があるため

例: プレイリスト

以下のようなプレイリスト情報を Solr に与えます。
(_default の configset で core を作っていればスキーマレスでインデックス作成できます)

{
    "id":"list_1",
    "contentType":"playlist",
    "title":"list1",
    "songs":[
	{
	    "id":"song_1",
	    "contentType":"song",
	    "title":"title1",
	    "artist":"artist1",
	    "trackNum":1
	},
	{
	    "id":"song_2",
	    "contentType":"song",
	    "title":"title2",
	    "artist":"artist2",
	    "trackNum":2
	}
    ]
},
{
    "id":"list_2",
    "contentType":"playlist",
    "title":"list2",
    "songs":[
	{
	    "id":"song_3",
	    "contentType":"song",
	    "title":"title3",
	    "artist":"artist3",
	    "trackNum":1
	},
	{
	    "id":"song_1",
	    "contentType":"song",
	    "title":"title1",
	    "artist":"artist1",
	    "trackNum":2
	}
    ]
}

こういう入れ子構造を検索するのに使える Block Join Query Parser が用意されています。

親文書すべて

q={!parent which="contentType:playlist"}

"response":{"numFound":2,"start":0,"docs":[
      {
        "id":"list_1",
        "contentType":["playlist"],
        "title":["list1"],
        "_version_":1630800916686831616},
      {
        "id":"list_2",
        "contentType":["playlist"],
        "title":["list2"],
        "_version_":1630800916689977344}]
  }

子文書の情報もまとめて取得

ChildDocTransformerを利用します。

q={!parent which="contentType:playlist"}
fl=id, title, [child parentFilter="contentType:playlist" childFilter="contentType:song" fl=id,trackNum,title,artist]

"response":{"numFound":2,"start":0,"docs":[
      {
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]},
        {
          "id":"song_2",
          "title":["title2"],
          "artist":["artist2"],
          "trackNum":[2]}]},
      {
        "id":"list_2",
        "title":["list2"],
        "_childDocuments_":[
        {
          "id":"song_3",
          "title":["title3"],
          "artist":["artist3"],
          "trackNum":[1]},
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[2]}]}]
  }

「”title2″を含むプレイリスト」

q={!parent which="contentType:playlist"} title:title2
fl=id, title, [child parentFilter="contentType:playlist" childFilter="contentType:song" fl=id,trackNum,title,artist]

"response":{"numFound":1,"start":0,"docs":[
      {
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]},
        {
          "id":"song_2",
          "title":["title2"],
          "artist":["artist2"],
          "trackNum":[2]}
}

部分的に更新するとどうなるか

更新するときは親子関係にある文書をすべてひとまとめにすること、という制限を破るとどうなるか試してみました。

{
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]},
        {
          "id":"song_2",
          "title":["title2"],
          "artist":["artist2"],
          "trackNum":[2]}]
}

というプレイリストの一部分だけを更新してみます。

{
    "id":"song_2",
    "contentType":"song",
    "title":"title22",
    "artist":"artist2",
    "trackNum":2
}

上のようなデータを与えてupdateしても特にエラーにはなりません。

q=title:title2

{
        "id":"song_2",
        "contentType":["song"],
        "title":["title2"],
        "artist":["artist22"],
        "trackNum":[2],
        "_version_":1630801429121728512}]
}

title:title2 という条件で検索してみると id:song_2 単体として見れば更新されていることが分かりますが、実は親子関係は破壊されてしまってます。

q={!parent which="contentType:playlist"}
fl=id, title, [child parentFilter="contentType:playlist" childFilter="contentType:song" fl=id,trackNum,title,artist]

"response":{"numFound":2,"start":0,"docs":[
      {
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]}]},
      {
        "id":"list_2",
        "title":["list2"],
        "_childDocuments_":[
        {
          "id":"song_3",
          "title":["title3"],
          "artist":["artist3"],
          "trackNum":[1]},
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[2]}]}]
}

検索実行時にJOINする方法との比較

この記事でご紹介したのは入れ子構造のままインデックスする方法です。それとは別に、親文書と子文書を別々のコア(コレクション)に分けて検索時にJOINする方法もあり、パフォーマンスと扱いやすさとのトレードオフが存在します。

  • 入れ子のまま扱う: 親子関係の情報自体をインデックスするのでパフォーマンス面で有利だがその分制限強め
  • 検索時JOIN: パフォーマンス面では不利だが柔軟

失敗作も成功のもと

こんにちは。開発担当のマットです。

以前に記事を書いた事がありますが、人間をぶっ飛ばす「人間大砲」ゲームを開発しました。

ゲームの内容はとても簡単です。
ボタンを押すと人間が大砲から飛ばされて、タイミング良く押すとより遠くへ飛んでいくという内容です。

人間をぶっ飛ばすことが面白くて楽しいアプリですが、「アンロックできるコンテンツ」や「レベル」というものがないので、公開した時は「すぐ飽きてしまうんじゃないか」とちょっと心配していました。

バージョン2を作ろう!

「人間大砲」をもっと面白くしようと思って、「人間大砲2!」的なプロジェクトを作って開発に取り組みました。
バージョン1と同じように作るけども、以下の2点を加えようと考えました。

  1. 人間がまっすぐに飛ぶだけではなく、もっとアチラコチラに飛び跳ねるようにする
  2. 飛ばす力をレベルアップできるようにする

とても簡単な変更だと思っていましたが、意外な問題が発生しました。

スケーリングの問題

IT用語では「スケーリング」は「規模を増減すること」を指します。
IT業界では、とても大事な概念です。
アプリケーションやシステムが大きくなるに連れ、どの問題に当たるかを事前に考えて対応をしなければなりません。

しかし、シングルプレイヤーのゲームだと、特に問題はないだろうと思って簡単なプロトタイプを作成してみたら・・・ 意外な問題にぶち当たりました。

人間がさらにアチラコチラに飛び跳ねる様になると大きく左右に飛ぶことになります。
特に問題はないと思っていましたが、バージョン1では1本道で十分足りていたことに対してバージョン2では、横の道まで行くことが可能になりますので、格子状の街を作らなければならないことになります。

ある意味「1次元の世界」が「2次元の世界」に変わります。
一つの設計変更で、次元を増やしてしまいました。

なお、バージョン2ではレベルアップができるので、トップレベルのパワーで飛ぶと建物の上まで飛んで遠くまで見ることが可能になります。

バージョン1では、近くのエリアだけをロードすれば十分でしたが、もっと高いところまで飛ぶと遠くまで見えてしまいます。その分も端末のメモリーにロードしなければなりませんが、そうするとパフォーマンスがグッと落ちてしまい、逆にストレスがたまるゲームになってしまいます。

見ての通り、同時にロードをしなければならない内容の量が全然違います

ゲーム性の問題

次の問題は技術的な壁ではなくて、ゲーム性の問題でした。

レベルアップができるようになるとより遠くに飛べるようになりましたが、パワフルな勢いで建物や障害物を変わった角度で当ててしまうと上までなが〜く飛んでしまいます。

その結果、空に飛んでいる間は障害物に当たることなく、緊張感もなく、なにも起こらない全然面白くないゲームになってしまいました。

ただ単に飛んでいるだけ・・・

気軽に作ろうと思っていたプロジェクトが、色々と問題が出てしまったので一旦棚上げすることにしました。
またいつか、人間大砲の改善バージョンを作りたいと考えていますが、とりあえず上記で作ったバージョン2は失敗作と見做したいと思います。

失敗で習ったこと

この記事を読んでいただいておわかりかと思いますが、ちょっとした変更でも最終的には大きな影響を及ぼすことがあります。

やっぱり、設計が大事!

ゲームを設計する時、「より大きい」「よりパワフル」「より広い」ことは必ずしも良いとは言えません。
今後、ゲームを設計する時はそのゲームに合っている内容量、そのゲームに合っている規模をきちんと考えて作らなければなりません。

習ったことを活用する

ちょうど、同じ頃にゾンビゲームも作っていました。
走り回って、ゾンビを倒して、生存者を救うという単純な内容ですが、面白くてハマってしまうジャンルです。

まずはマップを作成する必要がありました。
当初はやっぱり、「大きく、広く、膨大!」という気持ちで作りだしました。

膨大なマップを作っていた頃のスクリーンショット

横切れる山もあり、川と橋もマップに入れて、作ってみました。

しかし、人間大砲バージョン2で学んだことで、以下の懸念点に気が付きました。

  1. マップが広いと、走る距離も長くなり面白くなくなる。
  2. コンテンツをまばらにしないとパフォーマンス問題に当たる。
  3. マップに登れる山などがあると、新しい次元(上下向き)も対応しなければならない。

なお、モバイルではマウスが使えないので、左手の親指で前後移動と左右向きの操作をし、右手の親指で武器の操作を行う場合は、上下向きを操作する余裕はありませんでした。

やっぱり、設計が大事!

上記の懸念点を踏まえてマップを作り直すことにしました。
マップ自体を狭くし、その分コンテンツに密度を入れることができました。
登ったり下ったりできる個所を完全になくして、平面で遊べるようにしました。

作り直したマップ(実際にプレイできるエリアは街周りだけです)

最終的にこれらの変更のお陰で緊張感のある面白いゾンビーゲームを作ることに成功しました。

まとめ

昔、ある有名な航空設計士がこんなことを言いました。

完璧という状態は、何も加えるものがなくなったときではなく、何も除く必要がなくなったときに達成されるものだ。

ゲーム作成やソフトウェア設計でも同じかもしれません。
開発者としてはもっともっとと色々コンテンツや機能を盛り上げたくなりますが、盛り上げすぎるとそのソフトが逆に目的を果たさなくなってしまうこともあります。

今後も、このことをより把握した上で色々な面白いものを作っていきたいと思います!


Solr の Kuromoji を extended で使うとインデックスサイズが予想以上に小さくなる問題について

前回得られた、Kuromoji の mode を extended にした場合にインデックスサイズが最も小さくなるという不思議な結果を調査しました。

mode=search と mode=extended の違いは未知語の扱いです。 なので、未知語を含むクエリがどのように扱われるのかを確認しました。

【mode=search の場合】

"rawquerystring":"title:西沙諸島 AND text:パラセル諸島"
"parsedquery":"+title:西沙諸島 +(text:パラセル text:諸島)"

【mode=extended の場合】

"rawquerystring":"title:西沙諸島 AND text:パラセル諸島"
"parsedquery":"+title:西沙諸島 +text:諸島"

未知語が1-gramに分割されてインデックスサイズが多少大きくなることを予想していたのに、実際は逆に未知語が落とされてしまっています。

そこで、mode=search と mode=extended とで「パラセル諸島」を形態素解析した結果を比べてみました。

mode=search で「パラセル諸島」を形態素解析した結果
mode=extendedで「パラセル諸島」を形態素解析した結果

mode=search では「パラセル」が名詞-一般となっており、mode=extended では「パラセル」が1文字ずつに分解されてそれぞれが記号-一般となっています。分解のされ方はどちらも期待通りです。形態素解析に問題が無さそうなので、怪しいのは形態素解析後のフィルタということになります。おそらくストップワードに引っ掛かっているのでしょう。

設定ファイルの lang/stoptags_ja.txt を確認すると、記号-一般がストップワードとして定義されていました。

#  symbol-misc: A general symbol not in one of the categories below.
# e.g. [○◎@$〒→+]
記号-一般

そこで、この行をコメントアウトした configset (それ以外は全く同じ)を作り、wikipedia-ja のインデックスを作成しました。

4.2G    example/cloud/node1/solr/wikipedia-ja-extended2_shard1_replica_n1
2.1G example/cloud/node1/solr/wikipedia-ja-extended_shard1_replica_n1
3.1G example/cloud/node1/solr/wikipedia-ja-normal_shard1_replica_n1
3.1G example/cloud/node1/solr/wikipedia-ja-search_shard1_replica_n1

予想通り、4.2GBとmode=searchよりもインデックスサイズが大きくなりました。形態素解析の結果は以下の通りです。

"rawquerystring":"title:西沙諸島 AND text:パラセル諸島"
"parsedquery":"+title:西沙諸島 +(text:パ text:ラ text:セ text:ル text:諸島)"

インデックスサイズが大きくなったのは、単に未知語が1-gramに分割されただけではなく、他の記号類もストップワードから外れてインデックスに入ってしまったからだと思います。 1-gram に分割したときの品詞を変更できるようになっていればいいのですが、ざっと調べた感じではそうはなっていないようです。 その弊害を考えると、mode=extended はちょっと使い勝手が悪いと言えるかもしれません。


スマートロックがすごく安く良くなってた。

今回は、最近発売されたスマートロックを家の玄関に取り付けたというお話です。

 

ご存知の方も多いとは思いますが、既存のドアの鍵などを後付けでスマホなどから開閉出来るようにするシステムのことをスマートロックといいます。たいていBluetoothでスマホなどと通信して施錠・解錠するものが多いです。

 

スマホに専用のアプリをインストールして管理者から許可してもらえば鍵の開閉が可能になり、もしスマホを紛失しても管理者が許可を取り消せば操作できなくなるとか、操作可能時間帯や曜日などを指定できたり、操作記録が残ったりと、なかなか便利そうです。

  

ただ、これまでは3万円以上するものばかりで、別に普通の鍵で使えているのにわざわざ導入するにはちょっと高価ですし、性能がイマイチだったりして導入に踏み切れていませんでした。

  

ところが今回、Makuake というクラウドファンディングサイトで「セサミMini」というスマートロックが1つ9,800円で支援募集していたので、1万円以内なら一度使ってみてもいいか、ということで申し込んでみました。

  

  

  

付けました。

  

  

高さを合わせるスペーサーをネジで止めて、両面テープでピタっと貼り付けるだけ。非常にカンタンです。

  

  

正直あまり期待してなかったのですが、これ、いいです。(・∀・)イイ!!

  

  

扉が金属なのでやっぱり外からの接続には若干時間がかかりますが、普通に解錠・施錠できます。AppleWatchでも解錠出来るので、スマホすら出さないで解錠出来るのはスゴくラクです。(ただスマホより若干接続に時間がかかるみたいですが、、、)

電源はCR123Aという乾電池ですが、500日持つということなので年に一回交換しておけば電池切れで閉め出されるということもなさそうです。

  

家族も最初は使うのイヤがってたんですが、最近は慣れたみたいで全員スマホで解錠しています。わざわざ鍵をカバンから出すより便利なことに気がついたようです。まあスマホは大体手に持ってますからね。

  

私などはすでに普通の鍵は持たずに外出することも多いです。そのくらい安定して動作してるってことですね。

  

  

これ正式発売はまだのようですが、数ヶ月以内に発売されるみたいですので、みなさんも設置してみてはどうでしょう。

鍵を持たずに外出出来るって意外に気持ちいいですよ。