ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Rubyのスレッドで並列化するのに向いている処理を調べてみる

こんにちは、夏に向けて腹筋強化中のWebエンジニア、本間です。

はじめに、この開発者ブログを開設してから、半年が経過しました🎉

気づけばエントリー数も20を超え、開発者ブログっぽくなってきなーと感じております。 この勢いを続けていけるよう、会社としても個人としてもこれからも頑張っていく所存です。

話は変わりまして、今回のブログでは「Rubyのスレッドで並列化するのに向いている処理」を簡単に調べたので、メモがわりに残しておこうと思います。

調査を実施した理由として、バックグラウンドジョブを動かすGemの1つに sidekiq という有名なGemがあります。 このsidekiqの特徴の1つに「マルチスレッドで動作する」というものがあります。 一方、MRI(CRuby)にはGVL(Global VM Lock)、またはGIL(Global Interpreter Lock)と呼ばれる排他制御の機構があり、「同時に実行できるスレッドは1スレッドのみ」という制限がかかっています。 このロックにより、sidekiqでは複数スレッド立ち上げても処理性能が向上しないのでは、と疑問に思ったことがきっかけです。

ただし、GVLはIO関連の処理が実行されている間は解放されて他スレッドの処理を実行できる、と聞いたこともあります。 どの処理だとマルチスレッドにしてもGVLの影響を受けずに並列化できるのか、調査してみました。

続きを読む

AWS Lambda から Google Cloud Pub/Sub へ投入してみる(Python)

おはようございます。
こんにちは。
こんばんは。

最近の日差しの強さで肌が焼けるなと感じる地黒のインフラ担当すずきです。

本当に暑くなってきましたね。でも冷房で寒くてつらい…。
皆様、温度差で体を壊さないようにしていただけたらと思います。

さて本題、ユニファのシステムは基本的にAWSを利用しているのですが、BigQueryとかも利用したいのでGCPを利用しようという流れが来てます。
S3に溜まってるログをGCS経由で送ればいいじゃんってのもあるのですが、どうせならCloud Pub/Subとか使ってみようって事でLambdaからCloud Pub/Subを触ってみました。

続きを読む

iOSのCoreBluetoothの実装をしてみる

iOSエンジニアのしだです。
最近、Bluetooth Low Energy(BLE)をつかう場面が多くなってきたので、iOSでBLEを利用する方法を勉強中であります。 特に目新しい話ではないのですが、iOSでCentral側とPeripheral側の実装を試してみたので共有したいと思います。

続きを読む

エンジニアから見た新保育指針

こんにちは。エンジニアの田渕です。 5月も中旬を過ぎたのに、東京はまだなんだか微妙に肌寒い日々。。。毎年こんなもんだったっけ?と思いながら、過ごしています。

さて、すっかり定着してきたユニファのエンジニアブログですが、本日はユニファらしい(?)記事を一つ書いてみようかと思います。

続きを読む

Cordova で React VR をスマホアプリ化する

こんにちは。田中剛です。

今回は先日リリースされたReact VR を Cordovaで スマホアプリ化 する話を書いてみます。

え? ユニファでVR ? (||゚Д゚)

と思った方もいらっしゃるかもしれません。

はい、VRは私の趣味100%で業務には1%も関係ありません。。

が、Cordova や ReactNative などのハイブリッドアプリのフレームワークは何かのプロジェクトで使いたいと密かに思っています。 ( ̄ー ̄)

今回の記事を書くにあたって以下の記事がとても参考になりました。m(__)m qiita.com

では、やり方を順に説明していきます。

インストール
  1. cordovaのインストール

     $ npm install -g cordova
    
  2. React VRのインストール

     $ npm install -g react-vr-cli
    
プロジェクトの作成
  1. cordovaプロジェクトの作成

    $ cordova create path/to/your/dir com.example.hello.reactvr HelloCordovaReactVr
    

    createのパラメータは corodva create ディレクトリ 識別子 アプリ名 です。

    識別子でハイフンやアンダースコアを使うとハマるので要注意です。(AppIDとしてiOSではアンダースコア、Androidではハイフンを使えないため)

  2. iOSやAndroidのプラットフォームを追加

     $ cordova platform add ios --save
     $ cordova platform add android --save
    
  3. React VRのプロジェクトを作成

    corodvaプロジェクトのルートディレクトリでReact VRのプロジェクトを作成します。

     $ react-vr init react_vr
    

    initのパラメータは react-vr init アプリ名です。

アプリのビルドとコードの修正
  1. React VRアプリのコード(js)の修正

    index.vr.js をエディタで編集します

    せっかくなので extract-streetviewというツールを使って弊社オフィス地点のストリートビューのパノラマ画像をダウンロードして使ってみます。

     <View>
       <Pano source={asset('unifa_office_sv_pano.jpg')}
         style={{
           transform: [
             {rotateY : -90}
           ] }}
       />
       <Text
         style={{
           backgroundColor: '#77ff79',
           fontSize: 0.5,
           layoutOrigin: [0.5, 0.5],
           paddingLeft: 0.2,
           paddingRight: 0.2,
           textAlign: 'center',
           textAlignVertical: 'center',
           transform: [{translate: [0, 0, -3]}],
         }}>
         Welcome to Unifa
       </Text>
     </View>
    

    ちなみに extract-streetview は次のような感じで使えます。

     $ extract-streetview 35.7004793,139.7768184 -f jpg -q 100 -z 2 -o unifa_office_sv_pano.jpg
    
  2. React VRアプリをビルド

     $ cd react_vr
     $ npm run bundle
    
  3. React VRアプリのコード(html)の修正

    vr/index.html をエディタで編集します

    1. index.htmlにcordova.jsを追加

      <body>
            .....
           <script type="text/javascript" src="cordova.js"></script>
      </body>
      
    2. ビルドした js を使うようにパスを修正

      2箇所修正します。.jsをつけないとNGのようです

      client.bundle.js <script src="./build/client.bundle.js?platform=vr"></script>

      index.bundle.js './build/index.bundle.js?platform=vr&dev=true'

    3. static_assets が index.html があるディレクトリから見えるようにする

       ln -s ../static_assets .
      
    4. assetsRootのパラメータを追加

       ReactVR.init(
           // When you're ready to deploy your app, update this line to point to
           // your compiled index.bundle.js
           './build/index.bundle.js?platform=vr&dev=true',
           // Attach it to the body tag
           document.body,
           { assetRoot: 'static_assets' }  // 追加
         );  
      
  4. cordovaのwwwディレクトリがReact VRのコンテンツを参照するようにする

    cordovaプロジェクトのルートディレクトリに移動して

    $ mv www www.orig
    $ ln -s react_vr/vr www
    
  5. cordovaアプリをビルド

     $ cordova build [ios/android]
    

    この状態で $ cordova serve ios をするとブラウザ上で動作確認できます。

    f:id:sanshonoki:20170512101655g:plain

エミュレーターで確認

続いてエミュレーターで動作確認します

  1. iOS

     $ cordova emulate ios 
    

    必要に応じて --target=iPhone-6sのようにターゲット端末をオプションで指定します。

    f:id:sanshonoki:20170512102602p:plain:w250

    おお、出ました! ただし、シミュレーターではジャイロが使えないため真下向きのままから動きません… (´Д`。)

  2. Android

    Android は 最初にエミュレーターを起動してから コマンドを打ってください

     $ cordova emulate android
    

    f:id:sanshonoki:20170506152630p:plain:w250

    orz.. AndroidのエミュレーターはWebGLをサポートしてないようです..

実機で確認

手持ちのiPad mini でやってみます。

そのまま、$ cordova run ios すると

 `Code Sign error: No code signing identities found: No valid signing identities (i.e. certificate and private key pair) were found.` 

のエラーが出るので Xcodeのプロジェクトを開きます

$ open platforms/ios/HelloCorodvaReactVr.xcodeproj

Fix issueしてXcode上でビルド、実行します。 f:id:sanshonoki:20170511151122j:plain:w300

アプリは起動できたもののテクスチャが表示されません..。(ノ゚ο゚)ノ
iPad miniが古いせいでしょうか…

Androidは手持ちのNexus5がいつの間にやらUSBデバッグ接続できなくなっていて(なぜ…?)実機で確認できませんでした..

iOS、Androidとも最新の実機では動いてくれると信じています。。

実機で確認(続)

iPad mini の OS を10.3 に アップデートしたら見れるようになりました! ヾ(´▽`)ノ f:id:sanshonoki:20170512141643j:plain

アップデート前は動いてなかったThree.jsのサンプルがOSアップデート後に動くようになったのでそれでWebGL対応を判定できそうです。 https://threejs.org/examples/#webgl_animation_cloth

ユニファCTOの役割

 みなさまこんにちは。ユニファCTOの赤沼です。私がユニファでCTOという役割を担うようになってから一年ちょっと経ちました。私も入社当初はガンガンコードを書いていましたが、最近は基本的には自分ではコードを書かなくなり、他のメンバーからは私が何をやっているのか、どんな役割なのかが見えづらくなって来てしまいました。そこで先日開発部の定例ミーティング時に、私が考えるユニファCTOの役割についてメンバー向けに話させてもらったので、ここでも簡単に紹介させていただこうと思います。

先端的な技術を保育の現場や家族コミュニケーションに活かす

 一般的な定義としては、「技術的な視点で経営にコミットする」というのがCTOの役割であり、技術と人の問題に答えを出して実行する人ということになるかと思います。それをユニファの事業内容を踏まえて少し具体化すると、「先端的な技術を保育の現場や家族コミュニケーションに活かす方法を見つけて実行する、また、そういうチームを作る人」ということになると思っています。経営陣の一員としては技術視点での事業企画への提案を求められるわけですが、他の経営陣への技術的な理解を促すことも、前述の役割を果たすためには必要になって来ます。

高速道路のちょっと先を開拓できるチームを作る

 良くある話として、エンジニアやクリエイターがユーザ視点を忘れてシステムを作ると、実際のユーザにとっては受け入れられないことが多いので、ユニファではサービスの主なユーザである保育士や保護者の視点で考えることを重視しています。保育の現場や家族コミュニケーションそれ自体について深く掘り下げて考えることはとても重要で、開発部メンバーにとっても必要なことです。ですが、ちょっと見方を変えると、それは開発部メンバー以外でもできることではあったりします。逆に、それぞれの専門的な技術領域について深く掘り下げていったり、それを現場にどう活かせるかを考えるのは、開発部メンバーにしかできません。もちろんユーザ視点を忘れて良いということではないですが、それぞれの領域におけるプロフェッショナルとしての技術向上が本来の役割として強く求められます。例えばエンジニアがAIの技術的な流れに乗れていないと、長期的に見たときにビジネス面でも取り残される可能性があるわけです。

 ユニファは先日優勝したStartup World Cupをはじめ、色々なピッチコンテスト等で賞をいただいていて、事業モデルとしては高い評価をいただいていますが、会社としては事業モデルも優れているし、技術面でも強みがあるというのが理想なわけです。今後の技術的な方向性として、技術的にも強みなるし、事業面でも優位に立てる技術をいかに選択していくかということになります。

 とは言え純粋な技術要素自体を事業的な強みにするのは困難です。なので最初から全く新しい道を探すのではなく、すでにデファクトスタンダートになっている技術や、ある程度環境が整って来ている技術は大いに活用させていただき、高速道路を進んだそのちょっと先のまだ整っていない領域をどう開拓していくかということではないかと思っています。そのためには技術的な視点を高く保つことが大事ですし、技術要素だけでなく、開発プロセスなどに強みを見つけられる可能性もあります。

 また、そういった取り組みをしていくためには、会社全体として技術に投資していく文化を作ることが必要になります。ビジネスサイドメンバーからすると結果に直結する部分ではないので理解を得づらいのですが、将来的なことを考えて今技術に投資していくと同時に、現在のシステムが持っている、目に見えない技術的な課題を解消していくことについてリソースを割くことに納得感を持ってもらえるように働きかけていくことが求められると思っています。

トップダウン+ボトムアップ

 CTOとしては誰よりも深く考えて、5年後、10年後を考えたときに今どの技術を選ぶかの技術的な方針を打ち出す言わばトップダウンの視点になるわけですが、一方で各メンバーからも、こういうことができるんじゃないか、というボトムアップの提案が出てくることも重要です。こういったボトムアップの提案がメンバーからどんどん出てくる、ボトムアップ体質を持った、トップダウンとボトムアップの両面から挟み込んでいけるチームを作っていくことが重要だと思っています。技術面でも例えばエンジニアであれば、プログラミングやコンピュータサイエンスなどのベース技術のレベルアップと、先端技術のキャッチアップの両面が求められていきます。メンバーそれぞれがそれぞれの専門領域におけるプロフェッショナルとして、スキルアップへの手を休めず追求していくことに当然のこととして取り組んでいけるチームを作っていきたいと思っています。

Swift3でRealmを使って多対多のモデルを作るには

こんにちは。スマートフォンエンジニアのまさです。
最近サーバとやりとりをするアプリを作成していますが、APIをなんども叩くアプリでは、 どうしてもローディングが多くなりがちで、ユーザーにストレスを与えてしまいます。
何度も叩く必要のないAPIはレスポンスをRealmに保存しておいて、より快適なアプリを実現したいですね。
今回は、Realmで多対多のモデルを作る方法をご紹介します。

Realmのインストール

まずは、podでrealmを入れます。

$ pod init
$ vi Podfile
pod 'RealmSwift' # 追加
$ pod install

Realmのモデルを作成する

今回は複数の子供が複数のグループに所属しているモデルを作ってみようと思います。 基本的には、多対多は、中間テーブルを用意し、それぞれのテーブルに一対多にすることで実現します。

f:id:unifa_tech:20170419141521p:plain

import RealmSwift

class Kid: Object {
    dynamic var id = 0
    dynamic var name = ""
    var kidGroups = List<KidGroup>()
     
    override static func primaryKey() -> String? {
        return "id"
    }
}
 
class KidGroup: Object {
    dynamic var group: Group?
    dynamic var kid: Kid?
    let ownerKid = LinkingObjects(fromType: Kid.self, property: "kidGroups")
    let ownerGroup = LinkingObjects(fromType: Group.self, property: "kidGroups")
}
 
class Group: Object {
    dynamic var id = 0
    dynamic var name = ""
    var kidGroups = List<KidGroup>()
     
    override static func primaryKey() -> String? {
        return "id"
    }
}

Realmのモデルに実際にデータを入れてみる

モデルクラスを用意したら、データを入れてみます。 テストなので、viewDidLoadに入れてログを出して確認しています。

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
     
    // テストのため、毎回realmのDBを消して再度新規生成されるようにしています
    let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let path = paths[0] + "/test.realm"
    let url = URL(fileURLWithPath: path)
    let fileManager = FileManager.default
    if fileManager.fileExists(atPath: path) {
        try! FileManager.default.removeItem(atPath: path)
        try! FileManager.default.removeItem(atPath: path + ".lock")
        try! FileManager.default.removeItem(atPath: path + ".management")
    }
     
    let realm = try! Realm(fileURL: url)
     
    // たろう君は、たぬき組に、所属、
    // じろう君は、たぬき組、きつね組どちらにも所属させてみる
     
    let kid = Kid()
    kid.id = 1
    kid.name = "たろう"
     
    let kid2 = Kid()
    kid2.id = 2
    kid2.name = "じろう"
     
    let group = Group()
    group.id = 101
    group.name = "たぬき組"
     
    let group2 = Group()
    group2.id = 102
    group2.name = "きつね組"
     
    let kidGroup = KidGroup()
    kidGroup.group = group
    kidGroup.kid = kid
     
    kid.kidGroups.append(kidGroup)
    group.kidGroups.append(kidGroup)
     
    let kidGroup2 = KidGroup()
    kidGroup2.group = group2
    kidGroup2.kid = kid2
     
    kid2.kidGroups.append(kidGroup2)
    group2.kidGroups.append(kidGroup2)
     
    let kidGroup3 = KidGroup()
    kidGroup3.group = group
    kidGroup3.kid = kid2
     
    kid2.kidGroups.append(kidGroup3)
    group.kidGroups.append(kidGroup3)
     
    try! realm.write() {
        realm.add(kid)
        realm.add(kid2)
    }

}

正しく保存されているか確認してみる

それでは、正しく参照できるのか、また中間テーブルが削除されれば きちんと関連がきれるのか、テストしてみようと思います。下記のコードをviewDidLoadの下側に追記してみます。

    // クエリ
    print("Kidのオブジェクトをすべて取得する")
    let kids = realm.objects(Kid.self)
    print(kids.count) // => 2
    print(kids[0].kidGroups.count) // => 1
    print(kids[1].kidGroups.count) // => 2 
     
    print("Kidのidが2のオブジェクトを取得する")
    let kidsId_2 = realm.objects(Kid.self).filter("id = 2")
    print(kidsId_2[0].kidGroups.count) // => 2
    print(kidsId_2[0].kidGroups[0].group?.name ?? "") // => たぬき組
    print(kidsId_2[0].kidGroups[1].group?.name ?? "") // => きつね組
     
    print("Groupがきつね組に所属しているkidを取得する")
    let groupsId_102 = realm.objects(Group.self).filter("id = 102")
    print(groupsId_102[0].kidGroups.count) // => 1
    print(groupsId_102[0].kidGroups[0].kid?.name ?? "") // => じろう
     
    print("Kidじろうのgroupきつね組との関連を切ってみる")
    try! realm.write {
        realm.delete(groupsId_102[0].kidGroups[0])
    }
     
    print("きちんと関連が切れているか確認する groupから参照した場合")
    let updateGroupsId_102 = realm.objects(Group.self).filter("id = 102")
    print(updateGroupsId_102[0].kidGroups.count) // =>0
     
    print("きちんと関連が切れているか確認する kidから参照した場合")
    let updateKidsId_2 = realm.objects(Kid.self).filter("id = 2")
    print(updateKidsId_2[0].kidGroups.count) // => 1
    print(updateKidsId_2[0].kidGroups[0].group?.name ?? "") // => たぬき組
 

正しく参照できているようです。中間テーブルを削除して関連を切れば、kidから参照しても、groupから参照しても正しい結果と なっているのがわかります。多対多を実現させることができました。
まだまだRealm初心者なので、今後もいろいろと試してみようと思います。
もしここがおかしいとか、こういう書き方もあるよなどありましたら、ご指摘頂けると幸いです。