pg 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,320 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # vim: set nosta noet ts=4 sw=4:
3
- #
4
- # Script to automatically move partitioned tables and their indexes
5
- # to a separate area on disk.
6
- #
7
- # Mahlon E. Smith <mahlon@martini.nu>
8
- #
9
- # Example use case:
10
- #
11
- # - You've got a heavy insert table, such as syslog data.
12
- # - This table has a partitioning trigger (or is manually partitioned)
13
- # by date, to separate incoming stuff from archival/report stuff.
14
- # - You have a tablespace on cheap or slower disk (maybe even
15
- # ZFS compressed, or some such!)
16
- #
17
- # The only assumption this script makes is that your tables are dated, and
18
- # the tablespace they're moving into already exists.
19
- #
20
- # A full example, using the syslog idea from above, where each child
21
- # table is date partitioned by a convention of "syslog_YEAR-WEEKOFYEAR":
22
- #
23
- # syslog # <--- parent
24
- # syslog_2012_06 # <--- inherited
25
- # syslog_2012_07 # <--- inherited
26
- # syslog_2012_08 # <--- inherited
27
- # ...
28
- #
29
- # You'd run this script like so:
30
- #
31
- # ./warehouse_partitions.rb -F syslog_%Y_%U
32
- #
33
- # Assuming this was week 12 of the year, tables syslog_2012_06 through
34
- # syslog_2012_11 would start sequentially migrating into the tablespace
35
- # called 'warehouse'.
36
- #
37
-
38
-
39
- begin
40
- require 'date'
41
- require 'ostruct'
42
- require 'optparse'
43
- require 'pathname'
44
- require 'etc'
45
- require 'pg'
46
-
47
- rescue LoadError # 1.8 support
48
- unless Object.const_defined?( :Gem )
49
- require 'rubygems'
50
- retry
51
- end
52
- raise
53
- end
54
-
55
-
56
- ### A tablespace migration class.
57
- ###
58
- class PGWarehouse
59
-
60
- def initialize( opts )
61
- @opts = opts
62
- @db = PG.connect(
63
- :dbname => opts.database,
64
- :host => opts.host,
65
- :port => opts.port,
66
- :user => opts.user,
67
- :password => opts.pass,
68
- :sslmode => 'prefer'
69
- )
70
- @db.exec "SET search_path TO %s" % [ opts.schema ] if opts.schema
71
-
72
- @relations = self.relations
73
- end
74
-
75
- attr_reader :db
76
-
77
- ######
78
- public
79
- ######
80
-
81
- ### Perform the tablespace moves.
82
- ###
83
- def migrate
84
- if @relations.empty?
85
- $stderr.puts 'No tables were found for warehousing.'
86
- return
87
- end
88
-
89
- $stderr.puts "Found %d relation%s to move." % [ relations.length, relations.length == 1 ? '' : 's' ]
90
- @relations.sort_by{|_,v| v[:name] }.each do |_, val|
91
- $stderr.print " - Moving table '%s' to '%s'... " % [
92
- val[:name], @opts.tablespace
93
- ]
94
-
95
- if @opts.dryrun
96
- $stderr.puts '(not really)'
97
-
98
- else
99
- age = self.timer do
100
- db.exec "ALTER TABLE %s SET TABLESPACE %s;" % [
101
- val[:name], @opts.tablespace
102
- ]
103
- end
104
- puts age
105
- end
106
-
107
- val[ :indexes ].each do |idx|
108
- $stderr.print " - Moving index '%s' to '%s'... " % [
109
- idx, @opts.tablespace
110
- ]
111
- if @opts.dryrun
112
- $stderr.puts '(not really)'
113
-
114
- else
115
- age = self.timer do
116
- db.exec "ALTER INDEX %s SET TABLESPACE %s;" % [
117
- idx, @opts.tablespace
118
- ]
119
- end
120
- puts age
121
- end
122
- end
123
- end
124
- end
125
-
126
-
127
- #########
128
- protected
129
- #########
130
-
131
- ### Get OIDs and current tablespaces for everything under the
132
- ### specified schema.
133
- ###
134
- def relations
135
- return @relations if @relations
136
- relations = {}
137
-
138
- query = %q{
139
- SELECT c.oid AS oid,
140
- c.relname AS name,
141
- c.relkind AS kind,
142
- t.spcname AS tspace
143
- FROM pg_class AS c
144
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
145
- LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
146
- WHERE c.relkind = 'r' }
147
- query << "AND n.nspname='#{@opts.schema}'" if @opts.schema
148
-
149
- # Get the relations list, along with each element's current tablespace.
150
- #
151
- self.db.exec( query ) do |res|
152
- res.each do |row|
153
- relations[ row['oid'] ] = {
154
- :name => row['name'],
155
- :tablespace => row['tspace'],
156
- :indexes => [],
157
- :parent => nil
158
- }
159
- end
160
- end
161
-
162
- # Add table inheritence information.
163
- #
164
- db.exec 'SELECT inhrelid AS oid, inhparent AS parent FROM pg_inherits' do |res|
165
- res.each do |row|
166
- relations[ row['oid'] ][ :parent ] = row['parent']
167
- end
168
- end
169
-
170
- # Remove tables that don't qualify for warehousing.
171
- #
172
- # - Tables that are not children of a parent
173
- # - Tables that are already in the warehouse tablespace
174
- # - The currently active child (it's likely being written to!)
175
- # - Any table that can't be parsed into the specified format
176
- #
177
- relations.reject! do |oid, val|
178
- begin
179
- val[:parent].nil? ||
180
- val[:tablespace] == @opts.tablespace ||
181
- val[:name] == Time.now.strftime( @opts.format ) ||
182
- ! DateTime.strptime( val[:name], @opts.format )
183
- rescue ArgumentError
184
- true
185
- end
186
- end
187
-
188
- query = %q{
189
- SELECT c.oid AS oid,
190
- i.indexname AS name
191
- FROM pg_class AS c
192
- INNER JOIN pg_indexes AS i
193
- ON i.tablename = c.relname }
194
- query << "AND i.schemaname='#{@opts.schema}'" if @opts.schema
195
-
196
- # Attach index names to tables.
197
- #
198
- db.exec( query ) do |res|
199
- res.each do |row|
200
- relations[ row['oid'] ][ :indexes ] << row['name'] if relations[ row['oid'] ]
201
- end
202
- end
203
-
204
- return relations
205
- end
206
-
207
-
208
- ### Wrap arbitrary commands in a human readable timer.
209
- ###
210
- def timer
211
- start = Time.now
212
- yield
213
- age = Time.now - start
214
-
215
- diff = age
216
- secs = diff % 60
217
- diff = ( diff - secs ) / 60
218
- mins = diff % 60
219
- diff = ( diff - mins ) / 60
220
- hour = diff % 24
221
-
222
- return "%02d:%02d:%02d" % [ hour, mins, secs ]
223
- end
224
- end
225
-
226
-
227
- ### Parse command line arguments. Return a struct of global options.
228
- ###
229
- def parse_args( args )
230
- options = OpenStruct.new
231
- options.database = Etc.getpwuid( Process.uid ).name
232
- options.host = '127.0.0.1'
233
- options.port = 5432
234
- options.user = Etc.getpwuid( Process.uid ).name
235
- options.sslmode = 'prefer'
236
- options.tablespace = 'warehouse'
237
-
238
- opts = OptionParser.new do |opts|
239
- opts.banner = "Usage: #{$0} [options]"
240
-
241
- opts.separator ''
242
- opts.separator 'Connection options:'
243
-
244
- opts.on( '-d', '--database DBNAME',
245
- "specify the database to connect to (default: \"#{options.database}\")" ) do |db|
246
- options.database = db
247
- end
248
-
249
- opts.on( '-h', '--host HOSTNAME', 'database server host' ) do |host|
250
- options.host = host
251
- end
252
-
253
- opts.on( '-p', '--port PORT', Integer,
254
- "database server port (default: \"#{options.port}\")" ) do |port|
255
- options.port = port
256
- end
257
-
258
- opts.on( '-n', '--schema SCHEMA', String,
259
- "operate on the named schema only (default: none)" ) do |schema|
260
- options.schema = schema
261
- end
262
-
263
- opts.on( '-T', '--tablespace SPACE', String,
264
- "move old tables to this tablespace (default: \"#{options.tablespace}\")" ) do |tb|
265
- options.tablespace = tb
266
- end
267
-
268
- opts.on( '-F', '--tableformat FORMAT', String,
269
- "The naming format (strftime) for the inherited tables (default: none)" ) do |format|
270
- options.format = format
271
- end
272
-
273
- opts.on( '-U', '--user NAME',
274
- "database user name (default: \"#{options.user}\")" ) do |user|
275
- options.user = user
276
- end
277
-
278
- opts.on( '-W', 'force password prompt' ) do |pw|
279
- print 'Password: '
280
- begin
281
- system 'stty -echo'
282
- options.pass = gets.chomp
283
- ensure
284
- system 'stty echo'
285
- puts
286
- end
287
- end
288
-
289
- opts.separator ''
290
- opts.separator 'Other options:'
291
-
292
- opts.on_tail( '--dry-run', "don't actually do anything" ) do
293
- options.dryrun = true
294
- end
295
-
296
- opts.on_tail( '--help', 'show this help, then exit' ) do
297
- $stderr.puts opts
298
- exit
299
- end
300
-
301
- opts.on_tail( '--version', 'output version information, then exit' ) do
302
- puts Stats::VERSION
303
- exit
304
- end
305
- end
306
-
307
- opts.parse!( args )
308
- return options
309
- end
310
-
311
-
312
- if __FILE__ == $0
313
- opts = parse_args( ARGV )
314
- raise ArgumentError, "A naming format (-F) is required." unless opts.format
315
-
316
- $stdout.sync = true
317
- PGWarehouse.new( opts ).migrate
318
- end
319
-
320
-