çŽåŸ ã¡æéã
ãããã誰ããæ¬çªç°å¢ã§ã³ãŒãããããã¡ã€ãªã³ã°ããã¿ã¹ã¯ã«çŽé¢ããŠããŸãã Facebookã®xhprofã¯ãããããŸããã£ãŠããŸããããšãã°ã1/1000ã¯ãšãªã®ãããã¡ã€ã«ãäœæããçŸæç¹ã§ç»åã確èªããŸããåãªãªãŒã¹ã®åŸã補åãå®è¡ãããããªãªãŒã¹åã¯ããè¯ããããé«éã§ããããšè¡šç€ºãããŸããå±¥æŽããŒã¿ããªããäœã蚌æã§ããŸãããã§ãããïŒ
å°ãåãŸã§ãã³ãŒãã®åé¡ã®ããéšåãæžãçŽããããã©ãŒãã³ã¹ãå€§å¹ ã«åäžããããšãæåŸ ããŠããŸããããŠããããã¹ããäœæããè² è·ãã¹ããè¡ããŸããããã©ã€ãè² è·ã®äžã§ã³ãŒãã¯ã©ã®ããã«åäœããŸããïŒçµå±ã®ãšãããè² è·ãã¹ãã§ã¯å¿ ãããå®éã®ããŒã¿ã衚瀺ããããšã¯éããªããããå±éåŸã¯ãã³ãŒããããã£ãŒãããã¯ããã°ããååŸããå¿ èŠããããŸããããŒã¿ãåéããå ŽåããªãªãŒã¹åŸãæŠéç°å¢ã®ç¶æ³ãç解ããã®ã«10ã15åããå¿ èŠãããŸããã
çŽ åŸ ã¡æéãïŒ1ïŒãããã€ãïŒ2ïŒããŒã«ããã¯
ã¹ã¿ãã¯
ç§ãã¡ã®ã¿ã¹ã¯ã§ã¯ãåæ±ç¶ã®ClickHouseããŒã¿ããŒã¹ïŒç¥ããŠkxïŒã䜿çšããŸããããã®éžæã®äž»ãªçç±ã¯ãé床ãç·åœ¢ã¹ã±ãŒã©ããªãã£ãããŒã¿å§çž®ãããã³ãããããã¯ãªãã§ãããçŸåšãããã¯ãããžã§ã¯ãã®äž»èŠãªæ ç¹ã®1ã€ã§ãã
æåã®ããŒãžã§ã³ã§ã¯ããã¥ãŒã«ã¡ãã»ãŒãžãæžã蟌ã¿ããã§ã«æ¶è²»è ã«ãã£ãŠClickHouseã«ã¡ãã»ãŒãžãæžã蟌ã¿ãŸãããé 延ã¯3ã4æéã«éããŸããïŒã¯ããClickHouseã¯1ã€ãã€æ¿å ¥ããã®ãé ãã§ãèšé²ïŒãæéãçµã¡ãäœããå€ããå¿ èŠããããŸããããã®ãããªé 延ã§éç¥ã«å¿çããæå³ã¯ãããŸããã§ããã次ã«ããã¥ãŒããå¿ èŠãªæ°ã®ã¡ãã»ãŒãžãéžæããŠããããããŒã¿ããŒã¹ã«éä¿¡ãããããããã¥ãŒã§åŠçæžã¿ãšããŠããŒã¯ããã¯ã©ãŠã³ã³ãã³ããäœæããŸãããããã§åé¡ãçºçãããŸã§ãæåã®æ°ãæã¯ãã¹ãŠåé¡ãããŸããã§ãããã€ãã³ããå€ãããŠãéè€ããŒã¿ãããŒã¿ããŒã¹ã«è¡šç€ºããå§ãããã¥ãŒãæå³ããç®çã§äœ¿çšãããªããªãïŒããŒã¿ããŒã¹ã«ãªããŸããïŒãcrownã³ãã³ããClickHouseãžã®æžã蟌ã¿ã«å¯Ÿå¿ããªããªããŸããããã®éã«ãããã«æ°åã®ããŒãã«ããããžã§ã¯ãã«è¿œå ãããkxã§ãããã§æžã蟌ãå¿ èŠããããŸãããåŠçé床ãäœäžããŸããã解決çã¯å¯èœãªéãã·ã³ãã«ã§è¿ éã§ãããããã«ãããredisã®ãªã¹ãã䜿çšããŠã³ãŒããäœæããããã«ãªããŸãããã¢ã€ãã¢ã¯ããã§ãïŒç§ãã¡ã¯ãªã¹ãã®æåŸã«ã¡ãã»ãŒãžãæžããŸããCrownã³ãã³ãã䜿çšããŠãããã¯ãäœæãããã¥ãŒã«éä¿¡ããŸãã次ã«ãã³ã³ã·ã¥ãŒããŒã¯ãã¥ãŒã解æããäžé£ã®ã¡ãã»ãŒãžãkxã«æžã蟌ã¿ãŸãã
æã ã¯æã£ãŠããïŒClickHouseãRedisã®ãã¥ãŒïŒä»»æã®- RabbitMQã®ãã«ãã«ãbeanstalkdã...ïŒ
Redisãšãªã¹ã
ããæãŸã§ãRedisã¯ãã£ãã·ã¥ãšããŠäœ¿çšãããŠããŸããããããã¯å€ããã€ã€ãããŸããããŒã¹ã«ã¯å·šå€§ãªæ©èœããããç§ãã¡ã®ã¿ã¹ã¯ã§ã¯ãrpushãlrangeãltrimã®3ã€ã®ã³ãã³ãã®ã¿ãå¿ èŠã§ãã rpushã³ãã³ãã䜿çšããŠããªã¹ãã®æåŸã«ããŒã¿ãæžã蟌ã¿ãŸããCrownã³ãã³ãã§ãlrangeã䜿çšããŠããŒã¿ãèªã¿åãããã¥ãŒã«éä¿¡ããŸãããã¥ãŒã«éä¿¡ã§ããå Žåã¯ãltrimã䜿çšããŠéžæããããŒã¿ãåé€ããå¿ èŠããããŸãã çè«ããå®è·µãžãç°¡åãªãªã¹ããäœæããŸãããã 3ã€ã®ã¡ãã»ãŒãžã®ãªã¹ãããããŸããããå°ãè¿œå ããŸããã... æ°ããã¡ãã»ãŒãžããªã¹ãã®æåŸã«è¿œå ãããŸããlrangeã³ãã³ãã䜿çšããŠãããããéžæããŸãïŒ= 5ã¡ãã»ãŒãžãšããŸãïŒã
次ã«ãããã¯ããã¥ãŒã«éä¿¡ããŸãã次ã«ããã®ãã³ãã«ãå床éä¿¡ããªãããã«ãRedisããåé€ããå¿ èŠããããŸãã
ã¢ã«ãŽãªãºã ããããŸããå®è£ ã«åãââæãããŸãããã
å®è£
ClickHouseããŒãã«ããå§ããŸããããç§ã¯ããŸãæ°ã«ããããã¹ãŠãStringã¿ã€ãã§å®çŸ©ããŸããã
create table profile_logs
(
hostname String, // ,
project String, //
version String, //
userId Nullable(String),
sessionId Nullable(String),
requestId String, //
requestIp String, // ip
eventName String, //
target String, // URL
latency Float32, // (latency=endTime - beginTime)
memoryPeak Int32,
date Date,
created DateTime
)
engine = MergeTree(date, (date, project, eventName), 8192);
ã€ãã³ãã¯æ¬¡ã®ããã«ãªããŸãã
{
"hostname": "debian-fsn1-2",
"project": "habr",
"version": "7.19.1",
"userId": null,
"sessionId": "Vv6ahLm0ZMrpOIMCZeJKEU0CTukTGM3bz0XVrM70",
"requestId": "9c73b19b973ca460",
"requestIp": "46.229.168.146",
"eventName": "app:init",
"target": "/",
"latency": 0.01384348869323730,
"memoryPeak": 2097152,
"date": "2020-07-13",
"created": "2020-07-13 13:59:02"
}
æ§é ãå®çŸ©ãããŠããŸããåŸ ã¡æéãèšç®ããã«ã¯ãæéãå¿ èŠã§ãããã€ã¯ãã¿ã€ã é¢æ°ã䜿çšããŠç¹å®ããŸãã
$beginTime = microtime(true);
//
$latency = microtime(true) - $beginTime;
å®è£ ãç°¡çŽ åããããã«ãlaravelãã¬ãŒã ã¯ãŒã¯ãšlaravel-entryã©ã€ãã©ãªã䜿çšããŸããã¢ãã«ãè¿œå ããŸãïŒããŒãã«profile_logsïŒïŒ
class ProfileLog extends \Bavix\Entry\Models\Entry
{
protected $fillable = [
'hostname',
'project',
'version',
'userId',
'sessionId',
'requestId',
'requestIp',
'eventName',
'target',
'latency',
'memoryPeak',
'date',
'created',
];
protected $casts = [
'date' => 'date:Y-m-d',
'created' => 'datetime:Y-m-d H:i:s',
];
}
Redisã«ã¡ãã»ãŒãžãæžã蟌ãtick ã¡ãœããïŒç§ã¯ProfileLogServiceãµãŒãã¹ãäœæããŸããïŒãæžããŠã¿ãŸããããçŸåšã®æå»ïŒbeginTimeïŒãååŸããããã$ currentTimeå€æ°ã«æžã蟌ã¿ãŸãã
$currentTime = \microtime(true);
ã€ãã³ãã®ãã£ãã¯ãåããŠåŒã³åºãããå Žåã¯ãããããã£ãã¯é åã«æžã蟌ã¿ãã¡ãœãããçµäºããŸãã
if (empty($this->ticks[$eventName])) {
$this->ticks[$eventName] = $currentTime;
return;
}
ãã£ãã¯ãå床åŒã³åºãããå Žåã¯ãrpushã¡ãœããã䜿çšããŠRedisã«ã¡ãã»ãŒãžãæžã蟌ã¿ãŸãã
$tickTime = $this->ticks[$eventName];
unset($this->ticks[$eventName]);
Redis::rpush('events:profile_logs', \json_encode([
'hostname' => \gethostname(),
'project' => 'habr',
'version' => \app()->version(),
'userId' => Auth::id(),
'sessionId' => \session()->getId(),
'requestId' => \bin2hex(\random_bytes(8)),
'requestIp' => \request()->getClientIp(),
'eventName' => $eventName,
'target' => \request()->getRequestUri(),
'latency' => $currentTime - $tickTime,
'memoryPeak' => \memory_get_usage(true),
'date' => $tickTime,
'created' => $tickTime,
]));
$ this-> ticks å€æ°ã¯éçã§ã¯ãããŸããããµãŒãã¹ãã·ã³ã°ã«ãã³ãšããŠç»é²ããå¿ èŠããããŸãã
$this->app->singleton(ProfileLogService::class);
ããããµã€ãºïŒ$ batchSizeïŒã¯æ§æå¯èœã§ããå°ããå€ïŒããšãã°ã10,000ã¢ã€ãã ïŒãæå®ããããšããå§ãããŸããåé¡ãçºçããå ŽåïŒããšãã°ãClickHouseã䜿çšã§ããªãå ŽåïŒããã¥ãŒã¯å€±æãå§ããããŒã¿ããããã°ããå¿ èŠããããŸãã
ã¯ã©ãŠã³ã³ãã³ããæžããŠã¿ãŸãããïŒ
$batchSize = 10000;
$key = 'events:profile_logs'
do {
$bulkData = Redis::lrange($key, 0, \max($batchSize - 1, 0));
$count = \count($bulkData);
if ($count) {
// json, decode
foreach ($bulkData as $itemKey => $itemValue) {
$bulkData[$itemKey] = \json_decode($itemValue, true);
}
// ch
\dispatch(new BulkWriter($bulkData));
// redis
Redis::ltrim($key, $count, -1);
}
} while ($count >= $batchSize);
ClickHouseã«ããã«ããŒã¿ãæžã蟌ãããšãã§ããŸãããåé¡ã¯kronorãã·ã³ã°ã«ã¹ã¬ããã¢ãŒãã§åäœãããšããäºå®ã«ãããŸãããããã£ãŠãéã®æ¹æ³ã§é²ã¿ãŸããã³ãã³ãã䜿çšããŠãããã¯ãäœæããClickHouseã§ã®åŸç¶ã®ãã«ãã¹ã¬ããèšé²ã®ããã«ãã¥ãŒã«éä¿¡ããŸããæ¶è²»è ã®æ°ã調æŽããããšãã§ããŸã-ããã¯ã¡ãã»ãŒãžã®éä¿¡ãã¹ããŒãã¢ããããŸãã
ã³ã³ã·ã¥ãŒããŒã®äœæã«ç§»ããŸãããã
class BulkWriter implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $bulkData;
public function __construct(array $bulkData)
{
$this->bulkData = $bulkData;
}
public function handle(): void
{
ProfileLog::insert($this->bulkData);
}
}
}
ãããã£ãŠãããã¯ã®åœ¢æããã¥ãŒãžã®éä¿¡ãããã³ã³ã³ã·ã¥ãŒããŒãéçºãããŸãããããã¡ã€ãªã³ã°ãéå§ã§ããŸãã
app(ProfileLogService::class)->tick('post::paginate');
$posts = Post::query()->paginate();
$response = view('posts', \compact('posts'));
app(ProfileLogService::class)->tick('post::paginate');
return $response;
ãã¹ãŠãæ£ããè¡ãããŠããå ŽåãããŒã¿ã¯Redisã«ããã¯ãã§ããCrownã³ãã³ããæ··ä¹±ãããŠããã¯ããã¥ãŒã«éä¿¡ãããšãã³ã³ã·ã¥ãŒããŒã¯ããããããŒã¿ããŒã¹ã«æ¿å ¥ããŸãã
ããŒã¿ããŒã¹å ã®ããŒã¿ãã°ã©ããäœæã§ããŸãã
ã°ã©ãã¡ã
ããã§ã¯ããã®èšäºã®éèŠãªèŠçŽ ã§ããããŒã¿ã®ã°ã©ãã£ã«ã«ãªè¡šç€ºã«ç§»ããŸããããgrafanaãã€ã³ã¹ããŒã«ããå¿ èŠããããŸããdebainã®ãããªã¢ã»ã³ããªã®ã€ã³ã¹ããŒã«ããã»ã¹ãã¹ãããããŸããããããã¥ã¡ã³ããžã®ãªã³ã¯ã䜿çšã§ããŸããéåžžãã€ã³ã¹ããŒã«æé ã¯ãapt installgrafanaã«èŠçŽãããŸãã
ArchLinuxã§ã¯ãã€ã³ã¹ããŒã«ã¯æ¬¡ã®ããã«ãªããŸãã
yaourt -S grafana
sudo systemctl start grafana
ãµãŒãã¹ãéå§ããŸãããURLïŒhttpïŒ// localhostïŒ3000
次ã«ãClickHouseããŒã¿ãœãŒã¹ãã©ã°ã€ã³ãã€ã³ã¹ããŒã«ããå¿ èŠããããŸãã
sudo grafana-cli plugins install vertamedia-clickhouse-datasource
grafana 7+ãã€ã³ã¹ããŒã«ããå ŽåãClickHouseã¯æ©èœããŸãããæ§æãå€æŽããå¿ èŠããããŸãã
sudo vi /etc/grafana.ini
次ã®è¡ãèŠã€ããŸãããã
;allow_loading_unsigned_plugins =
ããã«çœ®ãæããŸãããïŒ
allow_loading_unsigned_plugins=vertamedia-clickhouse-datasource
ãµãŒãã¹ãä¿åããŠåèµ·åããŸãããïŒ
sudo systemctl restart grafana
å®äºãããã§ãgrafanaã«ç§»åã§ããŸãã
ãã°ã€ã³ïŒadmin /ãã¹ã¯ãŒãïŒããã©ã«ãã§ã¯adminã
æ¿èªãæåããããã®ã¢ãã¯ãªãã¯ããŸããéãããããã¢ãããŠã£ã³ããŠã§ã[ããŒã¿ãœãŒã¹]ãéžæããClickHouseæ¥ç¶ãè¿œå ããŸãã
æ§ækxãå ¥åããŸãã [ä¿åããŠãã¹ã]ãã¿ã³ãã¯ãªãã¯ãããšãæ¥ç¶ãæåãããšããã¡ãã»ãŒãžã衚瀺ãããŸãã
次ã«ãæ°ããããã·ã¥ããŒããè¿œå ããŸãããã
ããã«ãè¿œå
ããŸããæ¥ä»ãæäœããããã®ããŒã¹ãšå¯Ÿå¿ããåãéžæ
ããŸããã¯ãšãªã«ç§»ããŸããã
ãã°ã©ããååŸããŸãããã詳现ãå¿ èŠã§ããæ¥ä»ãšæå»ã5åééã®æåã«åãäžããå¹³ååŸ ã¡æéãåºåããŠã¿ãŸãããã
ããã§ãéžæããããŒã¿ããã£ãŒãã«è¡šç€ºããããããã«çŠç¹ãåãããããšãã§ããŸããã¢ã©ãŒãã®å Žåã¯ãããªã¬ãŒã®æ§æãã€ãã³ãã«ããã°ã«ãŒãåãªã©ãè¡ããŸãã
ãããã¡ã€ã©ãŒã¯ãããŒã«ã®ä»£ããã«ã¯ãªããŸããïŒxhprofïŒfacebookïŒãxhprofïŒtidewaysïŒãliveprof fromïŒBadooïŒããããŠããããè£å®ããã ãã§ãã
ãã¹ãŠã®ãœãŒã¹ã³ãŒãã¯ãäžã§githubã®-ãããã¡ã€ã©ã¢ãã«ããµãŒãã¹ãBulkWriteCommandãBulkWriterJobåã³ããã«ãŠã§ã¢ïŒ1ã2ïŒã
ããã±ãŒãžã®ã€ã³ã¹ããŒã«ïŒ
composer req bavix/laravel-prof
æ¥ç¶ïŒconfig / database.phpïŒãèšå®ããã¯ãªãã¯ããŠã¹ãè¿œå ããŸãã
'bavix::clickhouse' => [
'driver' => 'bavix::clickhouse',
'host' => env('CH_HOST'),
'port' => env('CH_PORT'),
'database' => env('CH_DATABASE'),
'username' => env('CH_USERNAME'),
'password' => env('CH_PASSWORD'),
],
ä»äºã®å§ãŸãïŒ
use Bavix\Prof\Services\ProfileLogService;
// ...
app(ProfileLogService::class)->tick('event-name');
//
app(ProfileLogService::class)->tick('event-name');
ãããããã¥ãŒã«éä¿¡ããã«ã¯ãcronã«ã³ãã³ããè¿œå ããå¿ èŠããããŸãã
* * * * * php /var/www/site.com/artisan entry:bulk
ãŸããã³ã³ã·ã¥ãŒããŒãå®è¡ããå¿ èŠããããŸãã
php artisan queue:work --sleep=3 --tries=3
ã¹ãŒããŒãã€ã¶ãŒ ãæ§æããããšããå§ãããŸããæ§æïŒ5人ã®æ¶è²»è ïŒïŒ
[program:bulk_write]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/site.com/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=5
redirect_stderr=true
stopwaitsecs=3600
UPDïŒ
1. ClickHouseã¯ãã€ãã£ãã«ãã«ãã¥ãŒããããŒã¿ãåŒãåºãããšãã§ããŸããããããšããsdm