Timescale 代码快速入门指南旨在帮助您将 Timescale 集成到您自己的程序中。它们使用您喜欢的编程语言来解释如何连接到 Timescale 数据库、创建和管理超表以及摄取和查询数据。

本快速入门指南将向您展示如何

在开始之前,请确保您已拥有

  • 创建了 Timescale 服务。有关更多信息,请参阅启动文档。记下您创建的 Timescale 服务中的 Service URLPasswordPort
  • 安装了 Rails
  • 安装了 psql 以连接到 Timescale 服务。

在本节中,您将通过 Ruby on Rails 应用程序创建与 Timescale 服务的连接。

  1. 创建一个新的 Rails 应用程序,配置为使用 PostgreSQL 作为数据库。您的 Timescale 服务作为 PostgreSQL 扩展工作。

    rails new my_app -d=postgresql

    Rails 创建并捆绑您的应用程序,并在该过程中安装所有必需的 Gems。

  2. 使用 Timescale 服务的 <PORT> 更新位于 my_app/config 目录中的 database.yml 中的 port

  3. DATABASE_URL 的环境变量设置为服务的 <SERVICE_URL>。例如,在 ZSH shell 中,使用以下内容编辑 ~/.zshrc 文件

    export DATABASE_URL="<SERVICE_URL>"
  4. 保存 ~/.zshrc 文件并使用以下命令加载环境变量

    source ~/.zshrc
  5. 将 Timescale 添加到您的 Rails 迁移

    rails generate migration add_timescale

    一个新的迁移文件 <migration-datetime>_add_timescale.rbmy_app/db/migrate 目录中创建。

  6. 使用 Rails 连接到您的服务

    echo "\dx" | rails dbconsole

    在密码提示符中输入 Timescale 服务的 tsdbadmin 密码。

    结果看起来像

    List of installed extensions
    Name | Version | Schema | Description
    ---------------------+---------+------------+---------------------------------------------------------------------------------------
    pg_stat_statements | 1.10 | public | track planning and execution statistics of all SQL statements executed
    plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
    timescaledb | 2.9.3 | public | Enables scalable inserts and complex queries for time-series data
    timescaledb_toolkit | 1.13.1 | public | Library of analytical hyperfunctions, time-series pipelining, and other SQL utilities
    (4 rows)
    重要

    为了确保您的测试成功运行,请在 config/environments/test.rb 文件中添加 config.active_record.verify_foreign_keys_for_fixtures = false。否则,您会收到错误,因为 Timescale 使用内部外键。

在本节中,您将创建一个表来存储用户代理或浏览器以及访问者加载页面时的时间。您可以轻松扩展这个简单的示例,以存储您感兴趣的许多其他 Web 分析。

  1. 生成 Rails 脚手架以在表中表示用户代理信息

    rails generate scaffold PageLoads user_agent:string

    一个新的迁移文件 <migration-datetime>_create_page_loads.rbmy_app/db/migrate 目录中创建。Timescale 要求表上的任何 UNIQUEPRIMARY KEY 索引都包含所有分区列,在本例中为时间列。新的 Rails 模型默认包含 id 的 PRIMARY KEY 索引,因此您需要删除该列或确保索引包含时间作为“复合键”的一部分。

    Rails 本身不支持复合键,但如果您出于某种原因需要保留 id 列,则可以使用 composite_primary_keys gem 添加对它们的支持。

  2. 将位于 my_app/db/migrate 目录的 <migration-datetime>_create_page_loads.rb 文件中的迁移代码更改为

    class CreatePageLoads < ActiveRecord::Migration[7.0]
    def change
    create_table :page_loads, id: false do |t|
    t.string :user_agent
    t.timestamps
    end
    end
    end

    Rails 生成所有辅助文件和数据库迁移。

  3. 在数据库中创建表

    rails db:migrate
  4. 使用以下命令确认表是否存在,并使用以下命令确认模型已正确映射

    rails runner 'p PageLoad.count'
    0
  5. rails dbconsole 输出中查看 page_loads 表的结构

    echo "\d page_loads" | rails dbconsole

    结果类似于

    Table "public.page_loads"
    Column | Type | Collation | Nullable | Default
    ------------+--------------------------------+-----------+----------+---------
    user_agent | character varying | | |
    created_at | timestamp(6) without time zone | | not null |
    updated_at | timestamp(6) without time zone | | not null |

创建关系表后,您可以创建超表。创建表和索引、更改表、插入数据、选择数据以及大多数其他任务都在超表上执行。

  1. 创建迁移以修改 page_loads 数据库并创建超表

    rails generate migration add_hypertable

    一个新的迁移文件 <migration-datetime>_add_hypertable.rbmy_app/db/migrate 目录中创建。

  2. 将位于 my_app/db/migrate 目录的 <migration-datetime>_add_hypertable.rb 文件中的迁移代码更改为

    class AddHypertable < ActiveRecord::Migration[7.0]
    def change
    execute "SELECT create_hypertable('page_loads', by_range('created_at'));"
    end
    end
    注意

    by_rangeby_hash 维度构建器是 TimescaleDB 2.13 的新增功能。

  3. 生成超表

    rails db:migrate
  4. 查看超表

    echo "\d page_loads" | rails dbconsole

    结果类似于

    Table "public.page_loads"
    Column | Type | Collation | Nullable | Default
    ------------+--------------------------------+-----------+----------+---------
    user_agent | character varying | | |
    created_at | timestamp(6) without time zone | | not null |
    updated_at | timestamp(6) without time zone | | not null |
    Indexes:
    "page_loads_created_at_idx" btree (created_at DESC)
    Triggers:
    ts_insert_blocker BEFORE INSERT ON page_loads FOR EACH ROW EXECUTE FUNCTION _timescaledb_functions.insert_blocker()

您可以通过几种不同的方式将数据插入到超表中。创建一个新的视图和控制器,以便您可以将值插入到数据库中,将用户代理和时间存储在数据库中,检索站点访问者的浏览器用户代理。然后,您可以创建一个 PageLoad 对象,存储用户代理信息和时间,并将该对象保存到 Timescale 服务。

  1. 创建一个新的视图和控制器,以便您可以将值插入到数据库中

    rails generate controller static_pages home

    这将为网站上名为 /static_pages/home 的页面生成视图和控制器文件。static_pages_controller.rb 文件位于 /my_app/app/controllers 目录中。

  2. 将此行添加到 static_pages_controller.rb 文件中,以检索站点访问者的浏览器用户代理。

    class StaticPagesController < ApplicationController
    def home
    @agent = request.user_agent
    end
    end
  3. 将您创建的 @agent 变量打印到位于 /my_app/app/views/static_pages/home.html.erb 文件中。

    <h1>StaticPages#home</h1>
    <p>Find me in app/views/static_pages/home.html.erb</p>
    <p>Request: <&= @agent &></p>
  4. 启动 Rails 服务器

    rails s

    转到 http://localhost:3000/static_pages/home。您应该看到浏览器用户代理的打印输出。

  5. 更新 static_pages_controller.rb 控制器文件以创建 PageLoad 对象,存储用户代理信息和时间,并将该对象保存到 Timescale tsdb 数据库

    class StaticPagesController < ApplicationController
    def home
    PageLoad.create(user_agent: request.user_agent)
    end
    end

    当您转到浏览器并多次刷新页面时。在 Rails 控制台窗口中,将出现提交消息

    Started GET "/static_pages/home" for ::1 at 2023-02-22 07:02:16 +0530
    Processing by StaticPagesController#home as HTML
    TRANSACTION (268.7ms) BEGIN
    ↳ app/controllers/static_pages_controller.rb:3:in 'home'
    PageLoad Create (207.8ms) INSERT INTO "page_loads" ("user_agent", "created_at", "updated_at") VALUES ($1, $2, $3) [["user_agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"], ["created_at", "2023-02-22 01:32:16.465709"], ["updated_at", "2023-02-22 01:32:16.465709"]]
    ↳ app/controllers/static_pages_controller.rb:3:in 'home'
    TRANSACTION (206.5ms) COMMIT
    ↳ app/controllers/static_pages_controller.rb:3:in 'home'
    Rendering layout layouts/application.html.erb
    Rendering static_pages/home.html.erb within layouts/application
    Rendered static_pages/home.html.erb within layouts/application (Duration: 0.1ms | Allocations: 7)
    Rendered layout layouts/application.html.erb (Duration: 9.4ms | Allocations: 2389)
    Completed 200 OK in 917ms (Views: 10.4ms | ActiveRecord: 682.9ms | Allocations: 4542)
  6. 使用 psql 连接到 tsdb 数据库

    psql -x <SERVICE_URL>
  7. 查看 Timescale tsdb 数据库中的条目

    SELECT * FROM page_loads ORDER BY created_at DESC;

    结果类似于

    -[ RECORD 1 ]---------------------------------------------------------------------------------------------------------------------
    user_agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/202.0.0.0 Safari/537.36
    created_at | 2023-02-22 01:32:53.935198
    updated_at | 2023-02-22 01:32:53.935198
    -[ RECORD 2 ]---------------------------------------------------------------------------------------------------------------------
    user_agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/202.0.0.0 Safari/537.36
    created_at | 2023-02-22 01:32:45.146997
    updated_at | 2023-02-22 01:32:45.146997

本节介绍如何针对数据库执行查询。您可以检索您插入的数据并查看它。

  1. static_pages_controller.rb 文件中,修改 home 方法以使用 Active Record 查询 page_load 数据库中的所有项目,并将它们存储在数组中

    class StaticPagesController < ApplicationController
    def home
    PageLoad.create(:user_agent => request.user_agent)
    end
    end
  2. 修改 home.html.erb 视图以迭代数组并显示每个项目

    <h1>Static Pages requests: <%= PageLoad.count &amp;></h1>

    现在,每次刷新页面时,您都可以看到记录正在插入到 tsdb Timescale 数据库中,并且页面上的计数器会递增。

  3. 您需要有大量的页面加载才能研究和探索 time_bucket 函数。您可以使用 Apache Bench 又名 ab 来请求 50,000 次,并行 10 次。

    ab -n 50000 -c 10 http://localhost:3000/static_pages/home

    Apache Bench 在超表中创建数千条记录。您可以计算 Rails 支持多少“空请求”。

  4. ab 命令开始运行后,您可以启动 rails 控制台并尝试使用 time_bucket 函数进行一些查询。

    rails console
  5. 查看每分钟的请求数

    PageLoad
    .select("time_bucket('1 minute', created_at) as time, count(1) as total")
    .group('time').order('time')
    .map {|result| [result.time, result.total]}

    结果类似于

    PageLoad Load (357.7ms) SELECT time_bucket('1 minute', created_at) as time, count(1) as total FROM "page_loads" GROUP BY time ORDER BY time
    =>
    [2023-02-22 01:32:00 UTC, 6],
    [2023-02-22 05:57:00 UTC, 3],
    [2023-02-22 05:59:00 UTC, 75],

作用域对于将复杂的 SQL 语句分解为 Ruby 对象非常有用。它还允许您根据需要引入参数和重用查询。创建一些有用的作用域,这些作用域可以帮助汇总和轻松访问 time_bucket 函数

  1. 在位于 my_app/app/models 目录的 page_load.rb 文件中,添加这些作用域

    class PageLoad < ApplicationRecord
    scope :last_month, -> { where('created_at > ?', 1.month.ago) }
    scope :last_week, -> { where('created_at > ?', 1.week.ago) }
    scope :last_hour, -> { where('created_at > ?', 1.hour.ago) }
    scope :yesterday, -> { where('DATE(created_at) = ?', 1.day.ago.to_date) }
    scope :today, -> { where('DATE(created_at) = ?', Date.today) }
    end
  2. 在新的 Ruby 控制台中,您可以运行这些命令来获取各种请求的视图

    PageLoad.last_week.count # Total of requests from last week
    PageLoad.last_hour.first # First request from last hour
    PageLoad.last_hour.all # All requests from last hour
    PageLoad.last_hour.limit(10) # 10 requests from last hour

    您还可以将作用域与其他 ActiveRecord 方法结合使用,例如

    # Count chrome users from last hour
    PageLoad.last_hour.where("user_agent like '%Chrome%'").count
  3. page_load.rb 文件中添加一个新的作用域,用于计算每分钟的维度

    class PageLoad < ApplicationRecord
    scope :counts_per, -> (time_dimension) {
    select("time_bucket('#{time_dimension}', created_at) as time, count(1) as total")
    .group(:time).order(:time)
    .map {|result| [result.time, result.total]}
    }
    end
  4. 在 Ruby 控制台中探索其他时间范围

    PageLoad.counts_per('1 hour')

    结果类似于

    PageLoad Load (299.7ms) SELECT time_bucket('1 hour', created_at) as time, count(1) as total FROM "page_loads" GROUP BY "time" ORDER BY "time" ASC
    =>
    [2023-02-22 01:00:00 UTC, 6],
    [2023-02-22 05:00:00 UTC, 78],
    [2023-02-22 06:00:00 UTC, 13063],
    [2023-02-22 07:00:00 UTC, 4114],

为了更深入地了解请求,请移动示例以监视所有服务器请求并存储端点路径和返回响应所需的时间。

  1. 使用 rails 迁移向数据库添加列

    rails g migration add_performance_to_page_load path:string performance:float

    Rails 生成器理解迁移的命名约定和额外的参数,以在 my_app/db/migrate 目录中创建一个新的迁移文件 <migration-datetime>_add_performance_to_page_load.rb

  2. 要在数据库中添加两列,请运行 rails db:migrate

    结果类似于

    == 20230226173116 AddPerformanceToPageLoad: migrating =========================
    -- add_column(:page_loads, :path, :string)
    -> 0.6050s
    -- add_column(:page_loads, :performance, :float)
    -> 0.3076s
    == 20230226173116 AddPerformanceToPageLoad: migrated (0.9129s) ================
  3. 要使用 around_action 钩子钩住应用程序控制器,请在位于 my_app/app/controllers 目录的 application_controller.rb 文件中添加这些

    class ApplicationController < ActionController::Base
    around_action do |controller, action|
    performance = Benchmark.measure(&action.method(:call))
    PageLoad.create(path: request.path,
    performance: performance.real,
    user_agent: request.user_agent)
    end
    end

    这将为系统中发生的任何请求创建 PageLoad 记录。

  4. 要查看最新记录,请在 Rails 控制台中运行:PageLoad.order(:created_at).last

    结果类似于

    PageLoad Load (318.2ms) SELECT "page_loads".* FROM "page_loads" ORDER BY "page_loads"."created_at" DESC LIMIT $1 [["LIMIT", 1]]
    =>
    #<PageLoad:0x000000010950a410
    user_agent:
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
    created_at: Sun, 26 Feb 2023 15:49:35.186955000 UTC +00:00,
    updated_at: Sun, 26 Feb 2023 15:49:35.186955000 UTC +00:00,
    path: "/static_pages/home",
    performance: 1.094204000197351>

    本示例仅使用来自 benchmark真实性能,但您可以收集其他指标以查看有关您的系统的更多详细信息。

既然您知道存在哪些页面,您就可以探索结果。您可以逐页浏览,或一起浏览所有页面,并按路径分组或不分组

  1. 在位于 my_app/app/models 目录的 page_load.rb 文件中,添加这些作用域,用于平均响应时间、minmax 请求,并从页面加载中收集唯一路径

    class PageLoad < ApplicationRecord
    scope :per_minute, -> { time_bucket('1 minute') }
    scope :per_hour, -> { time_bucket('1 hour') }
    scope :per_day, -> { time_bucket('1 day') }
    scope :per_week, -> { time_bucket('1 week') }
    scope :per_month, -> { time_bucket('1 month') }
    scope :average_response_time_per_minute, -> { time_bucket('1 minute', value: 'avg(performance)') }
    scope :average_response_time_per_hour, -> { time_bucket('1 hour', value: 'avg(performance)') }
    scope :worst_response_time_last_minute, -> { time_bucket('1 minute', value: 'max(performance)') }
    scope :worst_response_time_last_hour, -> { time_bucket('1 hour', value: 'max(performance)') }
    scope :best_response_time_last_hour, -> { time_bucket('1 hour', value: 'min(performance)') }
    scope :paths, -> { distinct.pluck(:path) }
    scope :time_bucket, -> (time_dimension, value: 'count(1)') {
    select(<<~SQL)
    time_bucket('#{time_dimension}', created_at) as time, path,
    #{value} as value
    SQL
    .group('time, path').order('path, time')
    }
    end
  2. 在 Rails 控制台中,从页面加载中收集唯一路径

    PageLoad.paths # => ["/page_loads/new", "/static_pages/home"]

    结果类似于

    PageLoad Pluck (276.1ms) SELECT DISTINCT "page_loads"."path" FROM "page_loads"
    => [nil, "/static_pages/home"]
  3. 在 Ruby 控制台中,要获取为响应时间生成的实际指标,请使用包含 response_time 的方法进行过滤

    PageLoad.methods.grep /response_time/

    结果类似于

    PageLoad.methods.grep /response_time/
    # => [:average_response_time_per_hour,
    # :average_response_time_per_minute,
    # :worst_response_time_last_hour,
    # :worst_response_time_last_minute,
    # :best_response_time_last_hour]
  4. 要基于每个页面构建摘要,并递归导航到所有页面并为每个页面构建摘要,请将以下内容添加到 my_app/app/models/ 文件夹中的 page_load.rb

    def self.resume_for(path)
    filter = where(path: path)
    get = -> (scope_name) { filter.send(scope_name).first&.value}
    metrics.each_with_object({}) do |metric, resume|
    resume[metric] = get[metric]
    end
    end
    def self.metrics
    methods.grep /response_time/
    end
    def self.statistics
    paths.each_with_object({}) do |path, resume|
    resume[path] = resume_for(path)
    end
    end
  5. 在 Rails 控制台中,要查看基于每个页面的摘要,请运行 PageLoad.resume_for("/page_loads/new")

    结果类似于

    => {:average_response_time_per_minute=>0.10862650000490248,
    :average_response_time_per_hour=>0.060067999991588295,
    :worst_response_time_last_minute=>0.20734900003299117,
    :worst_response_time_last_hour=>0.20734900003299117,
    :best_response_time_last_hour=>0.009765000082552433},
  6. 在 Rails 控制台中,递归导航到所有页面并为每个页面构建摘要

    结果类似于

    "/page_loads/new"=>
    {:average_response_time_per_minute=>0.10862650000490248,
    :average_response_time_per_hour=>0.060067999991588295,
    :worst_response_time_last_minute=>0.20734900003299117,
    :worst_response_time_last_hour=>0.20734900003299117,
    :best_response_time_last_hour=>0.009765000082552433},
    "/static_pages/home"=>
    {:average_response_time_per_minute=>1.214221078382038,
    :average_response_time_per_hour=>4.556298695798993,
    :worst_response_time_last_minute=>2.2735520000569522,
    :worst_response_time_last_hour=>1867.2145019997843,
    :best_response_time_last_hour=>1.032415000256151}}

关键词

在此页面上发现问题?报告问题 或 在 GitHub 上编辑此页面