ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

続・RailsアプリからGoogleフォトに写真をアップロードしてみる

暑い!Webエンジニアの本間です。

早速で恐縮なのですが、半年ほど前「RailsからGoogleフォトに写真をアップロードしてみる」というエントリーを書きました。

tech.unifa-e.com

こちらの記事では、PicasaのAPIを使って写真をアップロードしていました。 やりたいことは一通りできたのですが、記事の後半でも書いた通り、いつまでサポートされるかわからない問題がありました。

その後、2018年5月8日Googleから Google Phtos API の発表がありました。 正式なGoogleフォト専用のAPIということで、今後はこちらのAPIを使う方がよさそうです。 今回こちらのAPIを使って、前回作成したアプリケーションの改修を行い、使い勝手などを確認しようと思います。

続きを読む

ログ収集サービス「LogDNA」を試してみた

おはようございます、こんにちは、なますて、こんばんは

ボルダリングに週1で通い始めて2ヶ月ほど立ちましたが5Qで止まってるユニファのインフラ見てます残念なすずきです。 先週朝起きたら右足がつり、気にせずボルダリング行ったら左足がつってしまい体のヤバさを実感しています。

さて本題

からだもやばいですが、弊社のログサーバもやばい。 FluentdとFluetdクラスタで集めたログを、ログサーバに溜め込んでるのですがログを順番に一箇所にまとめるためログサーバは一台構成にしています、つまりSPOF(単一障害点)になってます!やばい!
そのためFluentdのバッファに頼ってるやばい構成なのでやばい
やばいしか言ってないですけどやばい

そんなやばい状況で存在を知った LogDNAであります。

続きを読む

ソラコム様の"Discovery" 2018にて出展して参りました。

梅雨開けにも関わらずやけに雨が多いな……と思っているエンジニアの田渕です。 暑くなったり少し涼しくなったりかんかん照りだったり雨が続いたり……なかなか体調管理の難しい季節ですので、みなさまお体ご自愛ください。

さて、本日の話題は、タイトルにも記載した通り、ソラコム様のSORACOM Conference "Discovery" 2018です。

discovery2018.soracom.jp

その歴史やイベント全体のレポート等は他の方々が既に書いてくださっておりますので、私は出展側(と登壇側)としてのレポートをさせて頂こうと思います。

今年は見るだけじゃない!

言わずと知れた一大イベント、昨年の"Discovery" 2017には弊社のCTO赤沼が参加者としてうかがっておりました。 今年は有難くも、赤沼の登壇および「るくみー午睡チェック」の出展のお話を頂き、イベントの少し前から色々と準備、調整をして参りました。

私は普段オフィスに篭って居ることが殆どで、こういう場に出展側として1日参加するのは初めてです。 しかもなんだか立派な会場で、服もどうしよう!?と前日まで右往左往。 当日朝、自宅から直行するも、案の定迷子になりました。。。

立派な受付を通り、更に会場の中でも迷子になりながら弊社のブースまで辿り着くと、とても素敵な看板が! 準備終わり、開場前に記念写真をパチリ。 f:id:unifa_tech:20180705142447j:plain

ありがたいことに大盛況

今年我々がブースを出させて頂いたのは、IoT Touch & Tryというエリアです。

medium.com

このエリアは、ソラコム様の各種サービスを利用して作られた製品等を実際に体験可能な場所になっており、興味深いブースがたくさん。 準備しつつも、あちこち気になり。。。 午前のキーノートを終え、タイムテーブル上はお昼休みの時間帯に相当する12時以降、徐々に参加者のみなさんの姿が増えてきました。 今回はマーケティング、営業、広報、エンジニアの系5名という体制で、ちょっと多いかも?と思っていたのですが、とんでもない。。。 ありがたいことに、お話を聞きに来てくださる方がたくさんいらっしゃって、全員埋まってしまう場面もあったり。 この状況は、セッションの合間、及び終了後も同じく続き、メンバー一同想定外の嬉しい悲鳴をあげていました。

エンジニアとして参加させて頂いて

今回はソラコム様のイベントということで、いらしている参加者の方もエンジニア、もしくはそれに近しい立場の方が多くなっています。保育業界向けのサービスを展開している弊社のことや、その製品を他業界の方が知る機会は殆どないため、初めて聞きますという方が殆どでした。とても印象深かったのが、説明すると一様に「るくみー午睡チェック」というサービスに対して共感を頂けたこと、また、その仕組みについても肯定的なお言葉を頂けることが多かったというところです。実際にご自身が何らかのものづくりをされている方が多いこともあり、労いのお言葉や、「頑張ってね!応援してるよ!」というお言葉を頂くこともありました。 午睡の現場における課題解決のお手伝いにと「るくみー午睡チェック」というサービスを試行錯誤しながら作り上げてきた訳ですが、例えば第三者のエンジニアから見た時に、この課題に対して現在の仕組みはどう映るんだろう?実はもっといいアプローチがあったりするんじゃないのか?ということは常々考えていました。そんな中、同業の方々から共感や感心のお声を頂けたことはとても心強く、また自信に繋がりました。

イベント終了後は、懇親会にも参加させて頂き、そちらでも色々と勉強になるお話を伺うことができました。 普段立ちっぱなしなんてことが殆どないので、お家に帰ったらご飯を食べて寝る支度して倒れるように寝てしまうくらいでしたが、とても楽しいイベントでした。

赤沼の登壇

冒頭でも書きましたが、今年は弊社CTO赤沼が登壇! 「IoTで新たな価値を顧客に届けるスタートアップの挑戦」という午後の一番はじめのセッションでした。

discovery2018.soracom.jp

このタイミングで、私は他の皆さんにブースをお願いし、応援に。。。(送り出してくれたみなさま、ありがとうございます。) f:id:unifa_tech:20180705153104j:plain 我々の期待を他所に?大きく噛んだりすることもなく、転んだりすることもなく、安定した登壇でした。発表資料等は後日公開されるそうです。

まとめ

  • SORACOM Conference "Discovery" 2018とっても楽しかったです。
  • 来年もお声がけ頂けるように頑張ります。
  • 似たような悩みを持つエンジニアの皆さんとお会い出来、刺激を頂くことが出来る貴重な場だと思いました。

それでは!

空気品質センサでオフィスの二酸化炭素濃度をモニターする

 皆様こんにちは。ユニファの赤沼です。ユニファでは着々とメンバーが増え、できることも増えてきて嬉しい限りなのですが、反面オフィスが手狭になってきて、換気が不十分で空気の悪さが気になるようにもなってきました。二酸化炭素濃度が上がると生産性にも悪影響があるという事でチームラボさんも測定と改善に取り組まれているようです。

ch.nicovideo.jp

 そこで試しに空気品質センサを使って空気品質モニターを自作してみました。今回使ったセンサはスイッチサイエンスさんで販売されている、 CSS811 というセンサモジュールを使った SparkFun 製のブレークアウトボードです。

www.switch-science.com

Hookup

 ブレークアウトボードのチュートリアルは SparkFun の下記サイトで公開されていますので、これを参考に回路を組んでいきます。

CCS811 Air Quality Breakout Hookup Guide - learn.sparkfun.com

 正確な測定を行うには温度センサで測定した温度を使用する必要があるのですが、今回はひとまず単体で使用してみます。

 チュートリアルでは Arduino の互換ボードを使用していますが、 Raspberry Pi の方が色々と融通が聞かせやすいので、 Raspberry Pi Zero W を使用してみます。また、CCS811 ではインタラプトモードを使用する事で、データが発生した時だけセンサモジュールを稼働させて省電力で使用することができますが、今回は常時電源に接続して使用する想定なので、インタラプトモードは使わずにシンプルな構成にしています。回路図は下記の通りです。

f:id:akanuma-hiroaki:20180628084323p:plain

 CCS811 は I2C による接続なので、電源と GND 以外は SDA(Serial Data), SCL(Serial Clock) の2本を使用しています。 CCS811 の動作電圧は 3.3V なので、電源は Raspberry Pi の 3.3V ピンから供給します。実際に組んでみた回路は下記のようになりました。

f:id:akanuma-hiroaki:20180628084529j:plain

I2C の使用設定

 Raspberry Pi ではデフォルトでは I2C が使える設定になっていませんので、 raspi-config から設定を行います。

$ sudo raspi-config

 メニュー画面が表示されたら Interfacing Options を選択します。

f:id:akanuma-hiroaki:20180628084936p:plain

 I2C の項目を有効にすることで I2C が使用できるようになります。

f:id:akanuma-hiroaki:20180628085001p:plain

 次に設定ファイル /boot/config.txt に下記の1行を追記します。

dtparam=i2c_baudrate=10000

 ここまでの設定ができたら一度 Raspberry Pi を再起動します。再起動後に i2cdetect コマンドで下記のようにセンサが認識されていれば使用可能な状態になっています。このブレークアウトボードの I2C アドレスは 0X5B です。

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- 5b -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                        

 プログラムの実装には Python 3 を使います。

$ python -V
Python 3.6.5

 Python 3 系で I2C を使用するには smbus2 をインストールする必要がありますので、 pip でインストールします。

$ pip install smbus2

プログラム実装

 それではプログラムを実装していきたいと思います。 SparkFun では Arduino 用のライブラリは提供していますが、 Python 用のライブラリやサンプルは提供されていないようです。ですが CCS811 を使用したブレークアウトボードは Adafruit からも販売されていて、そちらでは Python 向けのライブラリやサンプルが Github の下記リポジトリで提供されていましたので、そちらから必要な部分を抜き出す形で実装してみました。

github.com

github.com

 まずは CCS811 を直接扱うクラスの実装です。基本的な操作方法としては、該当の I2C デバイスのアドレスを指定し、操作に応じたレジスタに対してバイトデータやブロックデータの読み書きをしています。初期化時に self.writeList(CCS811_BOOTLOADER_APP_START, []) でセンサモジュールの動作を開始させ、 self.disableInterrupt() でインタラプトモードをOFFにしています。また、 self.setDriveMode(mode) でデータの取得間隔を指定しています。

#!/usr/bin/env python

import os
import smbus2 as smbus
from collections import OrderedDict
from logging import basicConfig, getLogger, DEBUG, FileHandler, Formatter
from time import sleep

CCS811_ADDRESS  =  0x5B

CCS811_STATUS = 0x00
CCS811_MEAS_MODE = 0x01
CCS811_ALG_RESULT_DATA = 0x02
CCS811_HW_ID = 0x20

CCS811_DRIVE_MODE_IDLE = 0x00
CCS811_DRIVE_MODE_1SEC = 0x01
CCS811_DRIVE_MODE_10SEC = 0x02
CCS811_DRIVE_MODE_60SEC = 0x03
CCS811_DRIVE_MODE_250MS = 0x04

CCS811_BOOTLOADER_APP_START = 0xF4

CCS811_HW_ID_CODE = 0x81

class CCS811:
    LOG_FILE = '{script_dir}/logs/ccs811.log'.format(
        script_dir = os.path.dirname(os.path.abspath(__file__))
    )

    def __init__(self, mode = CCS811_DRIVE_MODE_1SEC, address = CCS811_ADDRESS):
        self.init_logger()

        if mode not in [CCS811_DRIVE_MODE_IDLE, CCS811_DRIVE_MODE_1SEC, CCS811_DRIVE_MODE_10SEC, CCS811_DRIVE_MODE_60SEC, CCS811_DRIVE_MODE_250MS]:
            raise ValueError('Unexpected mode value {0}.  Set mode to one of CCS811_DRIVE_MODE_IDLE, CCS811_DRIVE_MODE_1SEC, CCS811_DRIVE_MODE_10SEC, CCS811_DRIVE_MODE_60SEC or CCS811_DRIVE_MODE_250MS'.format(mode))

        self._address = address
        self._bus = smbus.SMBus(1)

        self._status = Bitfield([('ERROR' , 1), ('unused', 2), ('DATA_READY' , 1), ('APP_VALID', 1), ('unused2' , 2), ('FW_MODE' , 1)])
        
        self._meas_mode = Bitfield([('unused', 2), ('INT_THRESH', 1), ('INT_DATARDY', 1), ('DRIVE_MODE', 3)])

        self._error_id = Bitfield([('WRITE_REG_INVALID', 1), ('READ_REG_INVALID', 1), ('MEASMODE_INVALID', 1), ('MAX_RESISTANCE', 1), ('HEATER_FAULT', 1), ('HEATER_SUPPLY', 1)])

        self._TVOC = 0
        self._eCO2 = 0

        if self.readU8(CCS811_HW_ID) != CCS811_HW_ID_CODE:
            raise Exception("Device ID returned is not correct! Please check your wiring.")

        self.writeList(CCS811_BOOTLOADER_APP_START, [])
        sleep(0.1)

        if self.checkError():
            raise Exception("Device returned an Error! Try removing and reapplying power to the device and running the code again.")
        if not self._status.FW_MODE:
            raise Exception("Device did not enter application mode! If you got here, there may be a problem with the firmware on your sensor.")

        self.disableInterrupt()

        self.setDriveMode(mode)

    def init_logger(self):
        self._logger = getLogger(__class__.__name__)
        file_handler = FileHandler(self.LOG_FILE)
        formatter = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        file_handler.setFormatter(formatter)
        self._logger.addHandler(file_handler)
        self._logger.setLevel(DEBUG)

    def disableInterrupt(self):
        self._meas_mode.INT_DATARDY = 1
        self.write8(CCS811_MEAS_MODE, self._meas_mode.get())

    def setDriveMode(self, mode):
        self._meas_mode.DRIVE_MODE = mode
        self.write8(CCS811_MEAS_MODE, self._meas_mode.get())

    def available(self):
        self._status.set(self.readU8(CCS811_STATUS))
        if not self._status.DATA_READY:
            return False
        else:
            return True

    def readData(self):
        if not self.available():
            return False
        else:
            buf = self.readList(CCS811_ALG_RESULT_DATA, 8)
            self._eCO2 = (buf[0] << 8) | (buf[1])
            self._TVOC = (buf[2] << 8) | (buf[3])
            if self._status.ERROR:
                return buf[5]
            else:
                return 0

    def getTVOC(self):
        return self._TVOC

    def geteCO2(self):
        return self._eCO2

    def checkError(self):
        self._status.set(self.readU8(CCS811_STATUS))
        return self._status.ERROR

    def readU8(self, register):
        result = self._bus.read_byte_data(self._address, register) & 0xFF
        self._logger.debug("Read 0x%02X from register 0x%02X", result, register)
        return result

    def write8(self, register, value):
        value = value & 0xFF
        self._bus.write_byte_data(self._address, register, value)
        self._logger.debug("Wrote 0x%02X to register 0x%02X", value, register)

    def readList(self, register, length):
        results = self._bus.read_i2c_block_data(self._address, register, length)
        self._logger.debug("Read the following from register 0x%02X: %s", register, results)
        return results

    def writeList(self, register, data):
        self._bus.write_i2c_block_data(self._address, register, data)
        self._logger.debug("Wrote to register 0x%02X: %s", register, data)

class Bitfield:
    def __init__(self, _structure):
        self._structure = OrderedDict(_structure)
        for key, value in self._structure.items():
            setattr(self, key, 0)

    def get(self):
        fullreg = 0
        pos = 0
        for key, value in self._structure.items():
            fullreg = fullreg | ( (getattr(self, key) & (2**value - 1)) << pos )
            pos = pos + value

        return fullreg

    def set(self, data):
        pos = 0
        for key, value in self._structure.items():
            setattr(self, key, (data >> pos) & (2**value - 1))
            pos = pos + value

 CO2濃度が閾値を超えた時に Slack に通知するためのクラスも下記のように実装しておきます。ユーザ名やテキストなどを指定して Slack の Webhook にポストします。

#!/usr/bin/env python

import json
import requests

class SlackNotifier:
    def __init__(self, webhook_url):
        self.webhook_url = webhook_url

    def notify(self, fallback, text, username, icon_emoji, channel, color):
        headers = {
            'Content-Type': 'application/json'
        }

        payload = {
            'username':    username,
            'channel':     channel,
            'icon_emoji':  icon_emoji,
            'attachments': [
                {
                    'fallback': fallback,
                    'text':     text,
                    'color':    color
                }
            ]
        }

        response = requests.post(self.webhook_url, json = payload, headers = headers)

        return {
            'status_code': response.status_code,
            'text':        response.text
        }

 そして上記のクラスをしようするメインのスクリプトを実装します。 CCS811 の初期化後にセンサが有効になったら、 self._ccs811.readData() で現在のCO2濃度などを計算し、計算後のデータを self._ccs811.geteCO2() で取得しています。取得した CO2 濃度から現在のステータスを判別し、ステータスが変わった場合には Slack に通知します。

#!/usr/bin/env python

import os
import sys
from logging import basicConfig, getLogger, DEBUG, FileHandler, Formatter
from time import sleep

import slack_notifier
from CCS811 import CCS811

class AirConditionMonitor:
    CO2_PPM_THRESHOLD_1 = 1000
    CO2_PPM_THRESHOLD_2 = 2000

    CO2_LOWER_LIMIT  =  400
    CO2_HIGHER_LIMIT = 8192

    CO2_STATUS_CONDITIONING = 'CONDITIONING'
    CO2_STATUS_LOW          = 'LOW'
    CO2_STATUS_HIGH         = 'HIGH'
    CO2_STATUS_TOO_HIGH     = 'TOO HIGH'
    CO2_STATUS_ERROR        = 'ERROR'

    SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX'
    SLACK_USERNAME    = 'AIR_CONDITION_MONITOR'
    SLACK_EMOJI_ICON  = ':loudspeaker:'
    SLACK_CHANNEL     = '#air_condition_monitor'

    LOG_FILE = '{script_dir}/logs/air_condition_monitor.log'.format(
        script_dir = os.path.dirname(os.path.abspath(__file__))
    )

    def __init__(self):
        self._ccs811 = CCS811()
        self.slack_notifier = slack_notifier.SlackNotifier(self.SLACK_WEBHOOK_URL)
        self.co2_status = self.CO2_STATUS_LOW
        self.init_logger()

    def init_logger(self):
        self._logger = getLogger(__class__.__name__)
        file_handler = FileHandler(self.LOG_FILE)
        formatter = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        file_handler.setFormatter(formatter)
        self._logger.addHandler(file_handler)
        self._logger.setLevel(DEBUG)

    def notify_to_slack(self, co2, co2_status):
        if co2_status == self.CO2_STATUS_ERROR:
            fallback = "Something Went Wrong..."
            text = fallback
            color = 'danger'
        else:
            fallback = "CO2 level is {0}: {1:,}ppm".format(co2_status, co2)
            text = fallback

            color = 'good'
            if co2_status == self.CO2_STATUS_HIGH:
                color = 'warning'
            elif co2_status == self.CO2_STATUS_TOO_HIGH:
                color = 'danger'

        res = self.slack_notifier.notify(
            fallback, text, self.SLACK_USERNAME, self.SLACK_EMOJI_ICON, self.SLACK_CHANNEL, color
        )
        self._logger.debug(
            'Notified to Slack. STATUS_CODE - {status_code}, MSG - {msg}'.format(
                status_code = res['status_code'], msg = res['text']
            )
        )

    def status(self, co2):
        if co2 < self.CO2_LOWER_LIMIT or co2 > self.CO2_HIGHER_LIMIT:
            return self.CO2_STATUS_CONDITIONING
        elif co2 < self.CO2_PPM_THRESHOLD_1:
            return self.CO2_STATUS_LOW
        elif co2 < self.CO2_PPM_THRESHOLD_2:
            return self.CO2_STATUS_HIGH
        else:
            return self.CO2_STATUS_TOO_HIGH

    def execute(self):
        while not self._ccs811.available():
            pass

        while True:
            if not self._ccs811.available():
                sleep(1)
                continue

            try:
                if not self._ccs811.readData():
                    co2 = self._ccs811.geteCO2()
                    co2_status = self.status(co2)
                    if co2_status == self.CO2_STATUS_CONDITIONING:
                        self._logger.debug("Under Conditioning...")
                        sleep(2)
                        continue

                    if co2_status != self.co2_status:
                        self.notify_to_slack(co2, co2_status)
                        self.co2_status = co2_status

                    self._logger.info("CO2: {0}ppm, TVOC: {1}".format(co2, self._ccs811.getTVOC()))
                else:
                    self._logger.error('ERROR!')
                    while True:
                        pass
            except:
                self._logger.error(sys.exc_info())
                self.notify_to_slack(-1, self.CO2_STATUS_ERROR)

            sleep(2)

if __name__ == '__main__':
    air_condition_monitor = AirConditionMonitor()
    air_condition_monitor.execute()

動作確認

 プログラムを実行すると下記のように二酸化炭素濃度と総揮発性有機化合物の測定値がログに出力されていきます。

2018-06-28 08:58:00,383 - AirConditionMonitor - INFO - CO2: 407ppm, TVOC: 1
2018-06-28 08:58:02,435 - AirConditionMonitor - INFO - CO2: 407ppm, TVOC: 1
2018-06-28 08:58:04,483 - AirConditionMonitor - INFO - CO2: 407ppm, TVOC: 1
2018-06-28 08:58:06,531 - AirConditionMonitor - INFO - CO2: 411ppm, TVOC: 1
2018-06-28 08:58:08,579 - AirConditionMonitor - INFO - CO2: 411ppm, TVOC: 1
2018-06-28 08:58:10,627 - AirConditionMonitor - INFO - CO2: 407ppm, TVOC: 1
2018-06-28 08:58:12,675 - AirConditionMonitor - INFO - CO2: 405ppm, TVOC: 0
2018-06-28 08:58:14,722 - AirConditionMonitor - INFO - CO2: 407ppm, TVOC: 1
2018-06-28 08:58:16,770 - AirConditionMonitor - INFO - CO2: 407ppm, TVOC: 1

 二酸化炭素濃度が閾値を越えると下記のように Slack に通知されます。下記の例では閾値はテスト用に低めに設定しています。

f:id:akanuma-hiroaki:20180628090010p:plain

 ちなみに CCS811 では初回使用時に48時間のエージングが推奨されています。また、起動時には20分間のコンディショニングが推奨されていて、起動直後には正しい値が測定されません。

まとめ

 ひとまず測定と通知が行えることは確認できましたが、オフィス内のどこに置くかによって実際の有効性には違いが出てくるかと思います。また、今回は温度を考慮していないので、今後温度も使用して測定したり、二酸化炭素濃度の推移をクラウド上に保存して可視化したりできると、より役に立ちそうかなと思っています。測定した結果どうやって空気品質を改善するかというのはまた課題ではありますが、まずは現状をモニタできるようにした上で、その後の改善を考えられると良いかと思います。

2018年度 人工知能学会全国大会の参加レポート

f:id:unifa_tech:20180621105651j:plain

iOSエンジニアのしだです。
2018年度 人工知能学会全国大会 が、2018/6/5 〜 6/8に鹿児島の城山ホテルで開催されました。
参加人数が、2572名ととても盛り上がりを感じました。 私は 6/6 と 6/7 に聴講参加してきたので報告したいと思います。

続きを読む

AWS SUMMIT 2018 「Tech上級:データベース入門」にいってきました

こんにちは、Webエンジニアのちょうです。先週開催したAWS SUMMIT 2018に行ってきました。予約が遅くて、2セッションしか取れなかったですが、その中の一つ「Tech上級:データベース入門」について自分の理解と感想をここで記録したいと思います。

セッションのレポートはこちらからご覧になれます。

dev.classmethod.jp

AWSにおいて、大枠に分けると、RelationalとNon-Relationalデータベース2種類があります。MySQL、PostgreSQLなどのRDBMSはもちろんRelationalデータベース(AWS RDS)に当てはまります。それとDWHに特化したRedshift。Non-Relationalには、NoSQLデータベースであるDynamoDB、KVデータベースのElasticCacheとまだPreview段階のグラフデータベースNeptuneが入ってます。

実際、利用するシーンとデータベースのタイプを見れば大体どれを使うのは分かるでしょう。例えば、PBレベルのデータがあり、SQLを使いたい場合はRedshift、キャッシュとして使いたいならElasticCache。でもRDSの中ではAWSでしかないAuroraというRDBMSがあります。普段使ってるデータベースとなにが違うのかすこし迷うになるでしょう。

セッションでは、普通のRDSはDBサイズが16TB未満という制限を言及していますが、多分これはクリティカル的な問題ではない。AuroraがMySQL 5.6/5.7, PostgreSQL 9.6と互換性がある上で、パフォーマンスとシームレスインスタンスのアップグレードが一番注目される機能でしょう。Auroraの紹介ページでは同じハードウェアの前提でMySQLの5倍、PostgreSQLの3倍早いと書かれています。その原因について、セッションで紹介されているAuroraのアーキテクチャからすこし分かると思います。

  1. AWSの既存サービス特にS3を利用し、可用性と耐久性の向上
  2. 複数ノード構成

ここで複数ノードがポイントになるでしょう。普通のRDBMSではシングルノードが想定されていて、パフォーマンスの向上はインスタンスタイプに決められています。つまりスケールアップの方法しかないということです。でも複数ノードになることで、分散実行などが可能になり、一リクエストに対して、並列実行のメリットが得られます。

もちろん、現実はマージソートのような簡単な問題ではありません。Auroraの内部で、分散データベースのように、ノードのダウンを備えて、データブロックは複数のAZにコピーしています。それと、アーキテクチャではノードとストレージが分離されています。つまり、スケールアウトもできるようになります。普通のデータベースがダウンしたら、再起動する段階でものすごくリクエストが入ってしまってまだダウンする問題を考え、キャッシュレイヤーがデータベースのプロセス外に移動されるという施策も紹介されました。

AWSすべてのデータベースを一通り説明するセッションなので、もっと詳しいアーキテクチャが話さなかったです。でもこれでAuroraについて大体なイメージをつかまり、なかなか興味深いです。

それ以外に、HTTPセッションに使えそうなDynamoDBのTTL機能も面白そうです。

いかがでしょうか。AWSのデータベースサービスが多くて、選択が難しいという方が多くいらっしゃると思いますが、ぜひこのセッションを見て決めるようになりましょう。

ユニファのWebエンジニアのテスト事情

こんにちは、Webエンジニアの本間です。

去る2018年5月25日(金)、 株式会社ウエディングパーク の皆さまと合同で勉強会を行いました。 弊社からは以下の2つの内容で発表しました。

  1. ユニファのWebエンジニアのテスト事情(現状と今後) by 本間
  2. 新しいスタンダードを作る ユニファのものづくり by 田渕、鶴岡

今回は繰り返しになってしまいますが、自分が発表した1の「ユニファのWebエンジニアのテスト事情(現状と今後)」の内容を紹介しようと思います。

f:id:ryu39:20180529175142j:plain
発表前で緊張している様子

続きを読む