alucepsの日記

ソフトウェアエンジニアをしているおっさんが生きている中でメモしたいと思ったことを記録します。

DMM mobileを使い始めて6ヶ月が経過したのでレビューを書いてみる

今年の1月に SIM フリーの iPhone6s を購入して、それまで使っていた auiPhone5s とお別れした。それと同時に DMM mobile の通話可能な SIM を手に入れて大手キャリアともお別れをした。その際 MNP は利用せずに電話番号も新しくした。

使ってみてわかったこと

4ヶ月使ってみてわかった事は、ざっくりいうと au の時とほとんど変わらない。インターネットを利用している時の体感速度もまったく変わらない。強いて言うなら多少は通信料を気にするようになったということぐらい。

料金はどのぐらい変わったか

結論からいうと DMM mobile を使う前後で比べると料金今は4分の1となった。

auの場合

まず au を使っていた時はいくらだったのかというとこんな感じ。当時の料金の事よく覚えていないので今の料金を au の ウェブサイトから引用。

項目 料金
基本使用料 934
インターネット接続サービス 300
LTEフラット 5,700
合計 6,934

だいたい月々7,000円ぐらいは支払っていた事になる。LTE フラットは 7GB まで利用可能な点を忘れないようにしたい。実家にはインターネット回線がないため、正月とか実家に1週間ぐらい帰省していると(暇なので)無駄にインターネットを使うのですぐに上限に達してしまっていた。

DMM mobile の場合

DMM mobile の場合は料金体系は明快だ。

項目 料金
基本使用料 1,500
合計 1,500

これは3GBでの契約。仮に au の時と同じように 7GB で契約すると基本使用料は2,560円。不安な点は帰省した時に 3GB で間に合うのか、というところ。しかし、DMM mobile には余った通信量を1ヶ月分を繰越できるという標準サービスがある。普段は家でも会社でも無線 LAN を利用しているため実はそれほど通信量を消費していない。今の時点で毎月 3GB ほど繰越している。ということはあまり心配しなくてもいいのかもしれない。

結論

大手キャリアや料金に不満があるなら DMM mobile などの格安 SIM にすると幸せになれると思う。

CakePHPでバッチ処理を作成した時にメモリ不足でハマった

CakePHPで数十万件のデータを処理するバッチを作成した。そのバッチを実行したところ、いつのまにかバッチが終了していた。ずいぶんと終わるのが早いなと思ったら以下エラーで終了していた。

PHP Fatal error: Allowed memory size of xxx

原因を調査しながら色々と試したので順を追って記載する。

メモリはどのように食いつぶされていくのか

ループの適当なところに以下コードを埋めて、どのようにメモリが食いつぶされていくのかを観察した。とりあえず1000回ループするごとにログを出力した。

$this->log(array(
  'count'  => $count,
  'memory' => memory_get_usage(true),
), LOG_DEBUG);

すると次のようにメモリが食いつぶされていることがわかった。

回数 メモリ使用量(byte)
0 128,974,848
1000 133,431,296 4,456,448
2000 137,887,744 4,456,448
3000 142,344,192 4,456,448
4000 146,800,640 4,456,448

数十万件のデータを一度に取得した場合、その時点で 100MB 以上を消費していた。さらに前述のように 10MB 近いメモリが消費されていく。単純に計算しても終了する頃には数GBのメモリが…おそろしい。

対策1 unsetを試す

消費したメモリを解放したい。まずは unset を試してみた。

$data = $this->Model->query($some_query);
foreach ($data as $v) {
  ...
  unset($v);
}

結論としては、メモリ消費の様子に変化なし。

対策2 foreach -> for に変更

foreach$value にデータをコピーすることでメモリ消費量が増えるのかもしれないと予想して以下のコードを試した。

$data = $this->Model->query($some_query);
for ($i = 0; $i < count($data); $i++) {
  ...
  unset($data[$i]);
}

結論としては、これもメモリ消費の様子に変化なし。

対策3 数十万件のデータを分割して取得する

そもそも最初にドーンとメモリを消費することが防げたら最終的なメモリの消費量も抑えられると予想して、数十万件のデータを分割して取得してみた。

$count = $this->Model->query($count_query);
$limit = 1000;
$loop  = ceil($count / $limit);

for ($i = 0; $i < $loop; $i++) {
    $offset = $limit * $i;
    $some_query = '... limit {$limit} offset {$offset};';
    $data = $this->Model->query($some_query);

    $count = count($data);
    for ($j = 0; $j < $count; $j++) {
        unset($data[$j]);
    }
}

これは想定通り。ただ、回数が増えるごとに 5MB 近く増えてしまう。

回数 メモリ消費量(byte)
0 3,145,728
1000 7,864,320 4,718,592
2000 12,845,056 4,980,736
3000 17,825,792 4,980,736
4000 22,544,384 4,718,592

対策4 Model->query() のキャッシュを無効にする

これは盲点だったのだが、CakePHPModel->query() はデフォルトでクエリをキャッシュする仕組みになっているようだ。

CakePHP1.2 モデル | query

query() は、モデルの呼び出しとは本質的に分離した機能で、 $Model->cachequeries の状態に従いません。query を呼び出すにあたりキャッシングを無効にするには、query($query, $cachequeries = false) というように第2引数に false を設定します

ということで Model->query() をの第2引数に false を指定してみた。

回数 メモリ消費量(byte)
0 2,883,584
1000 3,145,728 262,144
2000 3,145,728 0
3000 3,145,728 0
4000 3,145,728 0

素晴らしい。あの謎のメモリ消費量の増加はクエリキャッシュだったのか。

以上、Model->query() を使うときにメモリが気になったら第2引数に false を指定しよう、というお話。

Electron をいじってみる

準備

Electronのウェブサイトに記載されている「Get started with Electron」の通りに進める。

git clone https://github.com/electron/electron-quick-start
cd electron-quick-start/
npm install && npm start

npm がなかったので nodebrew でインストールする。

brew install node

もう一度 npm 周りを実行。

npm install
npm start

npm start するとこんなウインドウが表示された。

f:id:aluceps:20160511141435p:plain

ドキュメントを読む

チュートリアルに書かれている内容を読んでみる。

Main Process

package.json に記述されている "main": "main.js" がメインプロセスということらしい。

main.js

上から順番に見ていく。

'use strict';

const electron = require('electron');
// Module to control application life.
const app = electron.app;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;

これはいわゆるおまじないの部分。

function createWindow () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadURL('file://' + __dirname + '/index.html');

  // Open the DevTools.
  mainWindow.webContents.openDevTools();

  // Emitted when the window is closed.
  mainWindow.on('closed', function() {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
}
  • new BrowserWindow でブラウザウインドウを指定のサイズで作成する
  • loadURL で指定した index.html を読み込む
  • openDevToolsChromium の開発ツールを開いた状態にする
  • ウインドウを閉じた時に mainWindownull にする
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', function () {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow();
  }
});
  • ready 状態になったら先ほどの createWindow が呼ばれる
  • ウインドウを閉じるとアプリ終了とする
  • activate 状態になったら createWindow が呼ばれる

OSX の場合はウインドウを閉じてもアプリを終了しない。Dock のアイコンがクリックされるなどして新たにウインドウが作成された場合はもう一度 createWindow を呼ぶようだ。

main.js を変更してみる

早速 main.js を変更してみる。とりあえず Google の Web を表示させる。あと開発ツールはいらないので表示しない。

--- main.js.org
+++ main.js
@@ -15,10 +15,7 @@
   mainWindow = new BrowserWindow({width: 800, height: 600});

   // and load the index.html of the app.
-  mainWindow.loadURL('file://' + __dirname + '/index.html');
-
-  // Open the DevTools.
-  mainWindow.webContents.openDevTools();
+  mainWindow.loadURL('http://www.google.com');

npm start カチャカチャカチャ…ッターン!

f:id:aluceps:20160511141310p:plain

ということで簡単に Google アプリができた。簡単に OSX アプリが作成できたが、任意の OS コマンドが実行できるのでセキュリティ面は考慮する必要がある。Electron のセキュリティについては mobage developers blog で次のように書かれている。

通常のブラウザ(Chromiumを含む)であれば、window.openでfileスキームを指定しても開くことができない制約が入っていますが、Electronでは、このwindow.openをoverride.jsによってoverrideしていてブラウザとは異なる挙動をする仕様になっています。

override.js の恩恵で簡単にデスクトップアプリケーションが作成できる反面、セキュリティ面の考慮を怠ると簡単に悪意のコードが実行できてしまうので、その点は気をつけて Electron と戯れたい。

CentOS6.7にMySQL5.7をインストールする

リポジトリの取得

下記URLの Red Hat Enterprise Linux 6 / Oracle Linux 6 (Architecture Independent), RPM Package にある Download からリポジトリのURLを取得する。 MySQL :: Download MySQL Yum Repository

事前準備

rpm -qa | grep mysql
yum remove mysql*

リポジトリインストール

yum install http://repo.mysql.com//mysql57-community-release-el6-7.noarch.rpm

mysqlインストール

yum install mysql-community-client mysql-community-server --enablerepo=mysql57-community-dmr -y

mysqlの起動

service mysqld start

初回設定

mysql_secure_installation

ここでパスワードを求められる。パスワードは以下にある。

cat /var/log/mysqld.log
[Note] A temporary password is generated for root@localhost: 'password'

VagrantとAnsibleでLAMP環境を構築する

構築する予定の環境

name version
CentOS 6.7
Apache 2.2.15
mysql 5.7.11
php 7.0.3

Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
  # https://github.com/NREL/vagrant-boxes
  config.vm.box = "centos67"
  config.vm.box_url = "https://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.7-x86_64-v20151108.box"

  config.vm.define "web" do |node|
    node.vm.hostname = "play"
    node.vm.network :private_network, ip:"192.168.33.100"
    node.vm.synced_folder "data", "/vagrant"
  end

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioning/playbook.yml"
  end
end

playbook.yml

- hosts: all
  become: yes
  tasks:
    - name: system update
      yum: name=* state=latest

    - name: install repository
      yum: name={{item}}
      with_items:
        - http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
        - http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm

    - name: replace repos.d/*.repo
      replace: dest=/etc/yum.repos.d/{{item}} regexp="enabled *= *1" replace="enabled=0"
      with_items:
        - epel.repo
        - remi.repo
        - mysql-community.repo

    - name: install database
      yum: name={{item}} state=present enablerepo=mysql57-community-dmr
      with_items:
        - mysql-community-client
        - mysql-community-server

    - name: install php
      yum: name={{item}} state=present enablerepo=remi-php70
      with_items:
        - php70-php
        - php70-php-mbstring
        - php70-php-gd
        - php70-php-mysqlnd

    - name: symbolic link
      file: src=/usr/bin/php70 dest=/usr/bin/php state=link

    - name: iptables disabled
      service: name={{item}} state=stopped
      with_items:
        - iptables
        - ip6tables

    - name: httpd and mysql start
      service: name={{item}} state=started enabled=yes
      with_items:
        - httpd
        - mysqld

NeoBundleを導入する

NeoBundleの解説記事をいくつかみたけど、どれも情報が古い?みたいでうまく導入できなかった。だけど、GitHubのページにインストール方法とか書いてあって、しかも超絶楽だったのでメモ。

NeoBundleのインストール

curl https://raw.githubusercontent.com/Shougo/neobundle.vim/master/bin/install.sh > install.sh
sh ./install.sh

.vimrcの設定

" Note: Skip initialization for vim-tiny or vim-small.
if 0 | endif

if has('vim_starting')
  if &compatible
    set nocompatible    " Be iMproved
  endif

  " Required:
  set runtimepath+=~/.vim/bundle/neobundle.vim/
endif

" Required:
call neobundle#begin(expand('~/.vim/bundle/'))

" Let NeoBundle manage NeoBundle
" Required:
NeoBundleFetch 'Shougo/neobundle.vim'

" My Bundles here:
" Refer to |:NeoBundle-examples|.
" Note: You don't set neobundle setting in .gvimrc!

call neobundle#end()

" Required:
filetype plugin indent on

" If there are uninstalled bundles found on startup,
" this will conveniently prompt you to install them.
NeoBundleCheck

powerlineのインストール

" My Bundles here:
" Refer to |:NeoBundle-examples|.
" Note: You don't set neobundle setting in .gvimrc!
NeoBundle 'alpaca-tc/alpaca_powertabline'
NeoBundle 'Lokaltog/powerline', { 'rtp' : 'powerline/bindings/vim' }
NeoBundle 'Lokaltog/powerline-fontpatcher'

で、保存して再度.vimrcを開くと、インストールするかどうか聞かれるので素直にyしておく。インストール自体はこれで完了。

powerlineのための設定

filetype plugin indent onの下あたりに書いておく。

" for Powerline
set laststatus=2
let g:Powerline_symbols = 'fancy'
set t_Co=256

参考にした記事