DevDevデブ!!

プログラミングのこととか書きます。多分。。。

ログ集計バッチとしてのaws athena

タイトルそのまんまなんですが、最近aws athenaをログ集計バッチ代わりにつかっています。

athenaって何?

小宇宙(コスモ)を燃やすやつでも、サイコソルジャーでもない。

雑に説明すると、S3に配置したログファイルについてスキーマを定義して、SQLを実行できるawsのマネージド・サービス、という感じ

裏側ではprestoが動いてるらしい。

詳しくはググって

バッチ的に使ってみてどうか

本来データアナリスト的なポジションの人が、インタラクティブにクエリを実行するようなユースケースを想定していることもあって、バッチ的に使うのは本来のユースケースとは外れると思う。

というのも、athenaのクエリは発行と結果の出力が非同期的であるため、バッチからクエリ発行して結果を受け取って〜という処理は結構めんどくさい

(awsコンソール上からクエリを実行した場合は、クエリが完了次第、画面下部に結果が表示される)

boto3だとクエリ発行は以下のような感じになる

        athena = boto3.client('athena')
        response = athena.start_query_execution(
            QueryString="クエリ文字列",
            QueryExecutionContext={'Database': 'データベース名'},
            ResultConfiguration={'OutputLocation': 's3://結果出力先'}
        )

start_query_executionはすぐに完了するのだけど、戻り値はSQLの結果ではなく、「クエリの発行が完了した」ことを示すjson(boto3の場合はpythonの辞書型)

SQLの結果は上記コードのOutputLocationに指定したs3のパス配下に"クエリ発行ID".csvというファイル名で配置される。

"クエリ発行ID"というのはstart_query_executionの戻り値に含まれるQueryExecutionIdのこと

クエリの実行が完了したかどうかは、s3にファイルが出力されたかポーリングするか、get_query_executionでクエリの実行ステータスを確認する必要がある。

なお、出力ファイル名が"クエリ発行ID".csvになるというのは、ドキュメントに記載が見当たらなかったので、仕様というわけではなく、現状の法則にすぎないことに留意する必要がある。

何故prestoじゃなくてathenaを利用したのか

コストが安い

EMRでprestoを使うのと比較して、athenaのほうが安くなるため(これはユースケースによる)

athenaの料金はクエリ実行において、S3からスキャンしたデータ量に応じて課金される。5ドル/1TB

筆者の環境ではfluentd->kinesis stream->kinesis firehose->s3という流れでログをs3に配置し、1時間ごとにathenaを起動してログ集計を実行しているのだが、大体1クエリ700MB程度なので、EMRを使うより確実に安く抑えることができる。

無論、今後のログ流量の増加によっては料金が逆転することもあるだろう。

hadoop周辺に詳しい人がいない

EMR自体はawsコンソールからぽちぽちやるだけで立ち上がるのだが、障害発生時に対応できる人がいない。(hadoop周辺についてマジで無知であるため、エラーメッセージの意味が読み取れない)

筆者が新卒のころに、ちょうど「ビッグデータ」が持て囃されたのだが、どうせ自分で触る機会はないだろうとhadoopノータッチできたツケが回ってきた感じ。。。。

毎時バッチにEMR立ち上げるのはビミョい

微妙です。立ち上げっぱなしも無駄だし、毎時起動するにもオーバーヘッドがでかい気がするし。

TIPS

スキーマ定義の方法

スキーマAWS Glue Data Catalogに定義する。(言葉の使い方があってるのかいまいち分かってない)

awsコンソールから手動でポチポチ入力して定義することもできるのだが、AWS Glueのクローラを利用するのがてっとり早い

Glueクローラにs3の指定のパス配下をクロールさせると、そこに存在するログファイルをスキャンして、自動的にスキーマを抽出してくれる。

一度クローラで自動抽出させてから、細かい部分を手動で修正するとよいだろう

(なお、先頭行がラベルになってないcsvなど、ログからスキーマを抽出できないフォーマットの場合、恐らく自動抽出できないと思われる。jsonとparquetはできた)

パーティションの設定

スキーマパーティションを設定していると、クエリ実行時に指定したパーティションだけスキャンするので、データスキャン量を抑えることができ、クエリの実行時間も短縮することができる。

実はGlueのクローラはs3のパス構造から自動的にパーティションを設定してくれる機能がある。

たとえば、s3://sample.bucket/2018/02/14/20/sample_log.gz という感じにログファイルが配置されている場合、「2018」、「02」、「14」、「20」の部分を自動的にパーティションとして認識してくれる。

新規にs3://sample.bucket/2018/02/14/21/sample_log.gz ができた後、再びクロールを実行すると、新規にできたパスについてもパーティションを追加してくれる。

しかし、Glueクローラは指定したパス配下について、毎回全スキャンをかけてしまうため、ログファイルの増加に比例してクロール所要時間が増加するという問題がある。

残念だが、現状では自分でパーティション追加のDDLを発行するのが良いと思われる。

トラブルシューティング

athenaのクエリ実行は結構コケる

SQLの文法エラーはないにも関わらず、エラーになって失敗することがある。

エラーメッセージには「HIVE_CURSOR_ERROR」という文字列が含まれているので、背後で動いているhive、prestoのエラーなのだろう。

単純にリトライすると成功する。

上記を回避したい場合

自前でEMRを運用するしかないと思われる。

憶測だけど、athenaはでかいEMRのクラスタを複数のユーザが叩く形になっているので、どうしてもリソースが逼迫する状況があって、エラーが発生するのだと考えてる。

幸いなことにGlue Data Catalogはhiveメタストアとして指定することができるので、Glue Data Catalogに定義したスキーマは、prestoやhiveからも参照することができる。

docs.aws.amazon.com

athenaで動作するクエリはprestoでも動くので、athenaからprestoに切り替えるのはそれほど難しくはないと思われる。

SIGMA@circus tokyo 2nd Aniversery day3行ってきた

重要な点だけ先に書くと、circus tokyoで聴くドラムンベースはやばい

シ、シグマ。。。。?

実は、来日が発表された時点でSIGMA知らんかった。

正確には、以前にliquicityかどっかのレーベルコンピレーションで一度聴いて名前は知ってるはずだけど、その時点では特にピンとくるものが無かった感じ

ヒット曲の「Nobody To Love」もそんな好きなタイプのドラムンベースではないんだけど、ドラムンベースのイベントはとりあえず行くスタンスなので行ってきた。(理由は後述する)

(本人の曲そんな好きじゃなくても、DJプレイの選曲はストライクど真ん中ってことは往々にしてあるしね)

youtu.be

あの、SIGMAさん、ドラムンベースを。。。。。。

で、蓋を開けてみると、SIGMAさん、EDMを織り交ぜたプレイをなさるのですね。。。。

EDMは別に嫌いってわけじゃないんだよね。フューチャーハウスなんかは好きで、この前もagehaにオリバー・ヘルデンス見にいったし。

(ていうかEDMってくくりがデカすぎるんだよ)

しかしですね、完全に「ドラムンベースDJ/Producer」っていう認識だったから、どうしても肩透かし感はあるよね。

始まって最初はフロア満員状態でちょっとしんどかったんだけど、45分経過時点でちらほら人がいなくなってたしなー。(Netskyのときは最後まで満員だったはず)

でも最後のニューアルバムからの曲はナイスだったね。あれは早く音源欲しい。

その他のメンツ

最高としかいいようがない。どっちかっつーとこっちのほうに期待してたところあるし。オープンから行かなかったことが悔やまれる。

俺はDJプレイの上手い下手はよく分からないので、選曲観点で書くんだけど、FMYのかけるliquidfunkとGradateのかけるneurofunkはcircusのサウンドで聴くとヤバかった。

(さすがに繋ぎミスって音がワチャワチャしてたりすると分かる)

ドラムンベースって、キックとベースがいい音で鳴らないと、なんというか、密度が薄く聞こえると思うんだけど、この2ジャンルは特にそれが顕著だと思ってて、iPhoneにイヤホン直刺しで聴くと、ただの地味な曲に感じたりするんだよね。

(iTunesで聴くより、Localizeで聴いたときのほうが良く聞こえたりするんだけど、あれはなんなんだろう?)

ドラムンベースのイベントはとりあえず行く。why?

上に書いたのがそれで、クラブ・ミュージックは良い音で聴かないと良さが分からないケースが多々あり、かつ日本においては都内でさえドラムンベースのイベントは少ない(techno/houseあたりと比べると)。

となると、日本在住だとドラムンベースをいい音で聞ける機会って大分限られてくるわけだ。

じゃあ行けるだけ行くしかないってなるわけですね。

(金稼いでクラブ並の設備の家を建てるとか、ワンチャン渡英するとか、他に選択肢が無いわけではないが)

自分はこういう観点なんで、スペシャルゲストのプレイが終わったら帰っちゃう人たち、もったいないなーと思ってます。

そんなわけでオーガナイザー様様だと思いました

感謝

今回は箱側の主催だけど、ほとんどのイベントは普通に昼の仕事を抱えてるオーガナイザーの人が余暇を使って企画してるわけで、ありがたい話です。

なんやかんや言いましたが、circusも3rd anivでもドラムンの人招聘して欲しいですねー。

zapはgoroutine safeなのか??

READMEに明記されてないので調べてた

(というかzapはドキュメント薄い。。。ソース読まないと分からん)

(基本goroutine safeと明記されてないものはgoroutine safeでは無いんだけど)

github.com

事の発端

zapのリポジトリのREADMEにはgoroutine-safeの記載は無かったので、zapの中でgoroutine-safeにするためのapiとかサポートされてないのかと思ってググったら、以下のissueを見つけた。

github.com

対応するPR

github.com

zapは実際にログを書き込む際に、io.Writerをwrapしたinterfaceであるzap.WriteSyncerというinterface使うのだけど、それを更にLock対応のLockWriteSyncerというstructにwrapする形になる。

以下は現在のmasterブランチのlogger.goからの抜粋

type lockedWriteSyncer struct {
    sync.Mutex
    ws WriteSyncer
}

// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In
// particular, *os.Files must be locked before use.
func Lock(ws WriteSyncer) WriteSyncer {
    if _, ok := ws.(*lockedWriteSyncer); ok {
        // no need to layer on another lock
        return ws
    }
    return &lockedWriteSyncer{ws: ws}
}

func (s *lockedWriteSyncer) Write(bs []byte) (int, error) {
    s.Lock()
    n, err := s.ws.Write(bs)
    s.Unlock()
    return n, err
}

lockedWriteSyncerのWriteはWriteSyncerのwriteを叩く前にsync.Mutexでlockをかけるので、goroutine safeになっている。

lockedWriteSyncerもWriteSyncerのinterfaceを満たしているので、zapの他のAPIには影響がないというわけだ。

よって、WriteSyncerをzap.Lockに渡してwrapしてあげると、goroutine safeになる。

zapの標準プリセットはgoroutine safe?

zapには予め用意された設定(以後、便宜的に標準プリセットと呼ぶ)でロガーのインスタンスを作成する関数がいくつか用意されています。

zap.NewDevelopmentとか、zap.NewProductionとか

これらの関数で作成されるロガーは、goroutine safeなのかどうかソースを読んで確認した。

結論からいうと、goroutine safe

上述のzap.Lockの呼び出し箇所を確認したところ、Config構造体のopenSinksという関数の中で、引数に指定された複数のファイルパスからWriteSyncerを返すOpenという関数が叩かれており、その中で叩かれているCombineWriteSyncersという関数の中で、最後にzap.LockでWriteSyncerをwrapしたものをreturnしている。

なので、標準プリセットがどうというより、Config構造体のBuild関数を叩いて作成したLoggerについてはgoroutine safeとなるようだ。

ところでgodocをちゃんと読むと気になる記載が

以下、zapのlogger.goのLogger構造体の抜粋

// A Logger provides fast, leveled, structured logging. All methods are safe
// for concurrent use.
//
// The Logger is designed for contexts in which every microsecond and every
// allocation matters, so its API intentionally favors performance and type
// safety over brevity. For most applications, the SugaredLogger strikes a
// better balance between performance and ergonomics.
type Logger struct {
    core zapcore.Core

    development bool
    name        string
    errorOutput zapcore.WriteSyncer

    addCaller bool
    addStack  zapcore.LevelEnabler

    callerSkip int
}

「All methods are safe for concurrent use.」

はい??

InfoとかWarnとかのログ出力メソッドについては、zapcore.Coreの実装次第だと思うんだけども。。。?

このgodocの記載は信用していいのか。。。。?

そもそもio.Writer.writeがgoroutine safeだったりするケースありません?

zapでは(他のloggingライブラリもそうだと思うけど)最終的にio.Writerのwriteを叩くことになるので、io.Writerの実装次第では内部でlockをとっており、writeの呼び出し側ではlockしてあげる必要なかったりするのではと思って調べた。

(逆にいうと、io.Writerの実装依存なので、loggingライブラリ側ではwriteを叩く前にlockするのが妥当と言える)

とりあえず、os.Fileに関しては、writeの内部でlockがかかってるように見える

以下はfd_unix.goからの抜粋

// Write implements io.Writer.
func (fd *FD) Write(p []byte) (int, error) {
    if err := fd.writeLock(); err != nil {
        return 0, err
    }
    defer fd.writeUnlock()
    if err := fd.pd.prepareWrite(fd.isFile); err != nil {
        return 0, err
    }
    var nn int
    for {
        max := len(p)
        if fd.IsStream && max-nn > maxRW {
            max = nn + maxRW
        }
        n, err := syscall.Write(fd.Sysfd, p[nn:max])
        if n > 0 {
            nn += n
        }
        if nn == len(p) {
            return nn, err
        }
        if err == syscall.EAGAIN && fd.pd.pollable() {
            if err = fd.pd.waitWrite(fd.isFile); err == nil {
                continue
            }
        }
        if err != nil {
            return nn, err
        }
        if n == 0 {
            return nn, io.ErrUnexpectedEOF
        }
    }
}

先頭行の方で何やらlockをかけているのが分かる。

しかし、この実装はgo1.9からなんだよね。

go1.8ではfd_unix.goのパッケージ階層自体違うし、上記のようなlockはかかっていない。

そして、go1.9のリリースノートを確認したところ、以下の記載がある。

The os package now uses the internal runtime poller for file I/O. This reduces the number of threads required for read/write operations on pipes, and it eliminates races when one goroutine closes a file while another is using the file for I/O.

goroutine safeにしたとは言ってないような。。。???

(結果的にgoroutine safeになってるかもしれないけど)

これは非goroutine safeとして扱ったほうが良さそう

echoのcontent-type上書きに注意

またgoのechoの話です。

javascirptを実行して欲しかったんや。。。

scriptタグを吐き出すechoのエンドポイントを書いてたんだけど、ブラウザからアクセスしてもjavascriptとして実行されず、プレーンテキストとしてブラウザ上に表示されます。

まあ原因は簡単で、content-typeがtext/plainだったから。

c.String(http.statusOk, script)

ほんじゃあcontent-typeを適切なものに変更しましょうって話になりますね。

なので以下のようにしてみたわけです。

c.Request().Header.Set(echo.HeaderContentType, echo.MIMEApplicationJavaScript)
c.String(http.statusOk, script)

再トライしてもtext/plainのまま(;´Д`)

echoの書き込みメソッドはContent-Typeを上書きする

はい。してました。(普通に考えりゃそりゃそうだって話なんだけど)

func (c *context) String(code int, s string) (err error) {
    return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
}

上記はechoのcontext構造体のStringメソッド

c.Blogの真ん中の引数がContent-Typeなわけです。

JSONメソッドとかHTMLメソッドも中でc.Blobを叩いてて、適切なContent-Typeを設定している。

なので、最初から出力内容に適切なメソッドを使うか、Blobを直接叩いて自分で適切なContent-Typeを指定してあげる必要があると。

今回出力しようとしていたjavascriptはscriptタグを含んだものだったので、HTMLメソッドで出力するとちゃんと動いてくれた。

echoでgraceful restart

ちょろっとググっても見つからなかったので書いておく。

分かってると思うけど、golangのWAFのechoです。

go-server-starterを使う

github.com

使い方は下記の記事を参考にした。

shogo82148.github.io

net/httpをそのまま使うのであれば、上記の記事の通りに使えば完了なのだが、echoの場合、go-server-starter/listenerを使って得られるnet.Listenerインターフェイスの(実装)インスタンスをどこに差し込むのかという話になる。

まあ、echoのgodoc読めばすぐ分かるんだけどもね

type Echo struct {
    Server           *http.Server
    TLSServer        *http.Server
    Listener         net.Listener
    TLSListener      net.Listener
    AutoTLSManager   autocert.Manager
    DisableHTTP2     bool
    Debug            bool
    HideBanner       bool
    HTTPErrorHandler HTTPErrorHandler
    Binder           Binder
    Validator        Validator
    Renderer         Renderer
    // Mutex            sync.RWMutex
    Logger Logger
    // contains filtered or unexported fields
}

と、上記のようにEcho構造体のメンバ、Listenerは外部から差し替えられるので、そこに差し込めばよい

e.Listener = l

という感じに

(もちろんeはEcho構造体のインスタンスで、lはnet.Listenerのインスタンス)

あとは、公式cookbookのgraceful shutdownのページにある通りにすればよい

echo.labstack.com

注意点として、net.Listenerにすでにポート番号を指定しているので、echoサーバを開始するときにはポート番号を指定しないようにすること。(上記のリンク中では指定している)

e.start("")

のように空文字を指定する。

あとは、ビルドしてできたバイナリをstart_serverコマンド経由で起動する。

start_serverコマンドは本家のperl版のServer::Starterと同じなので適当にググって

簡単な例だと以下のような感じ(appはechoで作ったサーバのバイナリ名として)

start_server --port="任意のポート番号" --pid-file=/tmp/app.pid --status-file=/tmp/app.status -- app

pid-fileとstatus-fileのpathも任意です。

そして、supervisorとかでstart_serverプロセスを管理する。

graceful restartさせたい場合、start_serverのプロセスにHUPシグナルを発行するか、以下のようにする。

start_server --pid-file=/tmp/app.pid --status-file=/tmp/app.status --restart

pid-fileとstatus-fileのパスは、プロセスを立ち上げたときに指定したのと同じものを指定する。

facebook/graceとかじゃダメなん?

echoの公式でもfacebook/graceを使っていたので、これでいんじゃね?と思ったのだけど、facebook/graceだとプロセスIDが変わってしまうのだとか?

supervisorで管理したかったので、それは避けたかった

Server::Starterの場合、supervisorで直接管理するのはstart_serverプロセスで、greceful restartしたい対象のプロセスはstart_serverプロセスが管理するような形になる。

start_server管理下のプロセスはgraceful restartでプロセスIDが変わる(というか新規に立ち上げる)のだけど、start_serverプロセスのプロセスID自体は変わらないので、そのままsupervisorで管理できる。

EAT,DRINK,BREAKCOREに行ってきた

行ってきました。

twipla.jp

ブレイクコアって何さ?

わからん

ブレイクコア - Wikipedia

wikipediaの説明を読んでもよくわからん

しかし、un!teで8to7さんがプレイしているのを聴いておもしろいと思ったので、行ってみた

un1te.club

ワタクシ、アーメンブレイクは好きですからね。

ちなみにアーメンブレイクっていうのは「ズンズンチ、ズクズクンチ♪」ていうドラムパターンのアレです。語源はググれ。

そこはアーメンブレイクの洪水だった

アーメンブレイクが高速で流れるわ流れるわ

前日にオーガナイザのOthermoon a.k.a. ブレイクコア邪神さん(以下邪神さん)の主催するOthermanRecordsのサイトで少し予習したっきりで、代表的なアーティストも曲も知らず、出演者がプレイされてる曲も知らないんだけど、高速アーメンブレイクは気持ちいい

そんななか、前半戦最後の出演者、humangasさんがおもしろいことを言い出す。

お前らはアーメンに対する感謝が足りない

?????

ほんとにやりやがったwwww

この間、ベース・ミュージックをプレイされました(ダブステップ?)

俺はベース・ミュージック好きなので、これはこれで。。。。と思ってましたけどw

最終的にイェーガーショットで乾杯してアーメン解禁の運びに

そして襲いくるBPM260の暴力

最後の出演者、DJ KuraraさんがBPM260の曲をプレイするわけですが、260てw

普段聴いてるのがBPM174のドラムンベースで、ドラムンベースの場合、曲のBPMの半分のテンポでノるので、BPM260とかどうしたらいいんじゃ。。。。。(;´Д`)

しかしこの超高速BPMはなかなか中毒性がある:)

副作用として、一時間も聴いてると箱の外に出て聴く普通の音楽がずいぶんとゆっくりに感じるという問題があるけど

ブレイクコアに興味を持った人は、次回un!teに行こう

un1te.club

邪神さんがスペシャルゲストだそうです。

俺は行きます。(用事が無ければ)

じゃあの

ultra japan 2017 day 3行ってきたよ

半分愚痴だよ

やった 晴れた

木曜の時点で台風の5日間進路予測がかなり不穏な動きを見せており、三日目に直撃するかと思われたのだけど、17深夜から18日の早朝にかけて通り過ぎていってくれたので、day 3はすごく良い天気だった。

(かなり暑かったけど)

今年はサマソニも両日晴れたし、野外イベント参加時の天気運いいかもしれない。

エントランスの案内ちゃんとせーよ

今回はiflyerで電子チケットを買ったんだけど、まず東側の入場ゲートに行ったら「電子チケットは西側に行ってください」と言われ、西側ゲートにいって適当な列に並んで入場しようとしたら、「iflyerの受け付けは左端のみ」と言われた。

あのね、それ立て看板かなんかに出しとけよ

ていうか公式サイトのinfoかなんかで事前アナウンスしろ

(アナウンス済みならすまん。でも会場に分かるように表示しとけ)

なんで同じ料金払ってチケット買ってるのにたらい回しくらわないといかんのかっつー話ですよ。

そんでな、iflyerのチケットページに「本人確認のために購入時のクレカチェックもするからもってこい」って書いてあったけど、そんなん全然やらないのな。

やるんならちゃんとせーよ、色々とな

ライブはよかった

今回のお目当てはDJ Aki、pendulum、underworld

全部ライブステージ

ライブステージって、会場map公開されるまではてっきりzepp divercity使うのかなーと思ってたんだけど、レジスタンスステージの斜め後方なんだよね。

で、レジスタンスステージと音を相殺しないようにするためなのか、音圧が抑えめ?

大声出さなくても隣の人と会話できるレベル

これはちょっと残念だった。

DJ AKi + YUUKi MC

つい最近からドラムンベースを聞きにクラブに通い始めた人なので、はじめてYUUKi MCのパフォーマンスを見たんだけど、本場のMCっぽくてかっこよかった。

(HOSPITALのイベントとかでやってるMCのソレ)

シークレットゲストのACEもね

選曲もneurofunk多めで、最近eatbrainをよく聴いてる自分としては楽しかった。

pendulum

ようやくライブを観ることができた。

大学時代(多分2008年くらい)に初めて聴いてかっけえええええええと思い、卒業後に上京して「pendulum来日しないかなー」と思ってたら活動休止してしまったpendulum

(俺が好きになるアーティストこんなパターンほんと多くてな、、、school food punishmentとか。。。)

やっぱかっけえ

コンスタントに活動して欲しいなー。今度はワンマンで来て欲しい

(やらしい話だけど、knife partyのほうが儲かるのかな?w)

underworld

俺のダンス・ミュージックの原体験は、映画トレインスポッティングで流れる「born slippy nuxx」です。

映画をレンタルで見たのが多分小4か小5で、一週間レンタルで借りて四回は見たはず。

born slippy以外にも劇中の挿入曲がかっこいいんだよね。

(レントンとダイアンのベッドシーン目当てじゃないよ?ほんとだよ??)

そんなこんなで思い入れのあるunderworld、実は去年のサマソニ、武道館ワンマンとでそんなに期間を空けずに三回目。

まあ何回見てもいいっすわ。

underworldのライブはライト演出が凝ってていい。

そして帰るときに残念な気持ちになる

去年もだったんだけどさ、ゴミが散乱してるんだわ。

ほんとに理解に苦しむんだけど、目と鼻の先にゴミ箱あるのに何故ゴミを放置していくのかな?

マナーをおかーちゃんのお腹の中に忘れて生まれてきちゃった??

でもマナーやら道徳やらは生来獲得してるものではないんだよな。。。じゃあまともな教育を受けていない?

ドラムンベースが日本でもメジャーになって、日本でもHOSPITAL in the PARKみたいなイベント(LET IT ROLLでもRAMPAGEでもいいけど)やらないかなーって妄想するんだけど、メインストリームになるってことは、上記のような連中が増えるってことでもあるんだよね。

そう考えると、マイナーなままのほうがいいのかなーって思ってしまう

(⇡お前何様やねんって話だけどな)

そしてpendulumのライブ中に柵からジャンプして俺の足をおもっくそ踏んで謝罪も無しに去っていったやつ

お前はうんこ踏め。動物のじゃなくて人糞を!!!!!!

余談

ultraは飲料メーカーなどが協賛しているためか、ドリンク類の値段がそんな高くないのがうれしい

特にキリンの500mlペットボトル(メッツコーラとかソルティライチとか)が200円なのはマジでお財布に優しかった