kohei_blog.py

都内で働くエンジニアのブログです( ✌︎'ω')✌︎ 技術記事から時事ネタまで

【Newspicksより】若者の〇〇離れを経済的な面だけで語らないでほしい

少し前からよく耳にする「若者の〇〇離れ」

今回の記事では、根源を経済的な面で語る記事があり、

その記事に対して違和感を覚えました。

記事

newspicks.com

内容

  • 「若者の〇〇離れ」に関して、アンケートを元にまとめた記事
    • 「若者の〇〇離れ」の対象と感じているものは、車、新聞、読書、結婚、テレビetc...
    • 若者が「本当はやりたいもの」と感じているものは、旅行、読書、車、恋愛
    • 「若者の〇〇離れ」の原因は、収入の低下、ネットの発達、物欲の低下などがあげられた。

感想・意見

  • 車離れは地方に住んでいる方のみだと思う。

  • かつては社会的な地位への憧れに対する消費が多かったが、今は自分の価値観にあった消費をするようになった。

  • 経済的な面だけで「若者の〇〇離れ」を語らないでほしい。

TCPコネクションを切断するstubを作成して、Nginx configの検証を行う

f:id:kohei_iwamura:20180217195023j:plain

状況

  • Nginxでserverにproxyして通信する際、 一定確率でNginxとserverでTCPコネクションが確立されず、NginxがHTTP_statuscode502を返す事象が発生していた。

今回行ったこと

  • TCPコネクションを切断するstubを作成して再現させ、対処法を考えた

検証

stub_server

nginx config

# 自身のserverとproxyするため、private_ipを設定
# max_fails=0で、Nginx側からはコネクションを張ったままにする
upstream stub {
    server 172.31.27.174:8000 max_fails=0;
    server 172.31.27.174:8000 max_fails=0;
    server 172.31.27.174:8000 max_fails=0;
}

server {
    listen  80;
    server_name 172.31.27.174;
    charset     utf-8;

    # proxyの設定
    location / {
      proxy_pass http://stub/;
      # HTTP_statu_code500の場合のみ、次のserverとproxyするように設定
      proxy_next_upstream http_500;
    }
}

stub program

  • stubの処理を3種類にわけ、randomで処理を行うようにした
    • HTTP_response(status_code=200)を返す
    • HTTP_response(status_code=500)を返す
    • TCPコネクション切断。
# -*- coding:utf-8 -*-
import socket
import random

STUB_PORT = 8000

print "waiting now"
# TCPで通信を行うためのsocketを作成する
serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# hostsとportを設定してbindする
serversock.bind(('', STUB_PORT))
# 接続の待ち受けをする(キューの最大数を指定)
serversock.listen(100)
while True:
    # #接続された時にデータを格納する
    soc, addr = serversock.accept()
    # クライアントからのリクエストを受信する(50000byteまで。適当に設定した)
    soc.recv(50000)
    method = random.choice(["200","500","error"])
    if method == "200":
        # HTTP_responseを追加し、返している
        soc.send("HTTP/1.1 200 OK\r\nCache-Control: no-cache, private\r\nContent-Length: 0\r\nDate: Fri, 17 Feb 2018 10:21:21 GMT\r\n\r\n")
    elif method == "500":
        # HTTP_responseを追加し、返している
        soc.send("HTTP/1.1 500 InternalServerError\r\nCache-Control: no-cache, private\r\nContent-Length: 0\r\nDate: Fri, 17 Feb 2018 10:21:21 GMT\r\n\r\n")
    else:
        # コネクションを切断する
        soc.close()

response集計用script

import requests

status_code200 = 0
status_code500 = 0
error_response = 0
for i in range(100):
    r = requests.get('http://54.250.242.76/stub/')

    if r.status_code == 200:
        status_code200 += 1
    elif r.status_code == 500:
        status_code500 += 1
    else:
        error_response += 1


print "status_code200 : " + str(status_code200)
print "status_code500 : " + str(status_code500)
print "error_response : " + str(error_response)

集計結果

status_code200 : 48 # <--そのまま返すので、clientに返される割合は多くなる
status_code500 : 4 # <-- 一定回数retryするので、clientに返される割合は少なくなる
error_response : 48 # <--そのままresponseするので、clientに返される割合は多くなる

↓POINT↓

改修方法(nginx)

  • proxy_next_upstream に errorを追加する
    • TCPコネクションでerrorが起こってもretryしコネクションを確立させようとする
upstream stub {
    server 172.31.27.174:8000 max_fails=0;
    server 172.31.27.174:8000 max_fails=0;
    server 172.31.27.174:8000 max_fails=0;
}

server {
    listen  80;
    server_name 172.31.27.174;
    charset     utf-8;

    location / {
      proxy_pass http://stub/;
      proxy_next_upstream http_500 error;
    }
}

改修後の集計結果

status_code200 : 69 # <--そのまま返すので、clientに返される割合は多くなる
status_code500 : 16 # <-- 一定回数retryするので、clientに返される割合は少なくなる
error_response : 15 # <-- 一定回数retryするので、clientに返される割合は少なくなる

【NewsPicksより】Magic Leap One について

f:id:kohei_iwamura:20180217175213j:plain

今、テクノロジーの世界では「VR」「AR」が話題となっていますが、

「MR」市場でも注目の企業があるので、紹介します。

前提知識

  • VR(仮想現実): 完全に現実世界とはかけ離れた仮想空間
  • AR(拡張現実):現実世界が主体であり、そこに仮想の物質を複合させていく
  • MR(複合現実):仮想世界が主体であり、そこに現実の物質を複合させていく

記事

newspicks.com

内容

  • テクノロジー界のバズワードとして、「VR」「AR」「MR」があげられる。
  • MRといえば、Microsoftの「hololens」が有名であるが、先日、かなりの資金を調達しながらもベールに隠されていた「Magic Leap」が、新製品を発表した
  • 製品名「Magic Leap One」の特徴
    • 空間把握機能:現実世界の物質を把握することができるため、その物質を考慮したバーチャルの世界を作ることができる
    • デジタルイメージの固定化:一度見たイメージは再び見ても変わることはなく存在する
    • 空間音響:現実世界のユーザーが見た方向に合わせて音声を調節できるので、まるで現実世界から音が聞こえてくるような感覚にさせることができる
    • 多様なインタフェース:ユーザーのあらゆる行為がバーチャル空間に影響するので、より現実世界のように体感できる
    • 高速処理能力:反応が現実世界に劣らないスピードでイメージを生成できる

感想・意見

  • 本格的に、「ディスプレイ」が不要となる日も近いと本気で思っています。
  • 仮想空間にモニタを置いて、どこでも作業ができるようになってほしいです。

【NewsPicksより】Netflixのマーケティング戦略

f:id:kohei_iwamura:20180210143528j:plain

記事

newspicks.com

内容

  • Netflixマーケティングを広告エージェンシーに頼らずほとんど自社で行っている
  • マーケティング予算の大半はプログラマティック広告に使われている
    • アドエクスチェンジへの投資額は全ブランドの中でも10指に入る
  • 優秀な社員を数多く集める同社内でプログラマティック広告を運用していく方針
    • エージェンシーも携わっているのだが、任される仕事はキャンペーン実施のみとなっている。
  • 様々なユーザーによってコンテンツを分けられる技術力を持っている同社にとって、社内運用はさほど難しいわけではない
  • netflixにとってのライバルは動画ストリーミングサービスに止まらず、映画スタジオもライバルとして頭に入っている

感想・意見

  • 臨場感が強みであった映画館だが、これからは場所を選ばず五感のリアリティを体験できるテクノロジーが普及してくるため、netflixが存在しなくても映画館は窮地に立たされると思う。
  • 「お金2.0」にも書いてあったが、今後、企業にとっては、資金面での資本だけでなく、データも資本となっていくだろう。昨今の資本主義では資金だけが重要と見なされるが、データを持っていれば資金にも変換可能であり、戦略面で使用したり、様々な用途で重宝される重要な「資本」である。

【requiredが効かない?】FormSetのhas_changed()の深堀り

f:id:kohei_iwamura:20180204185721j:plain

DjangoのFormsetを使っている時、

Formの各fieldにrequiredオプションを設定しているのに、

空文字列を入力してもFormset.is_valid()=>Trueになってしまった。

(詳しくは下記「問題の状況」)

そこで、条件を変更した検証も交え、ボトルネックの調査をした。

今回使用するFormSetとForm

from django import forms
from django.forms.formsets import formset_factory

class PeriodForm(forms.Form):
    start = forms.DateTimeField(
        required=True,
    )
    end = forms.DateTimeField(
        required=True,
    )

PeriodFormSet = formset_factory(
    PeriodForm)

● 問題の状況

startとendどちらも空文字 → FormSetを生成

data = {
        'form-TOTAL_FORMS': '1',
        'form-INITIAL_FORMS': '0',
        'form-MIN_NUM_FORMS': '',
        'form-MAX_NUM_FORMS': '',
        'form-0-start': '', # <-- this date is missing but required
        'form-0-end': '', # <-- this date is missing but required
        }
>>> formset = PeriodFormSet(data)
>>> formset.is_valid()
True # <-- False expected

start,end共に空文字列の場合、requiredオプションを設定していても

is_valid()=Trueになってしまう。

○ 検証:どちらかに値が入っている場合

startのみdatetime、endは空文字 → FormSetを生成

from datetime import datetime
data = data.update({'form-0-start':datetime.now()})  # <-- end is missing but required
>>> formset = PeriodFormSet(data)
>>> formset.is_valid()
False # <-- False expected

想定通りis_valid()=Falseになる。

問題のボトルネック

今回のボトルネックとなった部分(Django1.9.xのsourceより)

● 問題の状況の場合

 # formset[0] : start="",end=""...
>>> formset[0].empty_permitted
True
>>> formset[0].has_changed()
False
if self.empty_permitted and not self.has_changed():
    return # <-- called in this situation
_clean_fields(self) # <-- not called

has_changed() = Falseのため、 Form._clean_fields()が呼ばれず、fieldごとのvalidationを行っていなかった

○ どちらかに値が入っている場合

 # formset[0] : start=datetime.now(),end=""...
>>> formset[0].empty_permitted
True
>>> formset[0].has_changed()
True

>>> form.empty_permitted
False
>>> form.has_changed()
False

has_changed() = Trueになり、 Form._clean_fields()が呼ばれ、fieldごとのvalidationも行う

なぜ Formset.has_changed() の値が変わるのか

Formset.has_changed()(djangoのsourceより)

return: any(form.has_changed() for form in self)
(FormSetの中の各Formに対してhas_changed()を呼び出し、boolで返している)

Form.has_changed()(djangoのsourceより)

return: bool(changed_data)

Form.changed_data()(djangoのsourceより)

return: data
# 各Fieldのchanged_data(initial, data)を呼び出し、Trueの場合はdataに値を追加し、dataを返り値としている
※ initial : Fieldの初期値。特に定義されていない場合はNone
※ data : widgetから値を取り出している(今回は ""(空文字列))

Field.has_changed(initial, data)(djangoのsourceより)

注目箇所

以下の3行でinitial,dataに対して、Noneだった場合は""(空文字)に変換する処理を加える

initial_value = initial if initial is not None else ''
data_value = data if data is not None else ''
return initial_value != data_value # <-- "" != ""
# => False
return: initial_value != data_value (=> False)

まとめ

今回のようにstart="", end=""の場合は、

Field.has_changed(initial, data)
"""
return: initial_value != data_value => False
"""
↓
Form.changed_data()
"""
return: data => []
"""
↓
Form.has_changed()
"""
return: Form.changed_data() => False
"""
↓
Formset.has_changed[f:id:kohei_iwamura:20180204185721j:plain]<figure class="figure-image figure-image-fotolife" title="【requiredが効かない?】FormSetのhas_changed()の深堀り">[f:id:kohei_iwamura:20180204185721j:plain]<figcaption>【requiredが効かない?】FormSetのhas_changed()の深堀り</figcaption></figure>()
"""
return: any(form.has_changed() for form in self) => False
"""

となり、各fieldごとのvalidationが行われず、

Formset.is_valid()
=> True

となる

nginXでretry処理を行う

f:id:kohei_iwamura:20180204104709j:plainnginXでproxyしたサーバーからのレスポンスがstatus_code:5xxだった場合

retryさせる設定を組み込みました。

nginX自体ではretryのオプションはないので、設定を応用して

結果retryさせている設定を入れる必要があります。


前提として

(nginx.conf)

location /image/ {
    proxy_pass http://bucket_name.s3-website-ap-northeast-1.amazonaws.com/image/;
 }

nginXへrequesturlが https://image/../.. のようなリクエストがきた時、

S3とproxyしてimageファイルを取得する設定を入れてあります。

https://image/profile/taro/hoge.png
→ s3://bucket_name/image/profile/taro/hoge.png のファイルを取得する

nginX設定の改修

status_code 500が返された場合、nginXでretryを行うようにする

nginx.confの設定

  • 変更前
location /image/ {
    proxy_pass http://bucket_name.s3-website-ap-northeast-1.amazonaws.com/image/;
 }
  • 変更後
upstream bucket_name {
    server s3-website-ap-northeast-1.amazonaws.com;
    server s3-website-ap-northeast-1.amazonaws.com;
    server s3-website-ap-northeast-1.amazonaws.com;
}

    location /image/ {
        proxy_pass http://bucket_name/image/;
        proxy_next_upstream http_500;
    }
}

設定内容詳細

upstream

  • サーバーのグループを定義している
  • nginXをHTTPロードバランサのように使用でき、グループ内のサーバーにアクセスされる

proxy_next_upstream

  • 指定のレスポンスが返された場合、upstream内で設定されている次のサーバーへproxyする
    • 今回はhttp_500と設定することにより、status_code:500で返された場合に実行される

● ポイント

同じサーバーをupstreamに並べることにより、実質retryさせている状況を作る

  • 実質、status_code:500で返された場合、3回retryを実行するようにしている

その他

upstreamにはオプション設定がいくつかあります。

今回は、retryの設定に関わるオプションを紹介します。

オプション default 説明
max_fails 1 fail_timeout の間、指定した回数の失敗が発生すると fail_timeout の間サーバーを無効にする (リクエストを割り振らない)
fail_timeout 10 指定秒数の間、接続の失敗やタイムアウトしたサーバーは一時的に無効状態にされる
backup backup でないサーバーが全て無効になった場合にのみ有効になる

今回取り入れた設定

upstream bucket_name {
    server s3-website-ap-northeast-1.amazonaws.com max_fail=0;
    server s3-website-ap-northeast-1.amazonaws.com max_fail=0;
    server s3-website-ap-northeast-1.amazonaws.com max_fail=0;
}

上記の設定を取り入れない場合の問題点

  • 1度でもエラーが返るとproxyが切断され、サーバーが無効となってしまう
    • 複数回のリクエストがnginXに送られた場合でも、fail_timeoutにより10秒間proxyが無効となっているので、status_code:502が返されます。

上記の設定を取り入れた利点

  • max_fail=0とすることで、サーバーからエラーが返ってきてもproxyを切断しないようにし、サーバーを無効状態にしないようにしました。

【NewsPicksより】表示速度をあげる手段としてのCDN

記事

newspicks.com

内容

  • スマホアプリやwebサイトのエンドユーザはページの読み込みに3秒以上かかると53%が離脱してしまう
  • 最近になってページの読み込み速度がかなり短縮されたサイトが登場している
    • そのサイトの多くで利用されているのがCDNという技術である

CDNについて

ネットワークの通信の仕組み

一般的にエンドユーザにコンテンツが提供される過程では、

以下の回線経路で通信が行われる

エンドユーザ → ISP* → IX → ISP* → 配信元サーバー
ISP:インターネットサービスプロバイダの略。インターネットへ接続させるサービスを提供する事業者。(例) インターネットイニシアティブ、NTTコミュニケーションズ、KDDI、ソフトバンク
IX:インターネットエクスチェンジの略。ISP間の接続ポイント

問題点

CDNのコンテンツを早く返す仕組み

  • ネットワークの各所にサーバーを設け、サーバー間での通信を行い、速度を改善する
  • エンドユーザに一番近いネットワークにキャッシュサーバを設け、image,css,jsを早く返す

CDNの問題点

  • 各所にネットワークを配置するので、管理コストがかかる
  • コンテンツ配信元が内容を変更した場合、キャッシュサーバへの反映が遅い(数日)

今回のCDNの活用法

  • IXの部分でキャッシュサーバーを配置し、問題となっていたIXを挟むことなくコンテンツを返すようにした
    • CDMで使われるサーバーも上流に位置し、キャッシュの反映を早めた
    • IXに集中するので、台数も減らすことができ、管理コストも抑えられた

感想・意見

  • こういった表示速度もUXの1つとして考えられると思う。
  • ms単位の読込速度の違いでも離脱率は大きく変わるので、ほんのわずかな読込速度の改修でも大きく効果があると思った。ただ、経験上ネットワーク部分の改修はアプリケーションよりも検証含めた改修コストが多くかかるので、費用対効果を吟味することが重要