fury_dumper 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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