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の導入について」の記事を掲載しましたが、導入後の効果について報告したいと思います。
2018年9月から導入に向けたシステム検証を重ね、本番環境へは2018年10月24日に導入しました。 今回は、導入前・後の1ヶ月間で効果測定してみました。
効果測定条件
効果測定条件は以下の通りです。
測定期間
変更前 9月23日~10月23日
変更後 10月24日~11月23日測定ページ
240px×240pxのサムネイル画像が8枚表示されているページ1か月のブラウザ利用状況
safari 50%
Chrome 25%
safari(in-app) 10%
Android Webview 10%
その他 5%ImageFluxのキャッシュヒット率
2018年11月のトータルリクエストに対して、キャッシュヒット率は約65%でした。
効果測定結果
上記条件下で、GAにて計測を行ってみました。
ページ速度
導入前・後の1ヶ月間で15%速度改善しました
離脱率
導入前・後の1ヶ月間で30%離脱率が改善しました
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年に入ってからは実行に移すフェーズでした。
- 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すると、UFWでfirewallを解放するので、今回のさくらインターネットのサーバーのように直接WANへのネットワークインターフェースがある場合、そこのポートも解放されている、という事が発覚しました。
基本的にはdockerネットワークを使用し不要なポートはpublishしていないので、実際に解放されていても大きな問題では無いのですが、とても気持ち悪かったので気付いてよかったです。
7月〜9月
少しづつインフラ以外に着手
- さくらのWebアクセラレータとImageFluxを導入
同じタイミングではないのですが、さくらインターネットに移転して、サービス利用の簡便さやネットワーク的な利点なども考えて、さくらのコンテンツ配信系サービスを導入してみました。
eatsmart.hatenablog.com
完全https化も実施して、http2利用など配信系の最適化に取り組みました。
- 新構成でマイクロサービス構築
dockerコンテナでの稼働が基本になったので、インフラ環境を気にせずに新しい構成でのサービスを立てやすくなりました。なので、新しい機能を実装する際には、既存のモノリシックなアプリケーションに実装するのではなく、閉じた機能のマイクロサービスとして構築することに挑戦しています。
- リポジトリをsubversionからgitへ
もともと既存アプリケーションのソースリポジトリとしてsubversionを使用しているのですが、ローカルとリモートのコミット戦略的な所ではgitの方が使いやすいかなと思っていました。今回、新機能をマイクロサービスで構築していくようになったことによって、新ソースのリポジトリをgitを使うようにしました。
新人2名採用
もともと通年で中途の採用活動を行なっていたのですが、応募も少なくなかなか採用に至りませんでした。そこで、経験の浅い新人を採用して業務を通して成長してもらおうという方針にして、2018年は第二新卒の人材紹介を通して採用を行いました。それが実り、2名も(!)採用することができました。
- http://eatsmart.hatenablog.com/entry/2018/07/31/151810
- http://eatsmart.hatenablog.com/entry/2018/08/27/113154
10月〜12月
がっつり機能開発
- 「カロリーチェックAPI」
弊社サービスの「カロリーチェックAPI」に機能追加をするにあたり、spring-bootを使用してRESTサービスを実装しました。
- 「クスパ」の先生向け有料サービス
弊社サービスの 料理教室・パン教室・お菓子教室の総合情報サイト「クスパ」 に、新たな有料サービスの実装を行いました。ここでも、独立した機能についてspring-bootでマイクロサービスを作りました。
久しぶりに大きめのサービス開発だったので、新人含めほとんどのメンバーをアサインして開発しました。
実は上記の2サービスの稼働環境は、前半に移転したサーバーと異なっており、コンテナ化されていません。でも、spring-bootをFully Executable JAR化することで、JARファイルをデプロイするだけで、簡単にサービスを稼働させる事ができました。(サービスのポート番号など可搬性以外の所では、dockerの方が楽ですが。)
これからやりたいこと
2018年にインフラ環境が変わり、技術的自由度が上がり、新人参加によるチーム開発体制に変わりました。そこからの発展と挑戦として、2019年は以下の事などに取り組んでいけたらなあと思っています。
- gitを使ったチーム開発手法の確立
- マイクロサービス化の促進
- ドメインによるサービス分割
- 現行のモノリシックな構造の分解
- データも分散化させたい
- フロントエンドの取り組み
- モジュール化
- Vueやらreactやら
あまりまとまっていないですが、今年もありがとうございました。それでは良いお年を。
Apache / Tomcatの基本設定と連携
こんにちは、EatSmartの新人エンジニアです。
弊社サービスであるもぐナビの開発に入るにあたり 環境構築を行いました。ApacheとTomcatの導入、基本設定について振り返ってみたいと思います。
■Tomcatの基本設定(eclipseで起動させる)
●eclipseからtomcatを起動できるように設定する
●インストール完了
●tomcatの設定ファイル(context.xml)
●tomcatのログを出力させる
■Apache基本設定
●設定ファイルの位置
●Servernameの設定
上記はローカル環境で動かしたときの例です
●リクエストを受け付けるときのポート番号の設定
ポート番号は複数記述できる
●ServerRootの設定
●Includeで必要なファイルを取り込む
●Apacheをサービスとして起動する
■Apache httpd と Tomcatを連携する
●ApahceとTomcatを連携させる
●AJP(Apache JServ Protcol)とは
●Tomcatで確認
「ポート8080番で HTTP 1.1 の通信を受け付ける」
「ポート8009番で AJP 1.3 の通信を受け付ける」
●Apache httpdの設定
●最後にApacheとTomcatを再起動して設定を反映させる
シナリオテストをやってみて
イートスマートの新人エンジニアが、今回はシナリオテストをやってみて学んだことを振り返っていきます。
シナリオテスト実施の背景
弊社が提供する料理教室情報サイト「クスパ」の方で、新たな機能を追加し、リリースさせるという流れがあります。
実施したシナリオテストについて
今回行うことになったシナリオテストでは、大きく二つの観点から検証が必要になります。
①料理教室を主催する「先生」の教室管理サイト側
②料理教室を探す「生徒」のユーザーサイト側
以下、実施したテストの一部になります。
・先生側のプラン変更に伴い、利用できるようになった機能のテスト
・生徒と先生の双方向性のあるコミュニケーション機能のテスト
・SNS連携を利用した機能、投稿機能のシェアなどに関するテスト
各シナリオについて、デバイス毎(PC、iPhone、Android)によってテストを行いました。
シナリオのサンプルを作成してみる
ここで、もう少し具体的にイメージできるように実際のシナリオ形式に近いサンプルを作ってみます。
シナリオは、プロジェクトの管理者や実装するエンジニアなど複数人が見ることになるので、状況を再現しやすいように
記述するのが望ましいかと思います。
この例では、生徒が料理教室を「お気に入り」登録→「お気に入り」した生徒に対し、先生がイベントを招待→生徒は、イベントの参加に対し、
意思表明ができる→先生は、「参加」を選んだ生徒にのみ「お礼メール」が遅れるというシナリオを想定しています。
スクリプトテストと探索的テスト
テストを実施していく中で、テストの手法としてどんなものがあるのか興味をもったので 調べてみました。
今回行ったシナリオテストは、いわゆるスクリプトテストに属するということがわかりました。あらかじめ設定したテストケース(シナリオ)を用意し、ドキュメントベースで動作確認を行っていきます。おそらく一般的にどこのWeb系企業も行っている手法でしょう。
スクリプトテストと対比される手法として、探索的テストというものがあります。 探索的テストは、あらかじめテストケースを用意せずに、テストを実施していく過程でテストケースを作っていくという手法になります。 メリットしては、膨大なテストケースを予め用意する必要がないこと、テストケースに縛られることなく、バグが潜んでいそうな分野に集中してその都度テスト内容を検討していけるという柔軟性があります。
探索的テストの導入の可能性
今回スクリプトテストを中心に行いましたが、振り返ってみて探索的テストを中心にプロジェクトに導入するというのは難しいと感じました。理由は以下の点になります。
①自分を含めテスターのサービス・機能に対する理解がテスト初期段階で浅かったため
②チェックする機能が広範囲にわたるため、網羅性を担保できない(ゆえに、不具合が潜んでいそうな部分の特定も難しい。)
③テスターとして動員できる人数、リリース時期のスケジュール感の問題
テスト終盤になってくるにつれて、サイトや機能についての理解が深まってきます。そのため、①の問題点は徐々にクリアする余地がでてきます。
まとめると、導入する場合は、スクリプトテストを一巡した後半で、③との兼ね合いを考慮し、同一のテスターが探索テストを実施してみるのはありではないでしょうか。
シナリオテストを実施してみての総括
シナリオテストをやってきて、最初はシナリオってこんなに在庫あるの!?って感じたり、多数のユーザー間の行き来していく中で、いま俺誰アカウントだっけ?となったり、テスターの苦労が少しわかった気がしています。そうすると、既に実施したテストでこういう問題があったから、次のテストもこうなるだろうという予測。あるいは、PCで確認した際に、スマホではどういう見え方になるんだろう?といった疑問が湧いたりします。時には、夢にも出てきます。そういった経験の中で、以前よりもテストの落とし所が見えてきたり、逆に新鮮な発見があったりします。普通に電車でスマホをいじってるだけでは、見過ごすようなものばかりです。
何よりも収穫であったのは、細かい部分に注目してサイトを見るようになるので、自社のサービス自体に詳しくなれたことだと思います。料理教室の先生と生徒双方のサイトを往復し、生徒になったり、先生になったりというような経験は日常でそう作れるものではありません。納期がある以上、テストを手際よくやっていくことも必要ですが、探索的にテストをしてみるという機会も作ってはいかがでしょうか。
以下、参考にした記事になります。 探索的テストについて
ZabbixからTomcatコンテナを監視する
EatSmartではサーバ、サービスの監視にZabbixを利用しています。 利用するテンプレートは、サーバの監視には"Template OS Linux"を、Tomcatの監視には"Template App Generic Java JMX"と独自に拡張したものを利用しています。 これらの構成はデータセンターを利用していた頃から基本的に変わらないのですが、Zabbix Java GatewayでTomcatを監視するのに工夫が必要でした。
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コンテナを監視することができるようになりました。