EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

Javaのデータベース操作について

イートスマートの新人エンジニアが、JavaのDB操作について学んだ内容を振り返ってみたいと思います。

大まかなDB操作の流れ

①Connectionでデータベースとの接続を確立。
②Statement(PreparedStatement)でSQLを実行。
③ResultSetでSQLの実行した結果にアクセス。

以下、データベースの接続や操作について必要なパッケージ、インターフェースの説明です。

java.sqlについて

リレーショナルデータベースのデータへアクセスし、様々な処理をするAPIを提供。以下で具体的に説明するような機能が含まれている。

Connection

特定のデータベースとの接続を確立する。Connectionのオブジェクトは、自動コミットモードになっており、データベースへの変更が自動保存される。

Statement

SQLを実行し、実行結果を返す。パラメータなしのSQLの実行に用いられ、Connectionから、Statementオブジェクトを生成し、Statementのオブジェクト に対し、executeメソッドでSQLを実行する。

PreparedStatement

パラメータありのSQLの実行に用いられる。パラメータに実際の値を設定する際に、自動でエスケープシーケンスが行われるので、SQLインジェクションの対策になる。 引数として渡すSQLは事前にコンパイルされるため、SQL が多数回実行される場合にも、高速に実行できる。

ResultSet

ResultSetは、Statementの実行の結果、取得したデータへのアクセスができる。さらに、データ(表)の現在行を指すカーソルを保持しており、nextメソッドによって、 カーソルが次行へ移動する。

SQLException

データベースエラーなどの情報を提供(SQL文の誤りなどによって発生する)。

料理教室の先生情報を取得する

上記の説明を踏まえて、データベースへ接続しSQLを実行するコードを組み立てます。

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SelectCookingTeacher {

public static void main(String[] args) {
    Connection conn = null;
    try {
      // JDBCドライバを読み込み(ここでは、PostgreSQLを使用する想定)
      Class.forName("org.postgresql.Driver");

      // データベースへ接続(接続先のurlを指定)
      conn = DriverManager.getConnection(
          "jdbc:DB接続先","userid","password");

      // 実行するSQLを定義(先生のID、名前、会員ステータスを取得するSQL)
      String sql = "SELECT TEACHER_ID,TEACHER_NAME,MEMBERSHIP_STATUS FROM COOKING_TEACHER";
      PreparedStatement pStmt = conn.prepareStatement(sql);

      // SQL(SELECT文)を実行し、ResultSetで実行結果を受け取る
      ResultSet rs = pStmt.executeQuery();

      // 結果に格納されたレコードをループ処理し、一覧表示(nextメソッドは、ResultSetが提供するメソッド)
      while (rs.next()) {

        BigDecimal teacherId = rs.getBigDecimal("TEACHER_ID");
        String teacherName = rs.getString("TEACHER_NAME");
        String membershipStatus = rs.getString("MEMBERSHIP_STATUS");

        // (例)料理教室の先生に関するデータを出力している
        System.out.println("先生ID:" + teacherId);
        System.out.println("先生の氏名:" + teacherName);
        System.out.println("会員区分:" +membershipStatus);
      }
    } catch (SQLException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } finally {
      // データベース切断
      if (conn != null) {
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

以上となります。

ImageFluxの導入効果について

以前「ImageFluxの導入について」の記事を掲載しましたが、導入後の効果について報告したいと思います。

eatsmart.hatenablog.com

2018年9月から導入に向けたシステム検証を重ね、本番環境へは2018年10月24日に導入しました。 今回は、導入前・後の1ヶ月間で効果測定してみました。

効果測定条件

効果測定条件は以下の通りです。

  • 測定期間
    変更前 9月23日~10月23日
    変更後 10月24日~11月23日

  • 測定ページ
    240px×240pxのサムネイル画像が8枚表示されているページ

  • 画像の種別
    概ねJPEG(一部pngあり)

  • 1か月のブラウザ利用状況
    safari 50%
    Chrome 25%
    safari(in-app) 10%
    Android Webview 10%
    その他 5%

  • ImageFluxのキャッシュヒット率
    2018年11月のトータルリクエストに対して、キャッシュヒット率は約65%でした。

効果測定結果

上記条件下で、GAにて計測を行ってみました。

ページ速度

導入前・後の1ヶ月間で15%速度改善しました

f:id:eatsmart:20190117210813p:plain

離脱率

導入前・後の1ヶ月間で30%離脱率が改善しました

f:id:eatsmart:20190117205244p:plain

1ヶ月の短期間ですが、グラフからも分かる様にページ速度が向上したことで離脱率が大幅に改善しました。サイトの特性上季節要因等でトラフィックが増減するので、ロングテールでどうなるのか等引き続き検証していきたいと思います。

サーバのディスクを整理した話

あけましておめでとうございます。 本年もEatSmartシステム部ブログをよろしくお願いします。 新年最初の記事としては地味ではありますが、昨年末に行ったサーバのディスク整理について書きたいと思います。

昨年インフラをデータセンターからクラウドへ移行したことは以前書きました。当時のディスク使用量と増加分を見込んで、サーバのスペックの選定を行いました。 年明けからインフラを増強する計画があり、それに伴いディスクの使用量を確認したところ、当初の見込みより増加のペースが早いことがわかりました。 当初の見込みよりペースが早くなった要因は、主に以下の5つでした。

Dockerの不要なイメージ

クラウドへ移行してから、全てのサービスはDockerコンテナで稼働しています。 このため、以前に比べDockerイメージの数が増えたこと、テストや検証に伴いDockerイメージを作成することが増えたことで、ディスク使用量が増えていました。 不要なDockerイメージを削除するため"docker system prune"を実行しました。

docker system prune | Docker Documentation

この作業で、サーバによっては100GB弱の容量を確保することが出来ました。

Jenkinsのビルド履歴

基本的に全てのジョブで"古いビルドの破棄"を指定することにしており、不要なファイルが残らないようにしています。 同様に成果物も保存していません。これはDockerイメージとして残しているためです。 この設定がされていないものがあり、ディスク使用量が増えていました。 正しく設定することで、数GBの容量を確保することが出来ました。

ログの転送漏れ

バッチ処理等で出力されるログファイルは、全てログサーバへ転送し、一定期間が過ぎるとアーカイブされる仕組みになっています。 Dockerコンテナで稼働するバッチ処理で、ログの出力先が以前と変わったために上記の転送対象から外れてしまっていたものがありました。 この他、td-agentで集約するログの対処もされていなかったので、転送と削除を行うようにしました。 これにより、数十GBの容量を確保することが出来ました。

一時ファイルの削除忘れ

各種検証やログ解析のために一時的に作成したファイルが残っていました。 絶対的な量は少ないですが、これも削除しました。

まとめ

サービスは正常に稼働しており、またディスク使用量が閾値より少ない状態だったので、気付くのが遅れてしまいました。 以上の作業で、当初の見込みとほぼ変わらない増加ペースに戻すことが出来ました。

2018年を振り返って

2018年はサービスのシステム開発だけでなく、色々な挑戦や取り組みをした1年でした。 自分にとってどんな1年だったかを振り返ってみたいと思います。

今年やったこと

1月〜3月

iDC移転

今年の前半は何と言ってもiDC移転が一番大きなトピックでした。 計画・準備は2017年後半から取り組んでいたのですが、2018年に入ってからは実行に移すフェーズでした。

eatsmart.hatenablog.com

eatsmart.hatenablog.com

  • DB移行

PostgreSQLのバージョンアップと(全サービスで)レプリケーションの構築を行いました。
また、データ移行については、
 ・旧DB→新DBへのWAN越しレプリケーションを構築し、移行時切り替え
 ・事前移行+当日差分移行(Postgresql DB移行 - EatSmartシステム部ブログ)
の2種類の移行を行いました。

  • サービス移行

サービスの環境としては、dockerコンテナ化(swarmモード)という大きなチャレンジをしました。swarmに関しては一部動作が不安定な事があったので、その後使用をやめました。(kubernetesを選んでいたら違っていたかも?)
サーバー筐体もOSも仕組みも全て刷新されるので、安定性と性能のテストはしっかりして、安心できるようにしました。

4月〜6月

移転後環境の安定化

iDCの移転自体は、失敗する事なく完了したのですが、その後の稼働安定化に対応していました。

  • 各サーバーのリソースチューニング

CentOS5からubuntu16+dockerになる事で、今までの知識が単純には使えなかったので、色々と調査しながら対応しました(以前が古すぎただけですが)。

  • 諸々のモニタリング構築

元々zabbixでモニタリングをしていたのですが、OSの変更やdockerコンテナ化などの構成の変化によるメトリクス取得の変更を色々としました。

  • nginxによるリバースプロキシ+swarmのローリングアップデート問題

サービスの入り口にnginxのリバースプロキシを置き、その後ろにswarmでクラスタする事で冗長性と可用性を実現しようとしていたのですが、swarmのローリングアップデート時にリバースプロキシに失敗するという現象がありました。また、本番環境ではないのですが、swarmのマネージャーが無応答になることがあるなど動作に不安があったので、swarmでのクラスタをやめ、アプリケーションサーバーによるクラスタに変更しました。

  • dockerコンテナのpublish portガバガバ問題

dockerコンテナで外部にポートをpublishすると、UFWfirewallを解放するので、今回のさくらインターネットのサーバーのように直接WANへのネットワークインターフェースがある場合、そこのポートも解放されている、という事が発覚しました。
基本的にはdockerネットワークを使用し不要なポートはpublishしていないので、実際に解放されていても大きな問題では無いのですが、とても気持ち悪かったので気付いてよかったです。

7月〜9月

少しづつインフラ以外に着手

同じタイミングではないのですが、さくらインターネットに移転して、サービス利用の簡便さやネットワーク的な利点なども考えて、さくらのコンテンツ配信系サービスを導入してみました。
eatsmart.hatenablog.com

完全https化も実施して、http2利用など配信系の最適化に取り組みました。

  • 新構成でマイクロサービス構築

dockerコンテナでの稼働が基本になったので、インフラ環境を気にせずに新しい構成でのサービスを立てやすくなりました。なので、新しい機能を実装する際には、既存のモノリシックなアプリケーションに実装するのではなく、閉じた機能のマイクロサービスとして構築することに挑戦しています。

eatsmart.hatenablog.com

もともと既存アプリケーションのソースリポジトリとしてsubversionを使用しているのですが、ローカルとリモートのコミット戦略的な所ではgitの方が使いやすいかなと思っていました。今回、新機能をマイクロサービスで構築していくようになったことによって、新ソースのリポジトリをgitを使うようにしました。

新人2名採用

もともと通年で中途の採用活動を行なっていたのですが、応募も少なくなかなか採用に至りませんでした。そこで、経験の浅い新人を採用して業務を通して成長してもらおうという方針にして、2018年は第二新卒の人材紹介を通して採用を行いました。それが実り、2名も(!)採用することができました。

10月〜12月

がっつり機能開発

  • 「カロリーチェックAPI

弊社サービスの「カロリーチェックAPI」に機能追加をするにあたり、spring-bootを使用してRESTサービスを実装しました。

eatsmart.hatenablog.com

  • 「クスパ」の先生向け有料サービス

弊社サービスの 料理教室・パン教室・お菓子教室の総合情報サイト「クスパ」 に、新たな有料サービスの実装を行いました。ここでも、独立した機能についてspring-bootでマイクロサービスを作りました。
久しぶりに大きめのサービス開発だったので、新人含めほとんどのメンバーをアサインして開発しました。

実は上記の2サービスの稼働環境は、前半に移転したサーバーと異なっており、コンテナ化されていません。でも、spring-bootをFully Executable JAR化することで、JARファイルをデプロイするだけで、簡単にサービスを稼働させる事ができました。(サービスのポート番号など可搬性以外の所では、dockerの方が楽ですが。)

これからやりたいこと

2018年にインフラ環境が変わり、技術的自由度が上がり、新人参加によるチーム開発体制に変わりました。そこからの発展と挑戦として、2019年は以下の事などに取り組んでいけたらなあと思っています。

  • gitを使ったチーム開発手法の確立
  • マイクロサービス化の促進
    • ドメインによるサービス分割
    • 現行のモノリシックな構造の分解
    • データも分散化させたい
  • フロントエンドの取り組み
    • モジュール化
    • Vueやらreactやら

あまりまとまっていないですが、今年もありがとうございました。それでは良いお年を。

Apache / Tomcatの基本設定と連携

こんにちは、EatSmartの新人エンジニアです。

弊社サービスであるもぐナビの開発に入るにあたり 環境構築を行いました。ApacheTomcatの導入、基本設定について振り返ってみたいと思います。

WEBサーバー:Apache
APサーバー:Tomcat
言語:Java
IDEeclipse

Tomcatの基本設定(eclipseで起動させる)

eclipseからtomcatを起動できるように設定する

ウィンドウ → ビューの表示→ その他
サーバーを選択 → 新規サーバー
参照ボタンからインストールしたディレクトリを指定する
サーバーから追加される
アイコン「起動」「停止」「再起動」が表示される
f:id:eatsmart:20181219180935p:plain

●インストール完了

ブラウザでlocalhost:8080を開きtomcatの画面が表示されればインストールが完了している

tomcatの設定ファイル(context.xml)

コンテキストにはアプリケーション群の動作に必要なJSP,Servlet,HTML,war,画像などが含まれる

tomcatのログを出力させる

アクセスログ
アクセスログはコンテキストに設定されたJSP,サーブレット,HTMLなどのファイルのアクセス状況を表すログ
・サーバーログ

Apache基本設定

●設定ファイルの位置

Apacheに対する設定はhttpd.conf に記述する
インストールしたディレクトリからファイルの位置を確認する

●Servernameの設定

デフォルトでは既に設定されている
コメントアウトを外す
f:id:eatsmart:20181219180337p:plain

上記はローカル環境で動かしたときの例です

●リクエストを受け付けるときのポート番号の設定

Listen 80
Listen 443(HTTPS通信)
f:id:eatsmart:20181219182017p:plain

ポート番号は複数記述できる

●ServerRootの設定

Apacheがインストールされているディレクトリを指定する

●Includeで必要なファイルを取り込む

f:id:eatsmart:20181219182138p:plain

Apacheをサービスとして起動する

f:id:eatsmart:20181219180815p:plain

Apache httpdTomcatを連携する

●ApahceとTomcatを連携させる

インターネットからのリクエストをhttpdで受け付けて、Javaの動的な処理が必要なページの処理はTomcatで実行させる
そのためにAJPというプロトコルを用いて連携させる

AJP(Apache JServ Protcol)とは

AJPとはTomcatと連携させるプロトコルのこと
Apache httpdが受け付けたリクエストをTomcatに連携させるためにこのAJPプロトコルを用いて通信する
mod_proxy_ajpというモジュールをインストールして使用する

Tomcatで確認

Tomcatのconfディレクトリにあるserver.xmlファイルに設定が記述されている
f:id:eatsmart:20181220190651p:plain

「ポート8080番で HTTP 1.1 の通信を受け付ける」

f:id:eatsmart:20181220190848p:plain

「ポート8009番で AJP 1.3 の通信を受け付ける」

Apache httpdの設定

Apahce httpd の設定を行い連携させる
mod_proxy_ajp と mod_proxyを読み込ませるためにコメントアウトを外し、有効にする

次にどのパスにアクセスされた場合にTomcatと連携するのかを設定する
f:id:eatsmart:20181221093646p:plain

●最後にApacheTomcatを再起動して設定を反映させる

シナリオテストをやってみて

イートスマートの新人エンジニアが、今回はシナリオテストをやってみて学んだことを振り返っていきます。

シナリオテスト実施の背景

弊社が提供する料理教室情報サイト「クスパ」の方で、新たな機能を追加し、リリースさせるという流れがあります。

実施したシナリオテストについて

今回行うことになったシナリオテストでは、大きく二つの観点から検証が必要になります。
①料理教室を主催する「先生」の教室管理サイト側
②料理教室を探す「生徒」のユーザーサイト側

以下、実施したテストの一部になります。
・先生側のプラン変更に伴い、利用できるようになった機能のテスト
・生徒と先生の双方向性のあるコミュニケーション機能のテスト
SNS連携を利用した機能、投稿機能のシェアなどに関するテスト

各シナリオについて、デバイス毎(PC、iPhoneAndroid)によってテストを行いました。

シナリオのサンプルを作成してみる

ここで、もう少し具体的にイメージできるように実際のシナリオ形式に近いサンプルを作ってみます。 シナリオは、プロジェクトの管理者や実装するエンジニアなど複数人が見ることになるので、状況を再現しやすいように 記述するのが望ましいかと思います。f:id:eatsmart:20181121174540p:plain
この例では、生徒が料理教室を「お気に入り」登録→「お気に入り」した生徒に対し、先生がイベントを招待→生徒は、イベントの参加に対し、 意思表明ができる→先生は、「参加」を選んだ生徒にのみ「お礼メール」が遅れるというシナリオを想定しています。

スクリプトテストと探索的テスト

テストを実施していく中で、テストの手法としてどんなものがあるのか興味をもったので 調べてみました。

今回行ったシナリオテストは、いわゆるスクリプトテストに属するということがわかりました。あらかじめ設定したテストケース(シナリオ)を用意し、ドキュメントベースで動作確認を行っていきます。おそらく一般的にどこのWeb系企業も行っている手法でしょう。

スクリプトテストと対比される手法として、探索的テストというものがあります。 探索的テストは、あらかじめテストケースを用意せずに、テストを実施していく過程でテストケースを作っていくという手法になります。 メリットしては、膨大なテストケースを予め用意する必要がないこと、テストケースに縛られることなく、バグが潜んでいそうな分野に集中してその都度テスト内容を検討していけるという柔軟性があります。

探索的テストの導入の可能性

今回スクリプトテストを中心に行いましたが、振り返ってみて探索的テストを中心にプロジェクトに導入するというのは難しいと感じました。理由は以下の点になります。

①自分を含めテスターのサービス・機能に対する理解がテスト初期段階で浅かったため
②チェックする機能が広範囲にわたるため、網羅性を担保できない(ゆえに、不具合が潜んでいそうな部分の特定も難しい。)
③テスターとして動員できる人数、リリース時期のスケジュール感の問題

テスト終盤になってくるにつれて、サイトや機能についての理解が深まってきます。そのため、①の問題点は徐々にクリアする余地がでてきます。

まとめると、導入する場合は、スクリプトテストを一巡した後半で、③との兼ね合いを考慮し、同一のテスターが探索テストを実施してみるのはありではないでしょうか。

シナリオテストを実施してみての総括

シナリオテストをやってきて、最初はシナリオってこんなに在庫あるの!?って感じたり、多数のユーザー間の行き来していく中で、いま俺誰アカウントだっけ?となったり、テスターの苦労が少しわかった気がしています。そうすると、既に実施したテストでこういう問題があったから、次のテストもこうなるだろうという予測。あるいは、PCで確認した際に、スマホではどういう見え方になるんだろう?といった疑問が湧いたりします。時には、夢にも出てきます。そういった経験の中で、以前よりもテストの落とし所が見えてきたり、逆に新鮮な発見があったりします。普通に電車でスマホをいじってるだけでは、見過ごすようなものばかりです。

何よりも収穫であったのは、細かい部分に注目してサイトを見るようになるので、自社のサービス自体に詳しくなれたことだと思います。料理教室の先生と生徒双方のサイトを往復し、生徒になったり、先生になったりというような経験は日常でそう作れるものではありません。納期がある以上、テストを手際よくやっていくことも必要ですが、探索的にテストをしてみるという機会も作ってはいかがでしょうか。

以下、参考にした記事になります。 探索的テストについて

ZabbixからTomcatコンテナを監視する

EatSmartではサーバ、サービスの監視にZabbixを利用しています。 利用するテンプレートは、サーバの監視には"Template OS Linux"を、Tomcatの監視には"Template App Generic Java JMX"と独自に拡張したものを利用しています。 これらの構成はデータセンターを利用していた頃から基本的に変わらないのですが、Zabbix Java GatewayTomcatを監視するのに工夫が必要でした。

Tomcatコンテナの設定

以前は一つのサーバに複数のTomcatをインストールしていたので、JMXが利用するポートが重複しないよう分けていました。 Dockerコンテナで可動するTomcatも同様にコンテナごとに重複しないようにポートは分けるようにしました。 当初、setenv.shと起動時のコマンドは以下のようにしました。

setenv.sh

CATALINA_OPTS="${CATALINA_OPTS} \
  -Dcom.sun.management.jmxremote=true \
  -Dcom.sun.management.jmxremote.port=12345 \
  -Dcom.sun.management.jmxremote.rmi.port=12345\
  -Dcom.sun.management.jmxremote.local.only=false \
  -Dcom.sun.management.jmxremote.authenticate=false \
  -Dcom.sun.management.jmxremote.ssl=false"

export CATALINA_OPTS

run.sh

docker run -d \
  --name tomcat-mog \
  -p 38080:8080 \
  -p 32345:12345 \
  esma/tomcat

このTomcatコンテナは各サービスで共通で利用することを想定しています。 そのため、コンテナ自体は8080/12345という固定のポートを利用し、必要に応じて実行時にポートフォワードさせます。 "tomcat-mog"という名前のこのコンテナは、外部に38080/32345の2つのポートを開放しています。 ZabbixからはZabbix Java Gatewayを経由して32345ポートを利用すれば良いと考えていました。

しかし、この設定では監視ができませんでした。 調査した結果、Zabbixが指定したIPアドレス/ポート(Dockerホストのもの)と自身のIPアドレス/ポート(Dockerコンテナのもの)が異なるため、Zabbix Agentはメトリクスを返していませんでした。

設定の変更

原因がわかったので解消するための設定を行います。

setenv.sh

CATALINA_OPTS="${CATALINA_OPTS} \
  -Dcom.sun.management.jmxremote=true \
  -Dcom.sun.management.jmxremote.port=${JMX_PORT} \
  -Dcom.sun.management.jmxremote.rmi.port=${JMX_PORT} \
  -Dcom.sun.management.jmxremote.local.only=false \
  -Dcom.sun.management.jmxremote.authenticate=false \
  -Dcom.sun.management.jmxremote.ssl=false \
  -Djava.rmi.server.hostname=${HOST_IP_ADDRESS}"

export CATALINA_OPTS

run.sh

HOST_IP_ADDRESS=`hostname -I`

docker run -d \
  --name tomcat-mog \
  -p 38080:8080 \
  -p 32345:32345 \
  --env="JMX_PORT=32345" \
  --env="HOST_IP_ADDRESS=${HOST_IP_ADDRESS}" \
  esma/tomcat

Dockerホストとコンテナ間のIP/ポートを同一にすることは出来ないので、ホストの設定をコンテナ起動時に渡すようにしました。 コンテナではその値を参照して、JMXの値を設定しています。 以上の変更で、無事ZabbixからTomcatコンテナを監視することができるようになりました。