fury_dumper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.ru.md ADDED
@@ -0,0 +1,382 @@
1
+ # FuryDumper πŸ§™β€
2
+
3
+ Π”ΠΎΠ±Ρ€ΠΎ ΠΏΠΎΠΆΠ°Π»ΠΎΠ²Π°Ρ‚ΡŒ Π³Π΅ΠΌ для быстрого ΠΈ Π»Π΅Π³ΠΊΠΎΠ³ΠΎ Π΄Π°ΠΏΠΌΠ° ΠΈΠ· ΡƒΠ΄Π°Π»Π΅Π½ΠΎΠΉ Π‘Π”!
4
+
5
+ Данная Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ Π²Π°ΠΌ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π΄Π°ΠΌΠΏ ΠΈΠ· ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… Π² main service ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… микросСрвисах, ΠΈΠΌΠ΅ΡŽΡ‰ΠΈΡ… Π³Π΅ΠΌ `fury_dumper`.\
6
+ *Π”ΠΎΠΊΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… языках: [English](README.md).*
7
+
8
+ Π’ этом Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΈ Π²Ρ‹ Π½Π°ΠΉΠ΄Π΅Ρ‚Π΅ Ρ„Π°ΠΉΠ»Ρ‹, Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΡ‹Π΅ для Π΄Π°Π½Π½ΠΎΠΉ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Π½Π° Ruby.
9
+ Если Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΏΠΎΠΌΠΎΡ‡ΡŒ - ΠΊΠΎΠ΄ΠΈΡ‚ΡŒ Ρ‚ΡƒΡ‚ΡŒ `lib / fury_dumper` 😊.\
10
+ *ΠžΡΠΎΠ±Π΅Π½Π½ΠΎΡΡ‚ΠΈ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ здСсь: [English](README.md#dev-documentation), [Russian](README.ru.md#докумСнтация-для-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ²).*
11
+
12
+ ## Установка
13
+
14
+ ПишСм Π² Gemfile вашСго ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°:
15
+
16
+ ```ruby
17
+ gem 'fury_dumper'
18
+ ```
19
+
20
+ ВыполняСм:
21
+
22
+ bundle install
23
+
24
+ Π’ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡ΠΊΡƒ ΠΌΠΎΠΆΠ½ΠΎ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ‚Π°ΠΊ:
25
+
26
+ gem install fury_dumper
27
+
28
+ ## ИспользованиС
29
+
30
+ ### ΠšΠΎΠ½Ρ„ΠΈΠ³ΠΈ
31
+
32
+ Π§Ρ‚ΠΎΠ±Ρ‹ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ Π΄Π΅Ρ„ΠΎΠ»Ρ‚Π½ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ:
33
+
34
+ bundle exec rails generate fury_dumper:config
35
+
36
+ Для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠΉ Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Π΄Ρ€ΡƒΠ³ΠΈΠΌΠΈ микросСрвисами Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠ½ΠΎ Π½Π°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ `fury_dumper.yml` ΠΊΠΎΠ½Ρ„ΠΈΠ³. ОписаниС Π΅Π³ΠΎ структуры:
37
+
38
+ ```yaml
39
+ # Π Π°Π·ΠΌΠ΅Ρ€ Π±Π°Ρ‚Ρ‡Π° Π½Π° ΠΏΠ΅Ρ€Π²ΠΎΠΉ ΠΈΡ‚Π΅Ρ€Π°Ρ†ΠΈΠΈ
40
+ #
41
+ # НС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ; ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ 100
42
+ batch_size: 100
43
+
44
+ # Π‘ΠΎΠΎΡ‚Π½ΠΎΡˆΠ΅Π½ΠΈΠ΅ количСства записСй (fetching_records), Π²Ρ‹Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌΡ‹Ρ… ΠΈΠ· Π‘Π”, ΠΊ Ρ€Π°Π·ΠΌΠ΅Ρ€Ρƒ Π±Π°Ρ‚Ρ‡Π°
45
+ # Π€ΠΎΡ€ΠΌΡƒΠ»Π°: fetching_records = ratio_records_batches * batch_size
46
+ # fetching_records выступаСт Π² Ρ€ΠΎΠ»ΠΈ значСния limit'Π° для sql запросов
47
+ #
48
+ # НС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ; ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ 10
49
+ ratio_records_batches: 10
50
+
51
+ # Π Π΅ΠΆΠΈΠΌ ΠΎΠ±Ρ…ΠΎΠ΄Π° Π³Ρ€Π°Ρ„Π° связСй - Π² ΡˆΠΈΡ€ΠΈΠ½Ρƒ(:wide) ΠΈΠ»ΠΈ Π³Π»ΡƒΠ±ΠΈΠ½Ρƒ(:depth)
52
+ #
53
+ # НС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ; ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ связи Π² ΡˆΠΈΡ€ΠΈΠ½Ρƒ
54
+ mode: wide
55
+
56
+ # Бвязи ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π±ΡƒΠ΄ΡƒΡ‚ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½Ρ‹ ΠΈΠ· Π΄Π°ΠΌΠΏΠ°,
57
+ # ΠΏΠΎΠ»Π΅Π·Π½ΠΎ для ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ скорости Π²Ρ‹Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ
58
+ # ΠΈΠ· Π½Π΅Π΅ Π»ΠΈΡˆΠ½ΠΈΡ… Π΄Π°Π½Π½Ρ‹Ρ…:
59
+ # < имя класса >.< имя связи >
60
+ #
61
+ # НС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ; ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ пустой массив
62
+ exclude_relations: User.friends, Post.author
63
+
64
+ # По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ Π΄Π°Π½Π½Ρ‹Π΅ Π²Ρ‹Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ быстро (Π±Π΅Π· сортироки)
65
+ # fast Ρ€Π΅ΠΆΠΈΠΌ позволяСт Π²Ρ‹Π³Ρ€ΡƒΠΆΠ°Ρ‚ΡŒ записи, отсортированныС ΠΏΠΎ ΠΏΡ€Π΅Π²Ρ‡ΠΈΠ½ΠΎΠΌΡƒ ΠΊΠ»ΡŽΡ‡Ρƒ(false) ΠΈΠ»ΠΈ Π½Π΅Ρ‚(true),
66
+ # ΠΏΠΎΠ»Π΅Π·Π½ΠΎ для ΠΏΡ€ΠΈ создании Π΄Π°ΠΌΠΏΠΎΠ² для Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ².
67
+ #
68
+ # НС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ; ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ true
69
+ fast: true
70
+
71
+ # Бписок микросСрвисных связСй
72
+ #
73
+ # НС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
74
+ relative_services:
75
+ # Имя микросСрвиса
76
+ #
77
+ # НС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
78
+ post_service:
79
+ # Имя ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π” для Π΄Π°Π½Π½ΠΎΠ³ΠΎ микросСрвиса
80
+ # с ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π±ΡƒΠ΄ΡƒΡ‚ Ρ‚ΡΠ½ΡƒΡ‚ΡŒΡΡ Π΄Π°Π½Π½Ρ‹Π΅
81
+ #
82
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
83
+ database: 'post_service_development_dump'
84
+
85
+ # Π₯ост для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π”
86
+ #
87
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
88
+ host: 'localhost'
89
+
90
+ # ΠŸΠΎΡ€Ρ‚ для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π”
91
+ #
92
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
93
+ port: '5432'
94
+
95
+ # Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π”
96
+ #
97
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
98
+ user: 'user'
99
+
100
+ # ΠŸΠ°Ρ€ΠΎΠ»ΡŒ для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π”
101
+ #
102
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
103
+ password: 'password'
104
+
105
+ # Бписок Ρ‚Π°Π±Π»ΠΈΡ†, ΠΈΠΌΠ΅ΡŽΡ‰ΠΈΡ… связи с микросСрвисом (post_service)
106
+ #
107
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
108
+ tables:
109
+ # Имя Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ с Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΌ сСрвисС
110
+ #
111
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
112
+ users:
113
+ # Имя Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ Π² микросСрвисС (post_service)
114
+ #
115
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
116
+ users:
117
+ # Имя ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ ΠΊ Ρ‚Π°Π±Π»ΠΈΡ†Π΅ Π΄Π°Π½Π½ΠΎΠ³ΠΎ сСрвиса (users)
118
+ #
119
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
120
+ self_field_name: 'id'
121
+
122
+ # Имя ΠΌΠΎΠ΄Π΅Π»ΠΈ Π² микросСрвисС (post_service)
123
+ #
124
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
125
+ ms_model_name: 'User'
126
+
127
+ # Имя ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ ΠΊ Ρ‚Π°Π±Π»ΠΈΡ†Π΅ микросСрвиса (involved_users)
128
+ #
129
+ # ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ
130
+ ms_field_name: 'root_user_id'
131
+ root_posts:
132
+ posts:
133
+ self_field_name: 'id'
134
+ ms_model_name: 'Post'
135
+ ms_field_name: 'root_post_id'
136
+ logs_service:
137
+ database: 'logs_service_development_dump'
138
+ host: 'localhost'
139
+ port: '5432'
140
+ user: 'user'
141
+ password: 'password'
142
+ tables:
143
+ users:
144
+ logs:
145
+ self_field_name: "log :: json - >> 'id'"
146
+ ms_model_name: 'Log'
147
+ ms_field_name: 'id'
148
+ ```
149
+
150
+ ### Routing для микросСрвисов
151
+
152
+ НСобходимо Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ этот ΠΊΠΎΠ΄ Π² `config/routes.rb` для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΡ€Π΅Π΄ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ Π΄Ρ€ΡƒΠ³ΠΈΠΌ микросСрвисам Π΄Π°ΠΌΠΏΠ°Ρ‚ΡŒ Π‘Π” Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°.
153
+
154
+ ```ruby
155
+ mount FuryDumper::Engine => "fury_dumper" unless Rails.env.production?
156
+ ```
157
+
158
+ ### Π’Ρ‹Π·ΠΎΠ² основной Ρ„ΡƒΠΊΡ†ΠΈΠΈ
159
+
160
+ **⚠️ ⚠️ ⚠️ Π’Π½ΠΈΠΌΠ°Π½ΠΈΠ΅! ΠŸΡ€ΠΈ ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠΈ Π΄Π°Π½Π½Ρ‹Ρ…, Π² случаС ΠΊΠΎΠ½Ρ„Π»ΠΈΠΊΡ‚Π° с ΠΈΠΌΠ΅ΡŽΡ‰ΠΈΠΌΠΈΡΡ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ, Π±ΠΎΠ»Π΅Π΅ ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚Π½Ρ‹ΠΌΠΈ ΡΡ‡ΠΈΡ‚Π°ΡŽΡ‚ΡΡ Π² ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π” (Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ пСрСтрутся)! ⚠️ ⚠️ ⚠️**
161
+
162
+ Для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π½Π°Ρ‡Π°Ρ‚ΡŒ Π΄Π°ΠΌΠΏ с ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π”, Π² консолС ΡƒΠΊΠ°ΠΆΠΈΡ‚Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ:
163
+
164
+ ```ruby
165
+ FuryDumper.dump(password: 'password',
166
+ host: 'localhost',
167
+ port: '5632',
168
+ user: 'username',
169
+ model_name: 'User',
170
+ field_name: 'token',
171
+ field_values: ['99999999-8888-4444-1212-111111111111'],
172
+ database: 'staging',
173
+ debug_mode: :short)
174
+ ```
175
+
176
+ Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, для ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊ ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΌΡƒ хосту, Π±ΡƒΠ΄Π΅Ρ‚ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ ssh ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ. ΠŸΡ€ΠΈΠΌΠ΅Ρ€ для Π‘Π” стСйдТа фСликса:
177
+ ```
178
+ ssh -NL <local_port>:<host>:<db_port> username@<host>
179
+ ```
180
+
181
+ ОписаниС Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²:
182
+
183
+ | АргумСнт | ОписаниС |
184
+ | --- | --- |
185
+ | host | хост для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π” |
186
+ | port | ΠΏΠΎΡ€Ρ‚ для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π” |
187
+ | user | имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π” |
188
+ | password | ΠΏΠ°Ρ€ΠΎΠ»ΡŒ для ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π” |
189
+ | database | имя Π‘Π” |
190
+ | model_name |имя модСли для дампа |
191
+ | field_name | имя поля, ΠΏΠΎ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌΡƒ Π±ΡƒΠ΄Π΅Ρ‚ Π΄Π°ΠΌΠΏ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ - 'id' ΠΈΠ»ΠΈ 'admin_token', default='id') |
192
+ | field_values | значСния поля field_name |
193
+ | debug_mode | Ρ€Π΅ΠΆΠΈΠΌ ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ (full Π²Ρ‹Π²ΠΎΠ΄ΠΈΡ‚ всС сообщСния, short - ΠΊΠΎΡ€ΠΎΡ‚ΠΊΠΈΠ΅ сообщСния, none - Π½ΠΈΡ‡Π΅Π³ΠΎ) |
194
+ | ask | Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΏΡ€ΠΈ различиях Π² Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ ΠΈ ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎΠΉ Π‘Π” |
195
+
196
+ ### ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‡ΠΈΠΊΠΈ
197
+
198
+ Π’ этих ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°Ρ… Π½Π΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ ΠΌΠ΅Π½ΡΡ‚ΡŒ `fury_dumper.yml` ΠΊΠΎΠ½Ρ„ΠΈΠ³, Π²ΠΎΠ·ΠΌΠΈΡ‚Π΅ [Π΄Π΅Ρ„ΠΎΠ»Ρ‚Π½Ρ‹ΠΉ](README.ru.md#ΠΊΠΎΠ½Ρ„ΠΈΠ³ΠΈ).
199
+
200
+ Π”Π°ΠΌΠΏ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΏΠΎ admin_token:
201
+ ```ruby
202
+ FuryDumper.dump(password: 'password',
203
+ host: 'localhost',
204
+ port: '5632',
205
+ user: 'username',
206
+ model_name: 'User',
207
+ field_name: 'admin_token',
208
+ field_values: [admin_token_value],
209
+ database: 'staging',
210
+ debug_mode: :short)
211
+ ```
212
+ Π”Π°ΠΌΠΏ 1000 ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ (здСсь ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠ΄ΠΊΡ€ΡƒΡ‚ΠΈΡ‚ΡŒ batch_size - ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ batch_size = 100, Ρ‡Ρ‚ΠΎΠ± Π΄Π°ΠΌΠΏΠ΅Ρ€ ΠΎΡ‚Ρ€Π°Π±ΠΎΡ‚Π°Π» Π½Π΅ 10 Ρ€Π°Π·):
213
+ ```ruby
214
+ FuryDumper.dump(password: 'password',
215
+ host: 'localhost',
216
+ port: '5632',
217
+ user: 'username',
218
+ model_name: 'User',
219
+ field_values: (500..1500),
220
+ database: 'staging',
221
+ debug_mode: :short)
222
+ ```
223
+ Π”Π°ΠΌΠΏ AdminUser:
224
+ ```ruby
225
+ FuryDumper.dump(password: 'password',
226
+ host: 'localhost',
227
+ port: '5632',
228
+ user: 'username',
229
+ model_name: 'AdminUser',
230
+ field_values: 3368,
231
+ database: 'staging',
232
+ debug_mode: :short)
233
+ ```
234
+
235
+ Π”Π°ΠΌΠΏ со стСйдТа:
236
+
237
+ ```bash
238
+ ssh -NL <port>:<host>:<hostport> username@<host>
239
+ ```
240
+
241
+ ```ruby
242
+ FuryDumper.dump(password: 'password',
243
+ host: 'localhost',
244
+ port: '5632',
245
+ user: 'username',
246
+ model_name: 'User',
247
+ field_values: 1,
248
+ database: 'staging',
249
+ debug_mode: :short)
250
+ ```
251
+ Π”Π°ΠΌΠΏ с Ρ€Π΅ΠΏΠ»ΠΈΠΊΠΈ ΠΏΡ€ΠΎΠ΄Π°:
252
+
253
+ ```bash
254
+ ssh -NL <port>:<host>:<hostport> username@<host>
255
+ ```
256
+
257
+ ```ruby
258
+ FuryDumper.dump(password: 'password',
259
+ host: 'localhost',
260
+ port: '5632',
261
+ user: 'username',
262
+ model_name: 'User',
263
+ field_values: 1,
264
+ database: 'production',
265
+ debug_mode: :short)
266
+ ```
267
+
268
+ ### Бтатистика πŸ“ˆ
269
+
270
+ Бтатистика Π΄Π°ΠΌΠΏΠ° с Ρ€Π΅ΠΏΠ»ΠΈΠΊΠΈ main service (конфигурация стандартная, см [этот ΠΊΠΎΠ½Ρ„ΠΈΠ³](README.ru.md#ΠΊΠΎΠ½Ρ„ΠΈΠ³ΠΈ))
271
+
272
+ | ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ user | ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ связанных сущностСй | ВрСмя |
273
+ | --- | --- | --- |
274
+ | 1 | ~ 150* | 2 min 14 sec |
275
+ | 10 | ~ 3 500* | 6 min 15 sec |
276
+ | 100 | ~ 10 000* | 11 min 8 sec |
277
+ | 1 000 | ~ 10 000* | 16 min 6 sec |
278
+
279
+ \* ΠžΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ Ρ‚Π°Ρ‰Π°Ρ‚ΡŒΡΡ нСсколько Ρ€Π°Π· ΠΏΠΎ Ρ€Π°Π·Π½Ρ‹ΠΌ путям ΠΈ ΠΌΠΎΠ³ΡƒΡ‚ Π΄ΡƒΠ±Π»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ ΠΌΠ΅ΠΆΠ΄Ρƒ собой, ΠΈΠ·-Π·Π° этого прСдставлСнноС Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅ число ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Ρ€Π°Π²Π½ΠΎ количСству ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹Ρ… записСй Π² Π‘Π”.
280
+
281
+ ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅: ВрСмя выполнСния ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΡ‚Π»ΠΈΡ‡Π°Ρ‚ΡŒΡΡ для Ρ€Π°Π·Π½Ρ‹Ρ… ΡŽΠ·Π΅Ρ€ΠΎΠ² Π² зависимости ΠΎΡ‚ количСства связанных сущностСй.
282
+
283
+ # ДокумСнтация для Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ²
284
+
285
+ БокращСния для большСго удобства
286
+ * PK - primary key
287
+ * FK - foreign key
288
+ * remote DB - удалСнная Π‘Π”, ΠΈΠ· ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π±ΡƒΠ΄ΡƒΡ‚ Ρ‚ΡΠ½ΡƒΡ‚ΡŒΡΡ Π΄Π°Π½Ρ‹Π΅
289
+ * target DB - тСкущая Π‘Π”, Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Π±ΡƒΠ΄Π΅Ρ‚ осущСствлСно ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅
290
+
291
+
292
+ ## ΠžΠ±Ρ…ΠΎΠ΄ Π³Ρ€Π°Ρ„Π° связСй
293
+
294
+ Π’ Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½ΠΎ 2 Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π° ΠΎΠ±Ρ…Π»Π΄Π° Π³Ρ€Π°Ρ„Π° связСй - Π² Π³Π»ΡƒΠ±ΠΈΠ½Ρƒ ΠΈ Π² ΡˆΠΈΡ€ΠΈΠ½Ρƒ.
295
+
296
+ ### ΠžΠ±Ρ…ΠΎΠ΄ Π² Π³Π»ΡƒΠ±ΠΈΠ½Ρƒ
297
+
298
+ Как Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌ Π²ΠΊΡ€Π°Ρ‚Ρ†Π΅:
299
+
300
+ 1. Находим модСль
301
+ 2. Находим всС связи ΠΌΠΎΠ΄Π΅Π»ΠΈ
302
+ 3. Для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ связи:
303
+ 1. Находим всС Π΄Π°Π½Π½Ρ‹Π΅ (PK/FK, ΠΈΡ… значСния ΠΈ Ρ‚Π΄)
304
+ 2. Π”Π°ΠΌΠΏΠ°Π΅ΠΌ Π½Π°ΠΉΠ΄Π΅Π½Π½ΡƒΡŽ связь
305
+
306
+ Π’ΠΎ Π΅ΡΡ‚ΡŒ классичСский ΠΎΠ±Ρ…ΠΎΠ΄ Π² Π³Π»ΡƒΠ±ΠΈΠ½Ρƒ
307
+
308
+ ![Depth_first_example](Depth-first.png)
309
+
310
+ ### ΠžΠ±Ρ…ΠΎΠ΄ Π² ΡˆΠΈΡ€ΠΈΠ½Ρƒ
311
+
312
+ Как Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌ Π²ΠΊΡ€Π°Ρ‚Ρ†Π΅:
313
+
314
+ 1. ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ модСль для Π΄Π°ΠΌΠΏΠ° (Π²Ρ…ΠΎΠ΄Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅) ΠΈ добавляСм Π΅Π΅ Π² ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ
315
+ 2. Пока Π² ΠΎΡ‡Π΅Ρ€Π΅Π΄ΠΈ Π΅ΡΡ‚ΡŒ ΠΌΠΎΠ΄Π΅Π»ΠΈ
316
+ 1. Π’Π΅ΠΊΡƒΡ‰Π΅ΠΉ модСлью считаСм ΠΏΠ΅Ρ€Π²ΡƒΡŽ Π² ΠΎΡ‡Π΅Ρ€Π΅Π΄ΠΈ
317
+ 2. ΠšΠΎΠΏΠΈΡ€ΡƒΠ΅ΠΌ Π΄Π°Π½Π½ΡƒΡŽ модСль ΠΈΠ· remote DB
318
+ 3. Для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ связи Π΄Π°Π½Π½ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ:
319
+ 1. Находим всС Π΄Π°Π½Π½Ρ‹Π΅ (PK/FK, ΠΈΡ… значСния ΠΈ Ρ‚Π΄)
320
+ 2. ΠŸΠΎΠΌΠ΅Ρ‰Π°Π΅ΠΌ ΡΠ²ΡΠ·Π°Π½Π½ΡƒΡŽ модСль Π² ΠΊΠΎΠ½Π΅Ρ† ΠΎΡ‡Π΅Ρ€Π΅Π΄ΠΈ
321
+
322
+ Π’ΠΎ Π΅ΡΡ‚ΡŒ классичСский ΠΎΠ±Ρ…ΠΎΠ΄ Π² ΡˆΠΈΡ€ΠΈΠ½Ρƒ
323
+
324
+ ![Breadth_first_example](Breadth-first.png)
325
+
326
+ По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ Π΄Π°ΠΌΠΏΠ΅Ρ€ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π² ΡˆΠΈΡ€ΠΈΠ½Ρƒ. Π­Ρ‚ΠΎ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ принято, Π² связи c Ρ‚Π΅ΠΌ, Ρ‡Ρ‚ΠΎ Π΄Π°ΠΌΠΏΠ΅Ρ€ считаСт Π±Π»ΠΈΠΆΠ½ΠΈΠ΅ связи Π±ΠΎΠ»Π΅Π΅ ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚Π½Ρ‹ΠΌΠΈ.\
327
+ Но ΠΌΠΎΠΆΠ½ΠΎ явно Π·Π°ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ Π΄Π°ΠΌΠΏΠ΅Ρ€ Ρ€Π°Π±ΠΎΡ‚Π°ΡŒ Π² Π³Π»ΡƒΠ±ΠΈΠ½Ρƒ, ΡƒΠΊΠ°Π·Π°Π² Π² ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠΌ Ρ„Π°ΠΉΠ»Π΅ `fury_dumper.yml` строку `mode: depth`.
328
+
329
+ ## ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° связСй Ρƒ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ
330
+
331
+ Π£ ΠΊΠ°ΠΆΠ΄ΠΎΠΉ рассматриваСмой ΠΌΠΎΠ΄Π΅Π»ΠΈ Π΅ΡΡ‚ΡŒ мноТСство связСй, ΠΌΡ‹ рассматриваСм практичСски всС. Π’ΠΎΡ‚ список рассматриваСмых свзяСй:
332
+ * has_one ΠΈ has_many (рассматриваСм вмСстС; has_one Π½Π΅ учиываСтся ΠΊΠ°ΠΊ LIMIT 1, Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Ρ‹Π²Π°ΡΡΡŒ Π² has_many)
333
+ * belongs_to
334
+ * has_and_belongs_to_many
335
+
336
+ Но Π΅ΡΡ‚ΡŒ нСсколько ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΉ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ связи through.
337
+
338
+ И Π½Π΅ΠΌΠ½ΠΎΠΆΠΊΠΎ ΠΎ скоупах Π² связи - ΠΎΠ½ΠΈ ΡƒΡ‡ΠΈΡ‚Ρ‹Π²Π°ΡŽΡ‚ΡΡ. Но Ссли Π΅ΡΡ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ ΡˆΠΈΡ€ΠΎΠΊΠ°Ρ (ΠΏΠΎΠΊΡ€Ρ‹Π²Π°ΡŽΡ‰Π°Ρ связь) - Π±Π΅Π· sсope, Ρ‚ΠΎ Π±ΡƒΠ΄Π΅Ρ‚ Π΄Π°ΠΌΠΏΠ°Ρ‚ΡŒΡΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎΠΊΡ€Ρ‹Π²Π°ΡŽΡ‰Π°Ρ связь.
339
+
340
+ НапримСр - Ρƒ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π΅ΡΡ‚ΡŒ всС Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ ΠΈ Π³Π»Π°Π²Π½Ρ‹ΠΉ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚:
341
+ * has_many :documents, class_name: 'User::Document'\
342
+ * has_one :main_document, -> { main }, class_name: 'User::Document'
343
+
344
+ Бвязь main_document Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ ΡƒΡ‡Ρ‚Π΅Π½Π° ΠΏΡ€ΠΈ Π΄Π°ΠΌΠΏΠ΅ Π² связи с Ρ‚Π΅ΠΌ, Ρ‡Ρ‚ΠΎ documents - ΠΏΠΎΠΊΡ€Ρ‹Π²Π°ΡŽΡ‰Π°Ρ связь, Ρ‚Π°ΠΊ ΠΊΠ°ΠΊ Π±ΠΎΠ»Π΅Π΅ ΡˆΠΈΡ€ΠΎΠΊΠ°Ρ ΠΈ Π±Π΅Π· sсope.\
345
+ Если Π±Ρ‹ связи documents Π½Π΅ Π±Ρ‹Π»ΠΎ, main_document дампалась Π±Ρ‹ с условиСм.
346
+
347
+ Π’Π°ΠΊΠΆΠ΅ Ρƒ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ ΡƒΡ‡ΠΈΡ‚Ρ‹Π²Π°ΡŽΡ‚ΡΡ ΠΏΠΎΠ»ΠΈΠΌΠΎΡ€Ρ„Π½Ρ‹Π΅ связи ( `belongs_to :resource, polymorphic: true` ΠΈ `has_many :devices, as: :owner` ).
348
+
349
+ ### ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° связСй has_and_belongs_to_many
350
+
351
+ Бвязи has_and_belongs_to_many ΠΈΠΌΠ΅ΡŽΡ‚ ΠΏΡ€ΠΎΠΊΡΠΈΡ€ΡƒΡŽΡ‰ΡƒΡŽ Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ, ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Ρ‚ΠΎΠΆΠ΅ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡΠ΄Π°ΠΌΠΏΠ°Ρ‚ΡŒ. Π­Ρ‚ΠΎ происходит Π² ΠΌΠΎΠΌΠ΅Π½Ρ‚ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ Ρƒ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π΅ΡΡ‚ΡŒ данная связь.
352
+
353
+ ### ΠžΡΠΎΠ±Π΅Π½Π½ΠΎΡΡ‚ΠΈ Ρ€Π°Π±ΠΎΡ‚Ρ‹ c as-связями
354
+
355
+ Бвязи Ρ‚ΠΈΠΏΠ° as ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°ΡŽΡ‚ΡΡ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΎΡ‚Π»ΠΈΡ‡Π½ΠΎ ΠΎΡ‚ ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Ρ…. Π’ связи с Ρ‚Π΅ΠΌ, Ρ‡Ρ‚ΠΎ ΠΊ этой Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ мноТСство связСй ΠΈ ΠΎΠ½ΠΈ Π½Π΅ Π±ΡƒΠ΄ΡƒΡ‚ Π΄ΡƒΠ±Π»ΠΈΡ€ΡƒΡŽΡ‰ΠΈΠΌΠΈΡΡ, нСся каТдая свой смысл ΠΈ ΠΏΠΎΡ‚Π΅Ρ€ΡΡ‚ΡŒ ΠΈΡ… нСльзя.\
356
+ НапримСр, связь для ΡŽΠ·Π΅Ρ€Π° `has_many :devices, as: :owner` ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈΡΡƒΡ‚ΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ ΠΈ Ρƒ Π»ΠΈΠ΄Π°. И Π² идСальой всСлСнной πŸ¦„ Π½ΡƒΠΆΠ½ΠΎ Π²Ρ‹Ρ‚Π°Ρ‰ΠΈΡ‚ΡŒ ΠΎΠ±Π΅.\
357
+ Для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ± Π΄Π°ΠΌΠΏΠ°Ρ‚ΡŒ ΠΎΠ±Π΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ, Π±Ρ‹Π»ΠΎ принято Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ Π·Π°ΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ ΠΏΡƒΡ‚ΡŒ связСй (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ as) ΠΏΠΎ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ ΠΏΡ€ΠΈΡˆΠ»Π° модСль. И Ссли ΠΎΠ΄ΠΈΠ½ ΠΈΠ· ΠΏΡƒΡ‚Π΅ΠΉ являСтся ΠΏΠΎΠ΄ΠΏΡƒΡ‚Π΅ΠΌ для Π΄Ρ€ΡƒΠ³ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ, Ρ‚ΠΎ ΠΎΠ½ΠΈ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ ΠΈ Π½Π΅ Π±ΡƒΠ΄ΡƒΡ‚ Π΄Π°ΠΌΠΏΠ°Ρ‚ΡŒΡΡ.
358
+
359
+ ## Π Π΅ΠΆΠΈΠΌ быстрых Π·Π°ΠΏΡ€ΠΎΠΎΠ²
360
+
361
+ Π Π΅ΠΆΠΈΠΌ быстрых sql-запросов Ρ€Π°Π±ΠΎΠ°Ρ‚Π΅Ρ‚ Π±Π΅Π· сортировки ΠΏΠΎ ΠΏΠ΅Ρ€Π²ΠΈΡ‡Π½ΠΎΠΌΡƒ ΠΊΠ»ΡŽΡ‡Ρƒ. Если Π²Ρ‹ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡΠ΄Π°ΠΌΠΏΠ°Ρ‚ΡŒ **послСдниС** записи Π² ΠΌΠΎΠ΄Π΅Π»ΠΈ, установитС `fast: false` Π² ΠΊΠΎΠ½Ρ„ΠΈΠ³Π΅. \
362
+ Π’ быстром Ρ€Π΅ΠΆΠΈΠΌΠ΅ sql запросы выглядят Ρ‚Π°ΠΊ:
363
+ ```sql
364
+ SELECT * FROM table WHERE fk_id IN (...) LIMIT 1000;
365
+ ```
366
+ Π’ Π½Π΅ быстром Ρ€Π΅ΠΆΠΈΠΌΠ΅ sql запросы выглядят Ρ‚Π°ΠΊ:
367
+ ```sql
368
+ SELECT * FROM table WHERE fk_id IN (...) ORDER BY table.id LIMIT 1000
369
+ ```
370
+ Но нСбыстрый Ρ€Π΅ΠΆΠΈΠΌ Π΄Π΅Π»Π°Π΅Ρ‚ запросы ΠΌΠ΅Π΄Π»Π΅Π½Π½Π΅Π΅ ΠΈΠ·-Π·Π° Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎ pg-ΠΏΠ»Π°Π½ΠΈΡ€ΠΎΠ²Ρ‰ΠΈΠΊ строит запрос ΠΏΠΎ индСксу ΠΏΠ΅Ρ€Π²ΠΈΡ‡Π½ΠΎΠ³ΠΎ ΠΊΠ»ΡŽΡ‡Π°, Π° fk_id IN (...) Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΡƒΠ΅Ρ‚ ΠΏΡ€ΠΈ сортировкС, Ρ‡Ρ‚ΠΎ Ρ€Π°Π±ΠΎΡ‚Π΅Ρ‚ дольшС.
371
+
372
+ ## ΠšΡ€Π°Ρ‚ΠΊΠΎ ΠΎ классах
373
+
374
+ * FuryDumper - ΠΈΠ½ΠΈΡ†ΠΈΠΈΡ€ΡƒΠ΅Ρ‚ процСсс Π΄Π°ΠΌΠΏΠ°, осущСствляСт Π±Π°Ρ‚Ρ‡ΠΈΠ½Π³ Π½Π° ΠΏΠ΅Ρ€Π²ΠΎΠΉ ΠΈΡ‚Π΅Ρ€Π°Ρ†ΠΈΠΈ
375
+ * FuryDumper::Dumper - основной класс, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ осущСствляСт процСсс Π΄Π°ΠΌΠΏΠ°, здась Π»Π΅ΠΆΠΈΡ‚ основной Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌ ΠΎΠ±Ρ…ΠΎΠ΄Π° связСй
376
+ * FuryDumper::Dumpers::Model - класс модСли
377
+ * FuryDumper::Dumpers::ModelQueue - ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ для Π΄Π°ΠΌΠΏΠ° Π² ΡˆΠΈΡ€ΠΈΠ½Ρƒ
378
+ * FuryDumper::Dumpers::DumpState - класс состояния Π΄Π°ΠΌΠΏΠ°, Ρ‚ΡƒΡ‚ хранится информация ΠΎ Ρ‚Π΅Ρ… модСлях, Ρ‡Ρ‚ΠΎ ΡƒΠΆΠ΅ Π΄Π°ΠΌΠΏΠ°Π»ΠΈ ΠΈ подводится нСбольшая статистика ΠΏΠΎ Π΄Π°ΠΌΠΏΡƒ
379
+ * FuryDumper::Dumpers::RelationItem - структура связи - ΠΊΠ»ΡŽΡ‡ΠΈ ΠΈ значСния, ΠΏΠΎ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ Π΄Π°ΠΌΠΏΠ°Π»ΠΈ. Для ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹Ρ… ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Π² RelationItem сравниваСтся ΠΌΠ΅ΠΆΠ΄Ρƒ собой Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎ ΠΊΠ»ΡŽΡ‡Π°ΠΌ. Additional опция Π΄Π°Π΅Ρ‚ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΡΡ€Π°Π²Π½ΠΈΡ‚ΡŒ ΠΏΠΎ ΠΊΠ»ΡŽΡ‡Ρƒ ΠΈ Π·Π½Π°Ρ‡Π΅Π½ΠΈΡŽ. Complex - явно Π³ΠΎΠ²ΠΎΡ€ΠΈΡ‚ ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ Π±ΡƒΠ΄Π΅Ρ‚ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΊΠ»ΡŽΡ‡, Ρ‚ΠΎ Π΅ΡΡ‚ΡŒ Π² ΠΊΠ»ΡŽΡ‡Π΅ содСрТится строка Ρ‚ΠΈΠΏΠ° `date_from IS NULL`, ΡΠ²Π»ΡΡŽΡ‰Π°ΡΡΡ условиСм для связи.
380
+ * FuryDumper::Api - класс для связи с микросСрвисами
381
+ * FuryDumper::Config - класс ΠΊΠΎΠ½Ρ„ΠΈΠ³Π°
382
+ * FuryDumper::Encrypter - класс для ΡˆΠΈΡ„Ρ€ΠΎΠ²Π°Π½ΠΈΡ ΠΏΠ°Ρ€ΠΎΠ»Π΅ΠΉ
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FuryDumper
4
+ class DumpProcessController < ApplicationController
5
+ def dump
6
+ data = JSON.parse(request.body.read)
7
+ FuryDumper.dump(password: Encrypter.decrypt(data['password']),
8
+ host: data['host'],
9
+ port: data['port'],
10
+ user: data['user'],
11
+ database: data['database'],
12
+ model_name: data['model_name'],
13
+ field_values: data['field_values'],
14
+ field_name: data['field_name'],
15
+ debug_mode: :none,
16
+ ask: false)
17
+
18
+ render json: { message: :ok }
19
+ end
20
+
21
+ def health
22
+ render json: { message: :ok }
23
+ end
24
+ end
25
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ FuryDumper::Engine.routes.draw do
4
+ get '/health', to: 'dump_process#health'
5
+ post '/dump', to: 'dump_process#dump'
6
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/fury_dumper/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'fury_dumper'
7
+ spec.version = FuryDumper::VERSION
8
+ spec.authors = ['Nastya Patutina']
9
+ spec.email = ['npatutina@gmail.con']
10
+
11
+ spec.summary = 'Simple dump for main service and other microservices'
12
+ spec.description = 'Dump from remote DB by lead_ids interval'
13
+ spec.homepage = 'https://github.com/NastyaPatutina/fury_dumper'
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') # rubocop:disable Gemspec/RequiredRubyVersion
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/NastyaPatutina/fury_dumper'
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.add_development_dependency 'bundler', '~> 2.0'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+
32
+ spec.add_runtime_dependency 'highline', '~> 1.6'
33
+ spec.add_runtime_dependency 'httpclient', '~> 2.8'
34
+ spec.add_runtime_dependency 'pg', '~> 1.4'
35
+ spec.add_runtime_dependency 'rails', '~> 5.0', '>= 4.0.13'
36
+ spec.metadata['rubygems_mfa_required'] = 'true'
37
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FuryDumper
4
+ class Api
5
+ HEALTH_URL = 'fury_dumper/health'
6
+
7
+ # @param ms_name[String] - name of microservice for request
8
+ def initialize(ms_name)
9
+ @ms_name = ms_name
10
+ @http_client = init_http_client(ms_name)
11
+ @ms_config = FuryDumper::Config.fetch_service_config(@ms_name)
12
+ end
13
+
14
+ # Send dumping request to microservice
15
+ #
16
+ # @param ms_model[String] - name of table to start dumping
17
+ # @param ms_field_name[String] - field in table for find records for dump
18
+ # @param field_values[Array] - values of access_way for dumping
19
+ #
20
+ # @example FuryDumper::Dumper.dump("users", "id", [1,2,3])
21
+ def send_request(ms_model, ms_field_name, field_values)
22
+ # Check gem is included to microservice(ms)
23
+ return unless check_ms_health
24
+
25
+ message = {
26
+ model_name: ms_model,
27
+ field_name: ms_field_name,
28
+ field_values: field_values,
29
+ password: Encrypter.encrypt(@ms_config['password']),
30
+ host: @ms_config['host'],
31
+ port: @ms_config['port'],
32
+ user: @ms_config['user'],
33
+ database: @ms_config['database']
34
+ }.to_json
35
+
36
+ response = @http_client.post('fury_dumper/dump', message)
37
+
38
+ ok_responce?(response)
39
+ rescue StandardError => e
40
+ notify_error(e)
41
+ nil
42
+ end
43
+
44
+ def check_ms_health
45
+ response = @http_client.get('fury_dumper/health')
46
+
47
+ ok_responce?(response)
48
+ rescue StandardError => e
49
+ notify_error(e)
50
+ false
51
+ end
52
+
53
+ def notify_error(error)
54
+ context = { error: error }
55
+ BugTracker.notify error_message: "НС смогли ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· #{@ms_name}", context: context
56
+ end
57
+
58
+ def init_http_client(ms_name)
59
+ base_url = Rails.application.class.parent_name.constantize.config.deep_symbolize_keys[ms_name.to_sym][:endpoint]
60
+ HTTPClient.new(base_url: base_url, default_header: default_header)
61
+ rescue StandardError => e
62
+ notify_error(e)
63
+ nil
64
+ end
65
+
66
+ def default_header
67
+ {
68
+ 'Content-Type' => 'application/x-www-form-urlencoded, charset=utf-8',
69
+ 'Authorization' => 'Bearer 40637702df32be88886c7083c4fdb075',
70
+ 'Accept' => 'application/x-protobuf,application/json'
71
+ }
72
+ end
73
+
74
+ def ok_responce?(response)
75
+ unless response.status == 200
76
+ raise "[#{@ms_name}] invalid response status => #{response.status}/#{response.body.inspect}"
77
+ end
78
+
79
+ true
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module FuryDumper
6
+ class Config
7
+ MODES = %i[wide depth].freeze
8
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].freeze
9
+
10
+ def self.config
11
+ @config || {}
12
+ end
13
+
14
+ def self.load(file)
15
+ @config = ::YAML.safe_load(file.respond_to?(:read) ? file : File.open(file))
16
+ validate_config
17
+ end
18
+
19
+ def self.tables
20
+ return @tables if @tables
21
+
22
+ @tables = []
23
+ relative_services&.each do |_ms_name, ms_config|
24
+ @tables += ms_config['tables'].keys
25
+ end
26
+ @tables
27
+ end
28
+
29
+ def self.batch_size
30
+ @batch_size ||= (config['batch_size'] || 100).to_i
31
+ end
32
+
33
+ def self.limit
34
+ batch_size * ratio_records_batches
35
+ end
36
+
37
+ def self.ms_relations?(table_name)
38
+ tables.include?(table_name)
39
+ end
40
+
41
+ def self.exclude_relation?(relation_name)
42
+ exclude_relations.include?(relation_name)
43
+ end
44
+
45
+ def self.mode
46
+ @mode ||= config['mode'].to_sym if !@mode && MODES.include?(config['mode']&.to_sym)
47
+
48
+ @mode ||= :wide
49
+ end
50
+
51
+ def self.fetch_service_config(ms_name)
52
+ relative_services[ms_name]
53
+ end
54
+
55
+ def self.relative_services
56
+ config['relative_services']
57
+ end
58
+
59
+ def self.fast?
60
+ !config['fast'].in?(FALSE_VALUES)
61
+ end
62
+
63
+ def self.validate_config
64
+ return true unless relative_services
65
+
66
+ relative_services.each do |ms_name, ms_config|
67
+ check_presented(ms_config, "[#{ms_name}]")
68
+ %w[database host port user password].each do |required_field|
69
+ check_required_key(ms_config, required_field, "[#{ms_name}]")
70
+ end
71
+
72
+ check_presented(ms_config['tables'], "[#{ms_name}] tables")
73
+ validate_tables_config(ms_config['tables'], ms_name)
74
+ end
75
+
76
+ true
77
+ end
78
+
79
+ def self.validate_tables_config(config, ms_name)
80
+ config.each do |this_table, table_config|
81
+ check_presented(table_config, "[#{ms_name}] -> #{this_table}")
82
+
83
+ table_config.each do |ms_table, ms_table_config|
84
+ check_presented(ms_table_config, "[#{ms_name}] -> #{this_table} -> #{ms_table}")
85
+
86
+ %w[self_field_name ms_model_name ms_field_name].each do |required_field|
87
+ check_required_key(ms_table_config, required_field, "[#{ms_name}] -> #{this_table} -> #{ms_table}")
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def self.check_presented(config, prefix)
94
+ return if config.present?
95
+
96
+ raise "Configuration error! #{prefix} isn't describe"
97
+ end
98
+
99
+ def self.check_required_key(config, field, prefix)
100
+ return if config[field]
101
+
102
+ raise "Configuration error! #{prefix} #{field} expected"
103
+ end
104
+
105
+ def self.ratio_records_batches
106
+ @ratio_records_batches ||= (config['ratio_records_batches'] || 10).to_i
107
+ end
108
+
109
+ def self.exclude_relations
110
+ @exclude_relations ||= config['exclude_relations']&.split(',')&.map(&:strip) || []
111
+ end
112
+ end
113
+ end