пятница, 28 августа 2015 г.

Сказ о том как мы выливались до 3х часов ночи

Однажды у нас появилась относительно несложная (на вид) задача - отображать прогресс перевода документа не по количеству переведённых сегментов (относительно небольшие куски на которые разбивается документ для более удобной обработки), а по количеству слов в этих сегментах. Идея здравая, информация насчёт слов у нас вроде бы и так была. В целом казалось что сделать всё проще простого.
Однако реальность внесла свои коррективы. Сразу же выяснились 2 вещи:
1. Старая статистика хранилась в уже агрегированном виде в таблице документов и обновлялась при каждом изменении сегмента (что увеличивает количество запросов при обновлении, потенциально опасно и ухудшает и так не слишком читаемый код внутри персистентности, но это лирика)
2. Количество слов у сегмента у нас было только в C# коде (высчитывалось по примитивному алгоритму) - в базе эти данные не хранились, только общий агрегат.

Так что мы поняли что придётся всё же писать количество слов для каждого сегмента + обновлять кусок, который считает статистику для документа.
Это был один из первых звоночков, когда я стал понимать что так дальше жить нельзя - вся логика работы с базой была размазана по немалому количеству хранимых процедур, причем искать зависимости в базе - это совсем не find usages внутри студии. При этом даже статистика вытаскивалась как минимум двумя разными хранимками (мы с этим отлавливали баги - в одном месте были одни значения, а в другом - другие).
Но в общем и целом кое-как была таки дополнена логика на сервере, на клиенте, написана миграция базы переписывающая штук 5 хранимых процедур и вьюшку, а так же добавляющая недостающую колонку. Ну и следующей миграцией (накатываемой сразу же после первой) был написан SQL скрипт, который с помощью несложной функции считающей количество слов (аналог string.Split('  ').Count() в нашем коде) проходил по всем сегментам и прописывал количество слов в них.
После довольно немалого (как для такой несложной фичи) периода стабилизации на тестовом сервере мы таки решили выливаться в продакшн. По грубым прикидкам (банально поленились полезть и посмотреть, положившись на память) на продакшене было раза в 3-4 больше данных чем на тестовом сервере. Учитывая что скрипт на тестовом сервере шел порядка 5-10 минут, мы решили что в целом ничего ужасного нет и мы вполне можем подождать полчасика.

И вот наступает день Х. Мы ждём пока выльется другая команда, у них это затягивается до 10-11 вечера (как обычно, впрочем). После чего начинаем свою выливку - быстренько выливаем код и запускаем миграцию. Первая миграция (со структурой базы) проходит быстро, а вот вторая как-то затягивается - 30 минут, 40... В итоге примерно к концу часа, миграция падает с довольно невнятным сообщением об ошибке. Ладно, пробуем прогнать её еще раз и попутно пытаемся разобраться в чем там было дело. Толком непонятно, но к концу следующего часа миграция опять падает. Становится понятно что это не случайная ошибка, а какой-то систематический просчёт у нас в миграции.
Хуже того, Sql сервер начинает как-то уж очень плохо шевелиться, причем не только на нашей базе. Делать нечего, вызваниеваем одного из админов. Он сообщает что раздел диска выделенный под TempDB забит под завязку. После чего добавляет туда места, рестартует сервер и вроде бы всё начинает нормально работать. Уже около половины второго ночи. Мы выдыхаем чуть спокойнее и решаем что нам необязательно "вот прямо сейчас" иметь все данные обновлёнными и поэтому достаточно будет если мы смигрируем документы только за последние несколько месяцев, а остальное отложим на завтра. Собственно, это мы и сделали - в ручном режиме (запустив скрипт напрямую) прогоняем миграцию только части данных, быстренько прогоняем тесты и разъезжаемся по домам.
Утром я быстренько набросал консольное приложеньице, которое мигрирует сегменты подокументно (т.е. пачками 10-10к штук) и уже в рабочее время в течении дня прогнал миграцию, на всякий случай попросив админов мониторить нагрузку на сервер (там всё оказалось нормально).
Т.е. в общем и целом - особо ужасного конечно ничего не случилось - вряд ли много пользователей страдали от того что наш сервис плохо работал в районе полуночи, никаких данных мы не потеряли и к утру большинство сценариев даже отрабатывали как надо.
Но для нас это было неприятно и нетипично - крайне редко выливки у нас длились дольше чем до 20:00, при том что начинались не раньше чем в 18:30, а 90% времени тратилось на регрессивное тестирование автотестом + немного руками.

Так что мы сделали кое-какие выводы:
1. Иногда размер (базы) таки имеет значение. То что довольно легко проходит на тестовых данных, может застрять на реальных объёмах.
2. Всегда стоит внимательнее посмотреть - насколько отличаются размеры тестовых данных и насколько может деградировать производительность с ростом их количества. И если миграция на тестовом сервере проходит больше 3-5 минут, то это уже повод для беспокойства, особенно если при увеличении количества записей время растёт нелинейно (проще всего это проверить запустив миграцию сперва на части тестовых данных, а потом на всех).
3. Слона лучше есть по частям - не стоило запихивать такие масштабные изменения в один запрос. Именно это привело к распуханию TempDB. Так что следующие масштабные миграции мы решили делать в фоне и подокументно, т.е. относительно небольшими порциями. Кстати, мне стоило послушать Лёху, который предлагал это изначально ;)
4. Не совсем относящийся к миграции пункт - слой работы с базой был крайне плохоподдерживаемым. И мы затратили уйму сил чтобы всё это заработало и заработало без багов. И это на довольно простой задаче. Так что зачастую косвенные затраты на внесение новых фич в плохо поддерживаемых участках кода могут в совокупности довольно быстро перевесить затраты на нормальное переписывание. Надо будет не забыть про этот аргумент в следующий раз, когда буду выбивать время на рефакторинг кода.

Так что одним из моих сильнейших желаний стало желание переписать слой работы с базой  по человечески. Но это случилось уже примерно через полгода, до того момента мы пытались жить с тем что было. Да и довольно сложно выбить около месяца работы на задачу не приносящую никакой прямой пользы бизнесу.

среда, 26 августа 2015 г.

О Persistence ignorance и проблеме контроля изменений в слое работы с базой

О Persistence ignorance и проблеме контроля изменений в слое работы с базой.
В огромном количестве умных книжек по доменным моделям постоянно твердят о т.н. “Persistence ignorance” - утверждении о том, что доменная модель должна строиться без оглядки на базу данных. Мысль конечно правильая, вот только в качестве реализации почему-то всегда предлагают сделать ORM маппинги для доменной модели и делать вид что она при этом никак с персистентностью не связана, “оно само” работает с помощью ORM.
Однако даже в простейших “книжных” примерах или в демонстрационных видео видно что уже сразу начинаются всяческие уступки для того чтобы ORM заработала - virtual свойства, proteсted конструкторы без параметров (для того чтобы была возможность заполнить поля при вычитке из базы), использование обратных ссылок на родителей в дочерних объектах (public ParentClass Parent {get; set;}) для того чтобы включить инверсное сохранение дочерних объектов так как оно работает в СУБД (проблема one-to-many) и т.д. и т.п. Т.е. делается множество относительно мелких, но всё же вполне заметных допущений и “подпиливаний напильником” для того чтобы суметь втиснуть доменную модель в рамки ORM и работы с базой. Причем допущения эти в целом довольно неявные и неочевидные, но без них персистентность перестаёт работать.
И это еще в хорошем случае если доменная модель и структура базы совпадают между собой. Когда же начинают натягивать что-то не совпадающее 1 в 1, то в ход идёт тяжелая артиллерия - всяческие ухищрения со стороны маппингов и настроек ORM. В этом случае персистентность становится неимоверно хрупкой. И без знания этих деталей сделать изменения в базе или доменной модели становится очень опасно - могут возникать ошибки в абсолютно неочевидных местах.
Поэтому, лично моё мнение что в таком виде Persistence ignorance - это миф, слишком уж разные миры реляционных баз данных и объектов (и коллекций) в памяти.

Что же делать в этом случае? Перестать притворяться!
Т.е. разделить условный слой персистентности на две части:
Часть работы напрямую с базой: объекты-модели максимально точно воспроизводящие строение базы, их маппинги (или напрямую аннотации в классах), контекст работы с базой. Всё это имеет модификаторы internal и снаружи не доступно.
Часть, свободная от персистентности - уже “чистые” классы, которые будут нашим контрактом во внешний мир + публичные статические методы, которые создают, удаляют и обновляют записи в СУБД. Именно они манипулируют нашей первой частью в том виде, в котором нам это нужно.

Т.е. всё это выглядит примерно таким образом:
Сперва мы создаём “образ” нашей СУБД в виде всем понятных и известных классов и маппингов, не пытаясь притворяться что “ложки-базы нет” - это именно что модель сугубо для общения с базой, с публичными сеттерами и прочими вещами не свойственными для хорошей доменной модели. Там же мы создаём internal перегрузку контекста работы с базой (сессия в терминах NHibernate), чем сразу говорим “работа с базой дальше этой сборки не пойдёт”, заодно избавимся от соблазна протянуть Entity/NHibernate через всё приложение.

После этого можно приступать к созданию классов доступных уже за пределами проекта, не завязанного напрямую с базой, не имеющих маппингов, конструкторов без параметров (а то и вовсе не имеющих публичных конструкторов, только private/internal, если нам не надо создавать объекты без сохранения их в базу). 
Лично я создавал вообще read-only объекты, модификация которых была возможна только “через кассу базу” .

Т.е. выглядело это приблизительно так:
public class Document
{
 private readonly Guid id;
 private readonly string title;
 private readonly string fileName;

 internal Document(Guid id, string title, string fileName)
 {
  this.id = id;
  this.title = title;
  this.fileName = fileName;
 }
 
 public Guid Id {get { return id; } }
 public string Title {get { return title; } }
 public string FileName {get { return fileName; } }

 public static Document GetDocument(Guid id)
 {
     using (var context = DbContext.CreateInternal())
     {
         var entity = context.Documents.Find(id);
  
         return ConvertFromEntity(entity); 
     }
  }

  internal static Document ConvertFromEntity(Entities.Document entity)
  {
      return new Document(entity.Id, entity.Title, entity.FileName);
  }
}

При этом внутри этих статических методов может быть любая логика по работе с базой (но желательно только с ней, не стоит домешивать туда доменную) - транзакции, создание/удаление/обновление сопутствующих записей. Т.е. именно то что должно быть на уровне персистентности.

Отдельно стоит упомянуть проблему поддержания всех моделей (при подходе по типу CQRS, где модель для чтения и модель для записи - это две разных модели) в консистентном состоянии. Думаю, для многих не редкость ситуация, когда для того чтобы добавить колонку в таблицу, необходимо не только добавить колонку в таблицу и поле в модель, но еще и добавить её в несколько view и хранимых процедур в базе. Причем узнать какие именно это хранимые процедуры или вьюшки довольно непросто - в Sql Management Studio есть конечно view dependencies, но работает оно мягко говоря совсем не так удобно как Find Usages в студии, не говоря уже о том что это модальное окошко. В итоге, вносить изменения становится намного труднее, а отлавливать баги - куда чаще.

В случае с вышеприведённым подходом, есть возможность использовать одну и ту же инфраструктуру для работы с базой как для чтения, так и для записи, ведь всё равно она лишь повторяет схему базы.
К тому же, по моему опыту - большая часть view - моделей обычно состоят из плоских доменных моделей + каких-либо дополнительных данных (агрегатные данные типа сумм, количества и т.д. + какие-либо дочерние коллекции). Соответственно, никто особо не запрещает использовать либо наследование (хотя его не очень люблю и на свежую голову решил что необходимости в нём тут абсолютно нет) либо композицию и использовать те же самые CreateFromEntity. Что во-первых даёт полный контроль с т.з. системы типов и конструкторов, а во-вторых уменьшает количество точек где надо производить изменения до минимума - в таком случае для того чтобы добавить колонку в базе необходимо будет: добавить собственно колонку, добавить поле в entity для работы с базой, добавить поле во внешний объект и его конструктор, а также в CreateFromEntity (который, кстати, тоже может быть private/internal конструктором).

Т.е. если нам понадобился документ вместе с его содержимым, то мы создаём 


public class DocumentWithEntries
{
 private readonly Document document;
 private readonly IEnumerable<DocumentEntry> entries;

 private static DocumentWithEntries(Document document, IEnumerable<DocumentEntry> entries)
 {
  this.document = document;
  this.entries = entries;
 }

 public Document Document { get { return document; } }
 public IEnumerable<DocumentEntry> {get { return entries; } }

 public static DocumentWithEntries GetDocumentWithEntries(guid id)
 {
  using(var context = DbContext.CreateInternal())
  {
   var documentEntity = context
                           .Documents
                               .Include(x => x.Entries)
                            .FirstOrDefault(x => x.Id == id);
   return ConvertFromEntity(documentEntity, documentEntity.Entries);
  }
 }
 
 private static DocumentWithEntries ConvertFromEntity(Entities.Document document, 
IEnumerable<Entities.DocumentEntry> entryEntities)
 {
  var document = Document.ConvertFromEntity(document);
  var entries = entryEntities.Select(x => DocumentEntry.ConvertFromEntity(x));
  
  return new DocumentWithEntries(document, entries);
 }
}

Т.е. если вдруг понадобится добавить поле в Document, то сперва добавляем его в Document и соответственно его конструктор (ведь поле у нас readonly, иного способа его инициализировать нет). А дальше - у нас перестаёт билдиться ConvertFromEntity, т.к. внутри в вызове конструктора не хватает параметра, вслед за ним тянется Entities.Document (надо же откуда-то брать данные?) и всё! Останется лишь добавить колонку в базу, что в общем-то довольно сложно забыть. Всякие DocumentWithEntries, DocumentWithStatistics и всё что нам еще придёт в голову будут автоматически иметь нужное нам поле. При этом Entity позволяет получать довольно удобные агрегирующие функции прямо из Linq интерфейса путём трансляции этих запросов в базу. Т.е. в 90% случаев скорее всего вполне можно обойтись без Views/Stored Procedures и ручного написания SQL кода.

В качестве вывода хотел бы еще раз подчеркнуть что не стоит пытаться прятать голову в песок и утверждать что мы имеем Persistence Ignorance, потому что мы сумели кое-как натянуть ORM поверх доменных объектов. На мой взгляд лучше не пытаться притворяться и разделить эту часть на две - проекцию базы данных и нормальные доменные сущности, которые уже будут действительно POCO, без прикрученных сбоку маппингов, конструкторов без параметров и т.д. Заодно, поверх этой же проекции можно сделать и View - модели для чтения. Либо смешав их с доменными (т.к. они практически всегда строятся поверх уже имеющихся там данных), либо вынеся в отдельную friendly сборку. Это поможет иметь куда более удобный контроль над изменениями в структуре базы, практически ничего не останется на “это надо запомнить”, всё будет контроллироваться с помощью параметризированных конструкторов и системы типов.

UPD. Тут мне заняли хороший вопрос - а что происходит с этой моделью в дальнейшем и как будет выглядеть модификация данных?
В общем, первоначальным соблазном было сказать что на выходе мы и имеем доменную модель. Но тут возникает вопрос - а что мы хотим получить от доменной модели и что в таком случае будет ею являться?
Если она должна "делать зашибись", включая обновления базы, то наверное можно назвать её и таким образом. Но мне не нравится эта идея, потому как тогда эта доменная модель будет  намертво привязана к персистентности и зачастую смешивать собственную логику с логикой персистентности (во всяком случае соблазн будет крайне велик).
Так что я всё же склоняюсь к тому что это никакая не доменная модель, а просто довольно четко оформленная модель персистентности. А уже потом из неё можно сделать что угодно - и доменную модель и модель для отображения данных и чёрта в ступе. Т.е. опять приходится не лениться и делать разделение между моделями :(
Соответственно, изменения для такой модели тоже могут быть минималистичными с учетом необходимых юзкейсов. Например, если необходимо изменить текст в элементе документа, то вовсе не надо принимать в качестве модели весь элемент документа, достаточно просто получить его Id и новый текст.
public static DocumentEntry UpdateEntryText(Guid id, string newText)
{
    using(var context = DbContext.CreateInternal())
    {
        var entryEntity = context.DocumentEntries.First(x => x.Id == id);
        
        entryEntity.Text = newText;
        context.Save();

        return ConvertFromEntity(entryEntity);
    }
}

Кстати, если вдруг будет необходимость комбинировать разные операции внутри одного более глобального метода, то можно создавать 2 версии метода - internal перегрузку, которая будет принимать контекст и выполнять все действия в нём и public версию, которая будет создавать контекст и вызывать internal часть, которая всё и сделает. Соответственно, если нужно скомбинировать какие-то действия, то будут вызываться internal методы получающие контекст извне. И только внешний public метод будет создавать транзакции и управлять ими. 

вторник, 30 августа 2011 г.

How to get my music back from google

When i got invite to google music i decided to make good thing- write all tags to my music(some songs was too old and hasn't tag, other tags wasn't in unicode). I made big work(about 2-4k of songs needed editing) and was very glad. Except one little thing- i couldn't got my music with pretty tags back!
I started to investigate how i can do this:
1. Any open api from google, may be because this beta, may be copyright
2. Tried to get something from web-app. Any chance- about 460k obfuscated and compressed javascript. Too much work
3. Beta android music app can download files for offline listening. I'm not very good in android< but that was my chance. And it worked- any real protection.
What i found:
Files contains in sd card at directory /sdcard/Anrdoid/data/com.google.android.music/cache/music
Other players don't see it . Thanks xda developers- there need to delete hidden file with name .nomedia.But files there named with 1,2...3459 and contains old tags. So, music application store that data at other place. I found it at /data/data/com.google.android.music/databases/music.dbThis is simple unsecured SQLite base which contains all data about tracks in 'music' table.So, there wasn't big problem to write simple desktop(due to limited resources at android) application which get DB, directory with android music predownloaded mp3's and sort files by directories and with fulfilled tags.

Published with Blogger-droid v1.7.4

воскресенье, 14 июня 2009 г.

Свежие привычки

Преамбула:
Купил себе зимой PocketBook 301 plus. Для тех кто не в курсе-это электронная кижка, которая отображает текст практически как на бумаге. Читает кучу форматов, но дело сейчас не в этом. Есть у нее маленькая особенность-для экономии энергии реализовано автоотключение через 5-10-20 минут. И вот для того чтобы книга не отключилась, нужно нажать какую-либо кнопку.
Амбула:
Дали мне в iStore журнальчик с их рекламкой. Пришел домой и перед чаем решил почитать. Заварил чай, отложил журнал и пью себе спокойно. Тут проскакивает мысль:
"Надо журнальчик почитать, а то закроется!"

вторник, 17 марта 2009 г.

Размышления о механизмах персонализации и анонимности в интернете

Раз уж мне сегодня у меня есть настроение что-то писать, то напишу о своих мыслях насчет развития интернета.
Последнее время наблюдается тенденция к персонализации поиска в интернете. Т.е. скоро повсеместно будет персональная настройка сайтов и их контента. Простейшим примером могут быть новости- система ведет статистику просмотров и фильтрует интересные вам новости. Т.е. если я читаю новости про новинки технологии, то мне не будут показывать новости про Диму Билана и наоборот. Одним из лидеров в разработке этой концепции является гугл. 
Собственно, идея мне очень нравится, кроме одной маленькой вещи: это заставляет пользователя хранить все больше информации о себе в сети. Например про меня еще пару лет назад информации практически не было, сейчас же к сожалению есть. Радует только тот факт, что я как тот "Неуловимый Джо", который никому нафиг не нужен.
Но сам факт мне не нравится, я не хочу чтобы кто-то мог узнать, что я качаю порнографию или учебник юного террориста, слишком попахивает тотальным контролем.
Поэтому я стал обдумывать варианты сохранения преимуществ пресонализации сайтов с отсутствующим побочным эффектом гипотетической возможности контроля.
Суть заключается в комбинации локального и глобального механизмов:

1. Создается универсальное локальное хранилище данных, которое позволяет сохранять различные данные.  Примерный список особенностей и функций:
  • Хранение данных в зашифрованном виде
  • Данные хранятся в древовидном виде. Неважно, будет ли это xml или лисповые списки, сделать конвертацию будет нетрудно.
  • Данные каждого сайта хранятся отдельно друг от друга для предотвращения кражи информации. При этом должна быть возможность создавать доверенные группы, которые будут иметь доступ друг к другу, т.к. в том же гугле есть множество сервисов. Я еще не сильно задумывался на эту тему.
  • Необходимо некое api для управления хранилищем: простейшие операции добавления/удаления/редактирования и чтения+некий набор операций для группировки, обход дерева и т.д. Это api  и будет использоваться для общения сайта с хранилищем.
  • Генерация закрытых и открытых ключей для идентификации сессии и шифрования траффика
2. На веб-серверах должна быть поддержка этой технологии в том смысле, что опционально должен быть отказ от логинов, поддержка шифрования траффика и установки связи при запросе клиента. Так же сервер должен знать, как обращаться с хранилищем на стороне клиента. Т.е. вместо собственной базы статистики будет использоваться клиентская через соответсвующий высокоуровневый(?) api. 
Таким образом, работа системы будет выглядеть примерно следующим образом:
  1. Клиент обращается к серверу в первый раз. Сервер дает обычный ответ+информацию о том, что он поддерживает технологию анонимной персонализации. Клиентская часть (вероятнее всего интегрированная с браузером) выдает пользователю сообщение, что данный сайт поддеживает такую возможность. 
  2. В случае подтверждения пользователем, клиентская часть создает хранилище для данного сайта. 
  3. Также клиентская часть генерирует пару ключей, одна из них будет отослана серверу, а вторая будет использоваться для шифрования исходящего траффика
  4. Клиент отсылает открытый ключ серверу,  сервер генерирует свою пару ключей и отсылает открытый клиенту. С этого момента начинается обычная работа системы по зашифрованным каналам, клиентская программа переходит в пассивыный режим. Эти ключи используются как аналог временного логина, через некоторое время они устаревают и процедуру нужно повторить. 
  5. В случае возникновения необходимости у сервера для сохраненияили чтения какой-либо информации, он будет просто возвращать страницу с необходимыми данными или командами в блоке страницы. Простой парсер на клиенте будет выполнять эти команды в api клиента  и вернуть данные серверу в запросе.  Структуру данных определяет софт на сервере, так что это будет похоже на обычную работу с СУБД.
Это первоначальный набросок, который описывает всю систему, есть еще некоторые белые пятна, над которыми надо думать дальше.

Признаки сетевого лохотрона

За время своего нахождения в сети наблюдал множество вариантов сетевых лохотронов. Возникла мысль составить список их основных признаков. Итак:
  1. Выделение ВАЖНЫХ СЛОВ заглавными буквами. Например: "Только ВЫ можете участвовать в нашей УНИКАЛЬНОЙ АКЦИИ". Это железный признак лохотрона или идиотии пишущего, не считая редких исключений- имитирования крика в сообщении в крайне редких случаях.
  2. Использование ярко выделенного(и по цвету в том числе) шрифта в средине предложений. То же самое что пункт 1.
  3. Попытка взять "на слабо". Пример: "НЕУДАЧНИКИ закроют эту страницу сразу же". Жалкая попытка манипулировать сознанием.
  4. Обращение адресованное лично каждому(не чувствуете подвоха?). Меня почему-то жутко раздражает этот прием. Пример: "Именно ты самый лучший и достойный нашего внимания. С тобой мы добьемся невиданных высот". Либо в более простых вариантах рассылка всем пользователям в локальной сети личными сообщениями типа "Привет, у тебя нету %что-угодно%?". Лично меня это жутко раздражает, т.к. пытаются манипулировать твоим восприятием. Сообщение построенное в виде личного, но нацеленное на широкую аудиторию уже насквозь лживо.
  5. Обещание невиданных результатов при нулевых вложениях. Тут имеются в виду также вложения собственных усилий. Т.е. если вам предлагают зарабатывать денег больше среднего при этом не напрягаясь, стоит очень задуматься на этот счет. Примерами могут служить различные предложения о работе в интернете и форекс. Да, есть люди которые зарабатывают на форексе, но поверьте, это не ваш случай. Кстати, у них лицензия не на брокерскую, а на игровую дефтельность. Т.е. шанс выиграть примерно такой же как в игровые автоматы или рулетку
  6. Использование невиданных технологий в производстве чего-либо. Например, нанотехнологии в косметике. При этом сам автор чаще всего совсем не понимает что это такое, но звучит очень красиво. Еще один вариант- бестопливные (или почти бестопливные, работающие на воде или еще чем-нибудь) генераторы. Этим модным словом стали называть всем известные вечные двигатели, на которые у всех уже развилась аллергия. Честно говоря, меня сильно смущает даже обычная реклама со всеми "экстрактами кашемира и шелка". Либо там действительно есть какое-то вещество, которое красиво окрестили таким названием для большей покупаемости, хотя добывают его уже давно не так, либо это вообще пустышка.
  7. Предложения изменения вашей жизни коренным образом. Запомните, никто кроме вас не сможет изменить жизнь, не ждите чуда. Все эти организации настроены на одно- отъем ваших денег. Отличным примером может стать церковь саентологии, основатель которой Лайфайет Рональд Хаббард говорил «Если человек действительно хочет заработать миллион долларов, то лучший способ — это основать собственную религию»  (журнал Reader’s Digest за май 1980 года)
  8. Тренинги НЛП. НЛП далеко не всегда и не на всех действует. Вместе с повышением шансов добиться своего, резко повышаются шансы получить по лицу от человека осознающего что им пытаются манипулировать. К тому же запомните, если человек и умеет использовать НЛП, это далеко не всегда значит что он сможет научить этому вас
Это примерный список, который мне удалось сходу вспомнить, возможно потом буду править и дополнять

Мой уютный бложик

Это уютный бложик имени меня. Он ни для кого не предназанчен, тут я буду складывать итересные для меня мысли