2014年11月18日火曜日

NewsPicks × D3.js


NewsPicksの開発をしている板倉です。


NewsPicksではニュースを見る画面とは別に、
どの記事がどれくらい読まれているかという画面の開発を進めています。
直感的にわかる画面がほしいということで、 D3.jsを使って画面を開発することになりました。
D3.jsを使うにあたって勉強するつもりで何か作ろうと思い書いたのが今回のエントリーになります。

今回の開発環境

Mac OS X(10.10)
D3.js(3.4.13)

D3.jsについて

まずは、D3について少しだけ。
Githubの人気リポジトリに入っている人気のJavaScriptライブラリです。(2014/11 時点)


APIリファレンスを見ると、たくさんの機能があるのがわかります。

地図を書いてみよう

APIリファレンスを見ていて気になったのがGeography
ということで日本地図を書いてみました。




参考にしたサイト

地図上に何か表示してみよう


地図を描いただけだと面白くないので、地震のデータを使って地図上に表示してみました。
まずは、データの取得。
以下のサイトで日本の緯度経度、地震の大きさと期間を入力してデータをダウンロードしました。

USGS


ダウンロードしたデータを0.1秒ずつずらして描画してみました。
円をそのままにしておくと画面が円だらけになるので、描画して2秒後に消しています。

d3.csv('geo/eq.csv', function(d) {
    // データが降順だったので反転
    d = d.reverse();
    // 大きさ
    var rScale = d3.scale.linear().domain(d3.extent(d, function(e){
         return e.mag;
    })).range([3, 81]);
    // 深さ
    var colorScale = d3.scale.linear().domain(d3.extent(d, function(e){
         return e.depth;
    })).range(['yellow', 'red']);
    g.selectAll('circle')
         .data(d)
         .enter().append('circle')
         .attr('fill', function(d) { 
              return colorScale(d.depth);
         })
         .attr('fill-opacity', 0.5)
         .attr('stroke-width', '1')
         .attr('stroke', function(d) { 
              return colorScale(d.depth);
         })
         .attr('cx', function(d){
             return projection([Number(d.longitude), Number(d.latitude)])[0];
         })
         .attr('cy', function(d){
             return projection([Number(d.longitude), Number(d.latitude)])[1];
         })
         .transition().delay(function(d, i) { 
              return i * 100;
         })
        .attr('r', function(d) {
             return rScale(d.mag)
         })
        .each('start', function(d){
             dateText.text(df(new Date(d.time)));
             mag.text('M ' + d.mag);
             depth.text('Depth: ' + d.depth + ' km');
         })
        .transition().delay(function(d, i) {
             return (i * 100) + 2000
         })
        .each('end', function(d){
             d3.select(this).remove();
         });
})


実装したものはこちら

数字が並んでるデータを見るだけだと気付きにくいことも
図形で表現してみると新しい発見がありますね。

Uzabaseではデータの視覚化を進めるエンジニアを募集しております。
興味をお持ちいただいた方はWantedlyなどからご連絡ください!

CentOS7にLAMP環境を構築してWordpressをインストールする

インターン生の阿達です。

いつかはこのブログや、会社のHPも自分で作れたらいいなあと思っている
プログラミング歴1か月のぺーぺーです。

その野望の第一歩として与えられた課題が
CentOS7にLAMP環境を構築してWordpressをインストールする
でした。
この記事では勉強した内容の復習を兼ねて手順を丁寧に紹介したいと思います。



目次

【1】LAMP( Apache + MariaDB(Mysql) + PHP )をインストールする

 0,準備
 1,Apacheをインストールする
 2,MariaDBをインストールする
 3.PHPをインストール

【2】Wordpressをインストールする
 
 1,Wordpressをダウンロードする
 2,MariaDBにWordpress用のデータベースを作成する
 3,Wordpressのセットアップをする
 4,




【1】LAMP( Apache + MariaDB(Mysql) + PHP )をインストールする

0,準備


○ユーザーをルートに切り替える

sudo su
su


○SELinux無効化

vi /etc/sysconfig/selinux


下記を変更
SELINUX=enforcing
↓
SELINUX=disabled

・OSを再起動します

shutdown -r now


○firewalld設定・現在の設定の確認

firewall-cmd --list-all-zones


(略)
public (default, active)
interfaces: enp0s3 enp0s8
sources:
services: dhcpv6-client ssh
(略)
初期設定では publicゾーンに ssh のみ許可されています。


・追加で http と https を許可設定します

firewall-cmd --add-service=http --zone=public --permanent
firewall-cmd --add-service=https --zone=public --permanent


・設定を読込みます

firewall-cmd --reload



・設定を確認します

firewall-cmd --list-services --zone=public


(下記表示であればOK)
dhcpv6-client http https ssh



1,Apacheをインストールする

yum install httpd

・.Apacheの設定を行う

vi /etc/httpd/conf/httpd.conf



・ここでは必要最小限のサーバー名(今回はexample.com)だけ設定しておきます

ServerName example.com:80



・Apacheのデーモンを起動する

・ウェブサーバー(httpd)を起動します

systemctl start httpd.service



・ウェブサーバー(httpd)のデーモン(サービス)が起動しているか確認します

systemctl list-units |grep httpd


(下記表示であればOK)



httpd.service        \
    loaded acitive running The Apache HTTP Server




・次に、ウェブサーバー(httpd)デーモンがブート時に自動起動するように設定しておきます。

systemctl enable httpd.service
以下のように表示されてればOK
ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/multi-user.target.wants/httpd.service'


・ウェブサーバー(httpd)デーモンの登録状態を確認します。
systemctl list-unit-files |grep httpd
(下記表示であればOK)
httpd.service         enabled


・ブラウザから先程サーバー名で設定したアドレス(この場合はexample.com)にアクセスすると、
Apacheの「Red Hat Enterprise Linux Test Page」が表示されます。







2,MariaDBをインストールする


yum install mariadb-server mariadb



・MariaDBのデーモンを起動する

・mariadbデーモンが起動しているか確認します。

systemctl list-units |grep mariadb



・何も出力されないので、起動していない状態だとわかります。

・次に、mariadbデーモンの登録状態を確認します。

systemctl list-unit-files |grep mariadb

mariadb.service        disabled

disabledなので、再起動してもmariadbデーモンは起動しません。


・mariadb のデーモン(サービス)を起動します。

systemctl start mariadb.service



・mariadb のデーモン(サービス)が起動しているか確認します。

systemctl list-units |grep mariadb
(下記表示であればOK)
mariadb.service                \
   loaded active running MariaDB database server



・次に、mariadbデーモンの登録状態を確認します。

systemctl list-unit-files |grep mariadb

mariadb.service        disabled
disabledなので、再起動してもmariadbのデーモンは起動しません。


・次に、mariadbデーモンがブート時に自動起動するように設定しておきます。

systemctl enable mariadb.service
(下記表示であればOK)
ln -s '/usr/lib/systemd/system/mariadb.service' '/etc/systemd/system/multi-user.target.wants/mariadb.service'


・再度、mariadbデーモンの登録状態を確認します。

systemctl list-unit-files |grep mariadb
(下記表示であればOK)
mariadb.service        enabled
enabledなので、再起動してもmariadbデーモンは起動されます。


・MariaDBのセキュリティ設定をします。

/usr/bin/mysql_secure_installation



OTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user.  If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

Enter current password for root (enter for none): 【空enter】
OK, successfully used password, moving on...
Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.
Set root password? [Y/n] 【Y】
New password: 【パスワードを設定する、今回はhoge】
Re-enter new password: 【先程のパスワードを入力する、今回はhoge】
Password updated successfully!
Reloading privilege tables..
 ... Success!
By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.
Remove anonymous users? [Y/n] 【Y】
 ... Success!
Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.
Disallow root login remotely? [Y/n]【Y】
 ... Success!
By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.
Remove test database and access to it? [Y/n]【Y】
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!
Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.
Reload privilege tables now? [Y/n]【Y】
 ... Success!
Cleaning up...
All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.
Thanks for using MariaDB!


・ログインできるか試す。

mysql -h localhost -u root -p




【】で示した部分が必要な操作です。


Enter password:【先程設定したパスワードを入力する、今回はhoge】

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 4
Server version: 5.5.37-MariaDB MariaDB Server
 
Copyright (c) 2000, 2014, Oracle, Monty Program Ab and others.
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
MariaDB [(none)]>【 exit】

Bye

3,PHPをインストールする

・PHPをインストールする

yum install php-mysql php php-gd php-mbstring



・phpがインストールできたか、バージョンをチェックしてみます。

php --version

PHP 5.4.16 (cli) (built: Jun 10 2014 02:52:47)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies

・httpdサービス(Apache)を再起動する

systemctl restart httpd.service



・php動作確認用のファイルを作成する。

cd /var/www/html
# echo '' > index.php


・上記で作成したindex.phpへブラウザからアクセスする。
(今回はexample.com/index.php)

phpの画面が表示されます。



【2】Wordpressをインストールする

1,Wordpressをダウンロードする

・最新のWordpressをダウンロードする

cd /tmp
wget http://wordpress.org/latest.tar.gz


・ダウンロードしたWordpressを解凍する

tar -xvzf latest.tar.gz -C /var/www/html


2,MariaDBにWordpress用のデータベースを作成する

・MariaDBにrootでログインする

mysql -h localhost -u root -p


・データベースを作成する

今回testとhogehogeで示した部分はご自分で設定してください
CREATE USER test@localhost IDENTIFIED BY "hogehoge";
CREATE DATABASE test_blog;
GRANT ALL ON test_blog.* TO test@localhost;
FLUSH PRIVILEGES;
exit


3,Wordpressのセットアップをする


・localhostとWordpressを紐づける

/etc/hosts に下記を書き足す

127.0.0.1 wordpress


・wp-config.phpを生成する

cd /var/www/html/wordpress

cp wp-config-sample.php wp-config.php


・wp-config.phpでWordpressの設定をする

vi wp-config.php

【2】2,で作成したデータベースの情報を入力します
define('DB_NAME', 'test_blog');
define('DB_USER', 'test');
define('DB_PASSWORD', 'hogehoge');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
(中略)
$table_prefix = 'wp_';
define ('WPLANG', '');
define('WP_DEBUG', false);

・example.com/wordpressにアクセスして設定をする。
指示に従って設定していけばOKです。
以上になります。
私がインストールしたときには、SELinuxで詰まったりしていたので
基礎の基礎から書いてみました。
備忘録兼ねてですが、どなたかの参考になれば幸いです。

2014年11月5日水曜日

荒ぶるRedisとNewsPicks


NewsPicks の開発を担当している杉浦です。

NewsPicksはおかげさまでユーザ数が20万を突破しました。
サービスが順調に成長するということは大変にうれしいことなのですが、エンジニアとしては負荷との戦いになったりします。我々も例に漏れず日々、負荷との戦いを強いられています。


NewsPicksの機能面の特長として次の2つがあります。
・フォローしているユーザのPickが自分のタイムラインに集約される
・各カテゴリで話題になった記事を閲覧できる

これらの機能を高速に処理・実現するためにRedisを採用しているのですが、
ユーザ数の増加による負荷増加によって問題が発生するようになりました。

本記事では、
・ユーザ数が増える中でRedisにどのような問題が発生したか
・ソースコードを読みながら問題の原因を考える
・そして、どのような対応を行っているか
を共有したいと思います。

どのような問題が発生したか

主に2つの問題が発生しました。

ピーク時のレスポンスタイムが遅くなる

NewsPicksは朝の8時にその日の注目ニュースをPush通知で送信します。その直後が最大のピークタイムになり、平常時の10倍程度のリクエストを処理する必要があります。ピークタイムに画面表示のレスポンスタイムが悪化するという問題が発生しました。

夜間バッチ実行時にslaveとのレプリケーションが切れる

夜間にRedisに保持している古いタイムラインデータを削除するというバッチが実行されるのですが、バッチによる大量のデータ更新が発生しレプリケーションのタイムアウト値を越えてしまい、レプリケーションが切れるという障害が発生しました。


ソースコードを読みながら問題の原因を考える

なぜこのような問題が発生するようになったのでしょうか。
原因を知るにはRedisのアーキテクチャを理解する必要があります。そして、ソフトウェアのアーキテクチャを理解する近道はいつの時代もソースコードを読むことです。ということで、redis-2.8.17をダウンロード して読んでみました。


ファイル数が少なくシンプルなソフトウェアということがわかります。
理解をしやすくするために、おおまかな機能ごとに整理します。


メインプログラムの redis.c から ae.c を読み進めると、RedisはI/O戦略としてイベントループモデルを採用していることがわかります。
イベントの発火時に各コマンドハンドラーが呼ばれて処理が進められています。

イベントループの詳細については TheC10kProblem を参照してください。
2006年の古い記事ですがよくまとまっていて、今読んでも本質的な部分は変わっていません。

イベントループモデルのメリットは以下が挙げられます。
  • 処理ごとにスレッド/プロセスを用意しないので使用メモリ量を抑えられる
  • スレッド/プロセス切り替えのコンテキストスイッチのオーバヘッドが少ない
  • ノンブロッキングI/Oを合わせて採用することで、I/O待ち時間に他の処理が行われる
逆に、デメリットは以下のようなものがあります。
  • ループの中に遅い処理が入ると、後続の処理が遅れる
  • (Redisの場合は) 1スレッドでループ処理を行うので、マルチコアのサーバの場合に全てのCPUリソースを使い切らない

Redisはデータを全てメモリ中に持つため、高速に処理を行いますが、
それでもRedisの利用度が上がってくると、Redisの能力を越える多量の読み書きが実行される時がやってきます。
そして、Redisはイベントループモデルの特性から、閾値を越えると一気に処理遅延が発生します。

NewsPicksが遭遇した問題はまさにこの事象で、あるタイミングを境に突然問題が発生するようになりました。
システムのアーキテクチャを考える際に負荷に合わせてタイミング良くスケールアウトしていくことができる仕組みを作ることが大切です。
なお、Nginx、Node.jsも同様にイベントループを採用しているので、同じ考え方を適用できると思います。

NewsPicksではどのような対応を行っているか

現在、データのシャーディングと、垂直・水平に処理リクエストを分散をするように対応を進めています。
タイムラインデータについては、一定のユーザ数毎にデータ保存するサーバを分離しています。一般的にユーザパーティショニングによる垂直分散と呼ばれる手法です。
ランキングデータについては読み取りリクエストをslaveに分散させるといった水平分散の対応を行っています。


NewsPicksでは一緒に負荷と戦ってくれるエンジニアを募集しています!
サービスの成長を肌で感じられるやりがいのある仕事です(笑
興味をお持ちいただいた方はWantedlyなどからご連絡ください!