Skip to content

Commit

Permalink
Clickhouse (#59)
Browse files Browse the repository at this point in the history
* added first task for creating clickhouse tables

* events are now saved on clickhouse

* preparing tests for clickhouse

and cleaned up the code a bit

* fixed format

* moving more metrics to clickhouse

* fixed worker to use clickhouse

* format

* more clickhouse fixes

moved all metrics to period metric class
fixed classes for event and session for clickhouse
moved all to clickhouse

* format fix

* updated shard

* fixed some bugs in time worker

when creating events and sessions

* fixed tests for clickhouse

* forgot format

* fixed ci for clickhouse

* adding env for clickhouse in tests

* added env to clickhouse task

* fixed nillable variables on clickhouse column

* let's use utc
  • Loading branch information
confact committed Jul 4, 2020
1 parent 19e3aa7 commit daf4fda
Show file tree
Hide file tree
Showing 30 changed files with 730 additions and 369 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -35,6 +35,9 @@ jobs:
--health-interval 10s
--health-timeout 5s
--health-retries 5
clickhouse:
# Docker Hub image
image: yandex/clickhouse-server
steps:
- uses: actions/checkout@v2
- name: Add postgres key
Expand All @@ -50,8 +53,15 @@ jobs:
with:
path: ~/.cache/crystal
key: ${{ runner.os }}-crystal
- name: migrate clickhouse
run: crystal run ./tasks.cr -- kind.clickhouse
env:
# The hostname used to communicate with the PostgreSQL service container
DB_HOST: postgres
CLICKHOUSE_HOST: clickhouse
- name: Run tests
run: crystal spec
env:
# The hostname used to communicate with the PostgreSQL service container
DB_HOST: postgres
CLICKHOUSE_HOST: clickhouse
1 change: 1 addition & 0 deletions Procfile.dev
@@ -1,3 +1,4 @@
system_check: script/system_check && sleep 100000
web: lucky watch --reload-browser
assets: yarn watch
worker: crystal run src/start_worker.cr
22 changes: 19 additions & 3 deletions shard.lock
Expand Up @@ -22,7 +22,11 @@ shards:

carbon:
git: https://github.com/luckyframework/carbon.git
version: 0.1.1
version: 0.1.2

clickhouse:
git: https://github.com/confact/clickhouse.cr.git
version: 0.5.6+git.commit.3fd20f6341dbf3c37b491f20901b07582d505385

cron_parser:
git: https://github.com/kostya/cron_parser.git
Expand Down Expand Up @@ -66,7 +70,7 @@ shards:

habitat:
git: https://github.com/luckyframework/habitat.git
version: 0.4.3
version: 0.4.4

hashids:
git: https://github.com/splattael/hashids.cr.git
Expand All @@ -76,6 +80,10 @@ shards:
git: https://github.com/confact/ip2country.git
version: 0.1.5

jq:
git: https://github.com/maiha/jq.cr.git
version: 0.8.0

jwt:
git: https://github.com/crystal-community/jwt.git
version: 1.4.2
Expand All @@ -94,7 +102,7 @@ shards:

lucky_cli:
git: https://github.com/luckyframework/lucky_cli.git
version: 0.22.0
version: 0.22.1

lucky_flow:
git: https://github.com/luckyframework/lucky_flow.git
Expand All @@ -116,6 +124,10 @@ shards:
git: https://github.com/will/crystal-pg.git
version: 0.21.1

pretty:
git: https://github.com/maiha/pretty.cr.git
version: 0.9.11

quoted_printable:
git: https://github.com/arcage/crystal-quotedprintable.git
version: 0.1.0
Expand All @@ -136,6 +148,10 @@ shards:
git: https://github.com/luckyframework/teeplate.git
version: 0.8.2

var:
git: https://github.com/maiha/var.cr.git
version: 1.2.0

wordsmith:
git: https://github.com/luckyframework/wordsmith.git
version: 0.2.0
Expand Down
4 changes: 4 additions & 0 deletions shard.yml
Expand Up @@ -7,6 +7,7 @@ authors:
targets:
kindmetrics:
main: src/kindmetrics.cr
worker: src/start_worker.cr

crystal: 0.35.1

Expand Down Expand Up @@ -55,3 +56,6 @@ dependencies:
github: arcage/crystal-quotedprintable
cron_scheduler:
github: kostya/cron_scheduler
clickhouse:
github: confact/clickhouse.cr
branch: master
11 changes: 7 additions & 4 deletions spec/requests/api/events/create_spec.cr
@@ -1,6 +1,9 @@
require "../../../spec_helper"

describe Events::Create do
before_each do
AddClickhouse.clean_database
end
it "domain not set" do
response = AppClient.exec(Events::Create, test: "")
response.status_code.should eq(404)
Expand All @@ -19,8 +22,8 @@ describe Events::Create do
response = AppClient.exec(Events::Create, domain: domain.address, user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36", referrer: nil, url: "https://#{domain.address}/asdsadasds", source: nil)

response.status_code.should eq(200)
domain.events!.size.should eq(1)
domain.sessions!.size.should eq(1)
AddClickhouse.get_domain_events(domain.id).size.should eq(1)
AddClickhouse.get_domain_sessions(domain.id).size.should eq(1)
end

it "events ignored if bot" do
Expand All @@ -31,7 +34,7 @@ describe Events::Create do
response = AppClient.exec(Events::Create, domain: domain.address, user_agent: "DuckDuckBot/1.0; (+http://duckduckgo.com/duckduckbot.html)", referrer: nil, url: "https://#{domain.address}/", source: nil)

response.status_code.should eq(200)
domain.events!.size.should eq(0)
domain.sessions!.size.should eq(0)
AddClickhouse.get_domain_events(domain.id).size.should eq(0)
AddClickhouse.get_domain_sessions(domain.id).size.should eq(0)
end
end
52 changes: 34 additions & 18 deletions spec/services/event_handler_spec.cr
@@ -1,53 +1,69 @@
require "../spec_helper"

describe EventHandler do
after_each do
AddClickhouse.clean_database
end
it "is current session" do
session = SessionBox.create &.user_id("test_id").length(nil)
user_id = "event1212461"
AddClickhouse.session_insert(user_id: user_id, length: nil, is_bounce: 1, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: DomainBox.create.id)
session = AddClickhouse.get_session(user_id)
AddClickhouse.event_insert(session_id: session.not_nil!.id, name: "pageview", user_id: user_id, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: session.not_nil!.domain_id)

response = EventHandler.is_current_session?(session.user_id)
response = EventHandler.is_current_session?(user_id)
response.should eq(true)
end

it "is current session with events" do
event = EventBox.create &.user_id("test_id")
event.domain_id = event.session!.not_nil!.domain_id
id = Random.new.rand(Int64)
user_id = "event12332112"
AddClickhouse.session_insert(user_id: user_id, length: nil, is_bounce: 1, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: DomainBox.create.id)
session = AddClickhouse.get_session(user_id)

SaveSession.update!(event.session!.not_nil!, length: nil)
event = event.reload
AddClickhouse.event_insert(session_id: session.not_nil!.id, name: "pageview", user_id: user_id, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: session.not_nil!.domain_id)

response = EventHandler.is_current_session?(event.session!.not_nil!.user_id)
response = EventHandler.is_current_session?(user_id)
response.should eq(true)
end

it "is old session" do
session = SessionBox.create &.created_at(80.minutes.ago).length(nil)
user_id = "event3463421"
AddClickhouse.session_insert(user_id: user_id, length: nil, is_bounce: 1, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: DomainBox.create.id, created_at: 80.minutes.ago)
session = AddClickhouse.get_session(user_id)

response = EventHandler.is_current_session?(session.user_id)
response = EventHandler.is_current_session?(session.not_nil!.user_id)
response.should eq(false)
end

it "is current session with events" do
event = EventBox.create &.created_at(50.minutes.ago)
event.domain_id = event.session!.not_nil!.domain_id
user_id = "event8673353"
EventHandler.create_session(user_id: user_id, name: "pageview", length: 0, is_bounce: 1, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: DomainBox.create.id)
session = AddClickhouse.get_session(user_id)

response = EventHandler.is_current_session?(event.session!.not_nil!.user_id)
response = EventHandler.is_current_session?(user_id)
response.should eq(false)
end

it "already done session" do
session = SessionBox.create &.created_at(80.minutes.ago).length(23.to_i64)
user_id = "event78945322"
AddClickhouse.session_insert(user_id: user_id, length: 23.to_i64, is_bounce: 1, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: DomainBox.create.id, created_at: 80.minutes.ago)
session = AddClickhouse.get_session(user_id)

AddClickhouse.event_insert(session_id: session.not_nil!.id, name: "pageview", user_id: user_id, referrer: "indiehacker.com", url: "https://kindmetrics.com/aaadsad", referrer_source: "indiehacker.com", path: "/asadasd", device: "Desktop", operative_system: "Mac OS", referrer_domain: "indiehacker.com", browser_name: "Chrome", country: "SE", domain_id: session.not_nil!.domain_id)

response = EventHandler.is_current_session?(session.user_id)
response = EventHandler.is_current_session?(user_id)
response.should eq(false)
end

it "add event to current session" do
session = SessionBox.create &.length(nil)
domain = DomainBox.create
user_id = "event679831441"

EventQuery.new.session_id(session.id).select_count.should eq(0)
EventHandler.create_session(user_id: user_id, name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: nil, is_bounce: 0, domain_id: domain.id)
session = AddClickhouse.get_session(user_id)

EventHandler.add_event(session.user_id, name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", domain_id: session.domain_id)
events = AddClickhouse.get_last_event(session.not_nil!)

EventQuery.new.session_id(session.id).select_count.should eq(1)
events.size.should eq(1)
end
end
47 changes: 31 additions & 16 deletions spec/services/metrics_spec.cr
@@ -1,9 +1,14 @@
require "../spec_helper"

describe Metrics do
before_each do
AddClickhouse.clean_database
end
it "unique calculation" do
domain = DomainBox.create
events = EventBox.create_pair &.domain_id(domain.id)

EventHandler.create_session(user_id: "11231212", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: nil, is_bounce: 0, domain_id: domain.id)
EventHandler.create_session(user_id: "53443534", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: nil, is_bounce: 0, domain_id: domain.id)

metrics = Metrics.new(domain, "7d")
unique = metrics.unique_query
Expand All @@ -12,7 +17,12 @@ describe Metrics do

it "total calculation" do
domain = DomainBox.create
events = EventBox.create_pair &.domain_id(domain.id)
user_id = "event973231"

EventHandler.create_session(user_id: user_id, name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: nil, is_bounce: 0, domain_id: domain.id)
session = AddClickhouse.get_session(user_id)

EventHandler.add_event(user_id: user_id, name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", domain_id: domain.id)

metrics = Metrics.new(domain, "7d")
total_views = metrics.total_query
Expand All @@ -21,27 +31,30 @@ describe Metrics do

it "bounce calculation" do
domain = DomainBox.create
sessions = SessionBox.create_pair &.domain_id(domain.id).length(0).is_bounce(1)
EventHandler.create_session(user_id: "53443534", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)
EventHandler.create_session(user_id: "2423432", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)

metrics = Metrics.new(domain, "7d")
bounce_rate = metrics.bounce_query
bounce_rate.should eq(100)
end

it "bounce with 50/50 calculation" do
domain = DomainBox.create
sessions = SessionBox.create_pair &.domain_id(domain.id).is_bounce(1)
sessions = SessionBox.create_pair &.domain_id(domain.id).is_bounce(0)

metrics = Metrics.new(domain, "7d")
bounce_rate = metrics.bounce_query
# It push down the bounce_rate, that's why it is 30 and not 50.
bounce_rate.should eq(30)
end
# it "bounce with 50/50 calculation" do
# domain = DomainBox.create
#
# EventHandler.create_session(user_id: "1573435124370987", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)
# EventHandler.create_session(user_id: "12441241565512", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 234, is_bounce: 0, domain_id: domain.id)
#
# metrics = Metrics.new(domain, "7d")
# bounce_rate = metrics.bounce_query
# # It push down the bounce_rate, that's why it is 30 and not 50.
# bounce_rate.should eq(30)
# end

it "7 days calculation" do
domain = DomainBox.create
events = EventBox.create_pair &.domain_id(domain.id)
EventHandler.create_session(user_id: "dsfdsfdsf", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)
EventHandler.create_session(user_id: "f32532ewfds", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)

metrics = Metrics.new(domain, "7d")
days, today, data = metrics.get_days
Expand All @@ -58,7 +71,8 @@ describe Metrics do

it "fill empty days" do
domain = DomainBox.create
events = EventBox.create_pair &.domain_id(domain.id)
EventHandler.create_session(user_id: "gsddddddr", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)
EventHandler.create_session(user_id: "236t5fvsdx", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)

metrics = Metrics.new(domain, "7d")
days, today, data = metrics.get_days
Expand All @@ -79,7 +93,8 @@ describe Metrics do

it "14 days calculation" do
domain = DomainBox.create
events = EventBox.create_pair &.domain_id(domain.id)
EventHandler.create_session(user_id: "gsddddddr", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)
EventHandler.create_session(user_id: "236t5fvsdx", name: "pageview", referrer: "https://indiehackers.com/amazing", referrer_domain: "indiehackers.com", url: "https://test.com/test/rrr", path: "/test/rrr", referrer_source: nil, device: "Android", browser_name: "Chrome", operative_system: "Android", country: "SE", length: 0, is_bounce: 1, domain_id: domain.id)

metrics = Metrics.new(domain, "14d")
days, today, data = metrics.get_days
Expand Down

0 comments on commit daf4fda

Please sign in to comment.